From 536c53c16cafcde3250e8d062aeb04c1590c1418 Mon Sep 17 00:00:00 2001 From: nlipka Date: Thu, 18 Feb 2021 11:37:20 +0100 Subject: add some space --- js/src/alert.js | 1 + 1 file changed, 1 insertion(+) (limited to 'js/src') diff --git a/js/src/alert.js b/js/src/alert.js index 3a018a638..8fc3f12a8 100644 --- a/js/src/alert.js +++ b/js/src/alert.js @@ -126,6 +126,7 @@ class Alert extends BaseComponent { * Data Api implementation * ------------------------------------------------------------------------ */ + EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DISMISS, Alert.handleDismiss(new Alert())) /** -- cgit v1.2.3 From e8f08d1802976b8200551de49354757f84e438cf Mon Sep 17 00:00:00 2001 From: Nikon the Third Date: Fri, 19 Feb 2021 09:24:53 +0100 Subject: Adjust regex `SAFE_URL_PATTERN` for use with test method of regexes. (#33136) The test method on regexes behaves different than the match method on strings in the presence of the global modifier. Add a unit test for sanitizing the same template twice. Co-authored-by: XhmikosR --- js/src/util/sanitizer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'js/src') diff --git a/js/src/util/sanitizer.js b/js/src/util/sanitizer.js index 18ac6f943..57653a891 100644 --- a/js/src/util/sanitizer.js +++ b/js/src/util/sanitizer.js @@ -23,7 +23,7 @@ const ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i * * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts */ -const SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file):|[^#&/:?]*(?:[#/?]|$))/gi +const SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file):|[^#&/:?]*(?:[#/?]|$))/i /** * A pattern that matches safe data URLs. Only matches image, video and audio types. -- cgit v1.2.3 From dc5e3328c12058de7fb6404edbe5dcee61f3400f Mon Sep 17 00:00:00 2001 From: Rohit Sharma Date: Mon, 22 Feb 2021 12:31:04 +0530 Subject: Allow constructors to accept a CSS selector (#32245) Co-authored-by: XhmikosR Co-authored-by: Mark Otto --- js/src/base-component.js | 4 +++- js/src/collapse.js | 6 +++--- js/src/modal.js | 2 +- js/src/scrollspy.js | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) (limited to 'js/src') diff --git a/js/src/base-component.js b/js/src/base-component.js index 9de274bd0..989a64156 100644 --- a/js/src/base-component.js +++ b/js/src/base-component.js @@ -17,12 +17,14 @@ const VERSION = '5.0.0-beta2' class BaseComponent { constructor(element) { + element = typeof element === 'string' ? document.querySelector(element) : element + if (!element) { return } this._element = element - Data.setData(element, this.constructor.DATA_KEY, this) + Data.setData(this._element, this.constructor.DATA_KEY, this) } dispose() { diff --git a/js/src/collapse.js b/js/src/collapse.js index 0a1b47547..f86166765 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -72,8 +72,8 @@ class Collapse extends BaseComponent { this._isTransitioning = false this._config = this._getConfig(config) this._triggerArray = SelectorEngine.find( - `${SELECTOR_DATA_TOGGLE}[href="#${element.id}"],` + - `${SELECTOR_DATA_TOGGLE}[data-bs-target="#${element.id}"]` + `${SELECTOR_DATA_TOGGLE}[href="#${this._element.id}"],` + + `${SELECTOR_DATA_TOGGLE}[data-bs-target="#${this._element.id}"]` ) const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE) @@ -82,7 +82,7 @@ class Collapse extends BaseComponent { const elem = toggleList[i] const selector = getSelectorFromElement(elem) const filterElement = SelectorEngine.find(selector) - .filter(foundElem => foundElem === element) + .filter(foundElem => foundElem === this._element) if (selector !== null && filterElement.length) { this._selector = selector diff --git a/js/src/modal.js b/js/src/modal.js index 79a2f143a..4f42e733e 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -83,7 +83,7 @@ class Modal extends BaseComponent { super(element) this._config = this._getConfig(config) - this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, element) + this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element) this._backdrop = null this._isShown = false this._isBodyOverflowing = false diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index 43a91e5e9..0c51eab0f 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -68,7 +68,7 @@ const METHOD_POSITION = 'position' class ScrollSpy extends BaseComponent { constructor(element, config) { super(element) - this._scrollElement = element.tagName === 'BODY' ? window : element + this._scrollElement = this._element.tagName === 'BODY' ? window : this._element this._config = this._getConfig(config) this._selector = `${this._config.target} ${SELECTOR_NAV_LINKS}, ${this._config.target} ${SELECTOR_LIST_ITEMS}, ${this._config.target} .${CLASS_NAME_DROPDOWN_ITEM}` this._offsets = [] -- cgit v1.2.3 From 056216a3bd2fd1f28ba9ec6f2797aa2aaec5c6f0 Mon Sep 17 00:00:00 2001 From: Muhammadamin Date: Tue, 23 Feb 2021 07:52:09 -0500 Subject: modal: don't add margin & padding when sticky is not full width (#30621) * modal: don't add margin & padding when sticky is not full width * Check if element is shorter than window Co-authored-by: XhmikosR Co-authored-by: Rohit Sharma --- js/src/modal.js | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'js/src') diff --git a/js/src/modal.js b/js/src/modal.js index 4f42e733e..5afb9791b 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -466,6 +466,10 @@ class Modal extends BaseComponent { _setElementAttributes(selector, styleProp, callback) { SelectorEngine.find(selector) .forEach(element => { + if (element !== document.body && window.innerWidth > element.clientWidth + this._scrollbarWidth) { + return + } + const actualValue = element.style[styleProp] const calculatedValue = window.getComputedStyle(element)[styleProp] Manipulator.setDataAttribute(element, styleProp, actualValue) -- cgit v1.2.3 From 48a95f7280735d6f8962fe8b17975b03e351710c Mon Sep 17 00:00:00 2001 From: alpadev <2838324+alpadev@users.noreply.github.com> Date: Tue, 2 Mar 2021 15:55:44 +0100 Subject: refactor: use a Map instead of an Object in dom/data (#32180) Co-authored-by: XhmikosR Co-authored-by: Rohit Sharma --- js/src/alert.js | 2 +- js/src/base-component.js | 6 ++-- js/src/button.js | 4 +-- js/src/carousel.js | 6 ++-- js/src/collapse.js | 8 ++--- js/src/dom/data.js | 80 +++++++++++++++++++++--------------------------- js/src/dropdown.js | 4 +-- js/src/modal.js | 4 +-- js/src/popover.js | 4 +-- js/src/scrollspy.js | 2 +- js/src/tab.js | 4 +-- js/src/toast.js | 2 +- js/src/tooltip.js | 8 ++--- 13 files changed, 62 insertions(+), 72 deletions(-) (limited to 'js/src') diff --git a/js/src/alert.js b/js/src/alert.js index 8fc3f12a8..d10e6c8da 100644 --- a/js/src/alert.js +++ b/js/src/alert.js @@ -98,7 +98,7 @@ class Alert extends BaseComponent { static jQueryInterface(config) { return this.each(function () { - let data = Data.getData(this, DATA_KEY) + let data = Data.get(this, DATA_KEY) if (!data) { data = new Alert(this) diff --git a/js/src/base-component.js b/js/src/base-component.js index 989a64156..2a9e29c2a 100644 --- a/js/src/base-component.js +++ b/js/src/base-component.js @@ -24,18 +24,18 @@ class BaseComponent { } this._element = element - Data.setData(this._element, this.constructor.DATA_KEY, this) + Data.set(this._element, this.constructor.DATA_KEY, this) } dispose() { - Data.removeData(this._element, this.constructor.DATA_KEY) + Data.remove(this._element, this.constructor.DATA_KEY) this._element = null } /** Static */ static getInstance(element) { - return Data.getData(element, this.DATA_KEY) + return Data.get(element, this.DATA_KEY) } static get VERSION() { diff --git a/js/src/button.js b/js/src/button.js index 4ec48ca08..7a9449f07 100644 --- a/js/src/button.js +++ b/js/src/button.js @@ -51,7 +51,7 @@ class Button extends BaseComponent { static jQueryInterface(config) { return this.each(function () { - let data = Data.getData(this, DATA_KEY) + let data = Data.get(this, DATA_KEY) if (!data) { data = new Button(this) @@ -75,7 +75,7 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, event => { const button = event.target.closest(SELECTOR_DATA_TOGGLE) - let data = Data.getData(button, DATA_KEY) + let data = Data.get(button, DATA_KEY) if (!data) { data = new Button(button) } diff --git a/js/src/carousel.js b/js/src/carousel.js index 75f8a4da7..a825aaef4 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -527,7 +527,7 @@ class Carousel extends BaseComponent { // Static static carouselInterface(element, config) { - let data = Data.getData(element, DATA_KEY) + let data = Data.get(element, DATA_KEY) let _config = { ...Default, ...Manipulator.getDataAttributes(element) @@ -586,7 +586,7 @@ class Carousel extends BaseComponent { Carousel.carouselInterface(target, config) if (slideIndex) { - Data.getData(target, DATA_KEY).to(slideIndex) + Data.get(target, DATA_KEY).to(slideIndex) } event.preventDefault() @@ -605,7 +605,7 @@ EventHandler.on(window, EVENT_LOAD_DATA_API, () => { const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE) for (let i = 0, len = carousels.length; i < len; i++) { - Carousel.carouselInterface(carousels[i], Data.getData(carousels[i], DATA_KEY)) + Carousel.carouselInterface(carousels[i], Data.get(carousels[i], DATA_KEY)) } }) diff --git a/js/src/collapse.js b/js/src/collapse.js index f86166765..036ffcf24 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -147,7 +147,7 @@ class Collapse extends BaseComponent { const container = SelectorEngine.findOne(this._selector) if (actives) { const tempActiveData = actives.find(elem => container !== elem) - activesData = tempActiveData ? Data.getData(tempActiveData, DATA_KEY) : null + activesData = tempActiveData ? Data.get(tempActiveData, DATA_KEY) : null if (activesData && activesData._isTransitioning) { return @@ -166,7 +166,7 @@ class Collapse extends BaseComponent { } if (!activesData) { - Data.setData(elemActive, DATA_KEY, null) + Data.set(elemActive, DATA_KEY, null) } }) } @@ -332,7 +332,7 @@ class Collapse extends BaseComponent { // Static static collapseInterface(element, config) { - let data = Data.getData(element, DATA_KEY) + let data = Data.get(element, DATA_KEY) const _config = { ...Default, ...Manipulator.getDataAttributes(element), @@ -380,7 +380,7 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( const selectorElements = SelectorEngine.find(selector) selectorElements.forEach(element => { - const data = Data.getData(element, DATA_KEY) + const data = Data.get(element, DATA_KEY) let config if (data) { // update parent attribute diff --git a/js/src/dom/data.js b/js/src/dom/data.js index c93a8dc7c..1d283d68b 100644 --- a/js/src/dom/data.js +++ b/js/src/dom/data.js @@ -11,57 +11,47 @@ * ------------------------------------------------------------------------ */ -const mapData = (() => { - const storeData = {} - let id = 1 - return { - set(element, key, data) { - if (typeof element.bsKey === 'undefined') { - element.bsKey = { - key, - id - } - id++ - } +const elementMap = new Map() - storeData[element.bsKey.id] = data - }, - get(element, key) { - if (!element || typeof element.bsKey === 'undefined') { - return null - } - - const keyProperties = element.bsKey - if (keyProperties.key === key) { - return storeData[keyProperties.id] - } +export default { + set(element, key, instance) { + if (!elementMap.has(element)) { + elementMap.set(element, new Map()) + } - return null - }, - delete(element, key) { - if (typeof element.bsKey === 'undefined') { - return - } + const instanceMap = elementMap.get(element) - const keyProperties = element.bsKey - if (keyProperties.key === key) { - delete storeData[keyProperties.id] - delete element.bsKey - } + // make it clear we only want one instance per element + // can be removed later when multiple key/instances are fine to be used + if (!instanceMap.has(key) && instanceMap.size !== 0) { + // eslint-disable-next-line no-console + console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`) + return } - } -})() -const Data = { - setData(instance, key, data) { - mapData.set(instance, key, data) + instanceMap.set(key, instance) }, - getData(instance, key) { - return mapData.get(instance, key) + + get(element, key) { + if (elementMap.has(element)) { + return elementMap.get(element).get(key) || null + } + + return null }, - removeData(instance, key) { - mapData.delete(instance, key) + + remove(element, key) { + if (!elementMap.has(element)) { + return + } + + const instanceMap = elementMap.get(element) + + instanceMap.delete(key) + + // free up element references if there are no instances left for an element + if (instanceMap.size === 0) { + elementMap.delete(element) + } } } - -export default Data diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 590c74801..fea0b1919 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -357,7 +357,7 @@ class Dropdown extends BaseComponent { // Static static dropdownInterface(element, config) { - let data = Data.getData(element, DATA_KEY) + let data = Data.get(element, DATA_KEY) const _config = typeof config === 'object' ? config : null if (!data) { @@ -387,7 +387,7 @@ class Dropdown extends BaseComponent { const toggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE) for (let i = 0, len = toggles.length; i < len; i++) { - const context = Data.getData(toggles[i], DATA_KEY) + const context = Data.get(toggles[i], DATA_KEY) const relatedTarget = { relatedTarget: toggles[i] } diff --git a/js/src/modal.js b/js/src/modal.js index 5afb9791b..332d636d0 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -508,7 +508,7 @@ class Modal extends BaseComponent { static jQueryInterface(config, relatedTarget) { return this.each(function () { - let data = Data.getData(this, DATA_KEY) + let data = Data.get(this, DATA_KEY) const _config = { ...Default, ...Manipulator.getDataAttributes(this), @@ -556,7 +556,7 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( }) }) - let data = Data.getData(target, DATA_KEY) + let data = Data.get(target, DATA_KEY) if (!data) { const config = { ...Manipulator.getDataAttributes(target), diff --git a/js/src/popover.js b/js/src/popover.js index 0677dafa0..553544754 100644 --- a/js/src/popover.js +++ b/js/src/popover.js @@ -136,7 +136,7 @@ class Popover extends Tooltip { static jQueryInterface(config) { return this.each(function () { - let data = Data.getData(this, DATA_KEY) + let data = Data.get(this, DATA_KEY) const _config = typeof config === 'object' ? config : null if (!data && /dispose|hide/.test(config)) { @@ -145,7 +145,7 @@ class Popover extends Tooltip { if (!data) { data = new Popover(this, _config) - Data.setData(this, DATA_KEY, data) + Data.set(this, DATA_KEY, data) } if (typeof config === 'string') { diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index 0c51eab0f..c7472439b 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -278,7 +278,7 @@ class ScrollSpy extends BaseComponent { static jQueryInterface(config) { return this.each(function () { - let data = Data.getData(this, DATA_KEY) + let data = Data.get(this, DATA_KEY) const _config = typeof config === 'object' && config if (!data) { diff --git a/js/src/tab.js b/js/src/tab.js index e60ecddb5..95968f4f8 100644 --- a/js/src/tab.js +++ b/js/src/tab.js @@ -182,7 +182,7 @@ class Tab extends BaseComponent { static jQueryInterface(config) { return this.each(function () { - const data = Data.getData(this, DATA_KEY) || new Tab(this) + const data = Data.get(this, DATA_KEY) || new Tab(this) if (typeof config === 'string') { if (typeof data[config] === 'undefined') { @@ -204,7 +204,7 @@ class Tab extends BaseComponent { EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) { event.preventDefault() - const data = Data.getData(this, DATA_KEY) || new Tab(this) + const data = Data.get(this, DATA_KEY) || new Tab(this) data.show() }) diff --git a/js/src/toast.js b/js/src/toast.js index 2f451aab7..ea91163d8 100644 --- a/js/src/toast.js +++ b/js/src/toast.js @@ -189,7 +189,7 @@ class Toast extends BaseComponent { static jQueryInterface(config) { return this.each(function () { - let data = Data.getData(this, DATA_KEY) + let data = Data.get(this, DATA_KEY) const _config = typeof config === 'object' && config if (!data) { diff --git a/js/src/tooltip.js b/js/src/tooltip.js index d35b5e0ab..6f33245f8 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -275,7 +275,7 @@ class Tooltip extends BaseComponent { this._addAttachmentClass(attachment) const container = this._getContainer() - Data.setData(tip, this.constructor.DATA_KEY, this) + Data.set(tip, this.constructor.DATA_KEY, this) if (!this._element.ownerDocument.documentElement.contains(this.tip)) { container.appendChild(tip) @@ -465,11 +465,11 @@ class Tooltip extends BaseComponent { _initializeOnDelegatedTarget(event, context) { const dataKey = this.constructor.DATA_KEY - context = context || Data.getData(event.delegateTarget, dataKey) + context = context || Data.get(event.delegateTarget, dataKey) if (!context) { context = new this.constructor(event.delegateTarget, this._getDelegateConfig()) - Data.setData(event.delegateTarget, dataKey, context) + Data.set(event.delegateTarget, dataKey, context) } return context @@ -761,7 +761,7 @@ class Tooltip extends BaseComponent { static jQueryInterface(config) { return this.each(function () { - let data = Data.getData(this, DATA_KEY) + let data = Data.get(this, DATA_KEY) const _config = typeof config === 'object' && config if (!data && /dispose|hide/.test(config)) { -- cgit v1.2.3 From b9e51dc3c4400ede5e72991dd0efacf9dbcb694e Mon Sep 17 00:00:00 2001 From: Rohit Sharma Date: Tue, 2 Mar 2021 20:57:13 +0530 Subject: =?UTF-8?q?Dropdown=20=E2=80=94=20Drop=20`flip`=20option=20(#33198?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- js/src/dropdown.js | 3 --- 1 file changed, 3 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index fea0b1919..eeec9076d 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -73,7 +73,6 @@ const PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start' const Default = { offset: [0, 2], - flip: true, boundary: 'clippingParents', reference: 'toggle', display: 'dynamic', @@ -82,7 +81,6 @@ const Default = { const DefaultType = { offset: '(array|string|function)', - flip: 'boolean', boundary: '(string|element)', reference: '(string|element|object)', display: 'string', @@ -328,7 +326,6 @@ class Dropdown extends BaseComponent { modifiers: [{ name: 'preventOverflow', options: { - altBoundary: this._config.flip, boundary: this._config.boundary } }, -- cgit v1.2.3 From 548be2ed6604ddfc8488cd4a793c6271c2caf485 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Tue, 2 Mar 2021 19:10:10 +0200 Subject: Offcanvas as component (#29017) * Add a new offcanvas component * offcanvas.js: switch to string constants and `event.key` * Remove unneeded code * Sass optimizations * Fixes Make sure the element is hidden and not offscreen when inactive fix close icon negative margins Add content in right & bottom examples Re-fix bottom offcanvas height not to cover all viewport * Wording tweaks * update tests and offcanvas class * separate scrollbar functionality and use it in offcanvas * Update .bundlewatch.config.json * fix focus * update btn-close / fix focus on close * add aria-modal and role return focus on trigger when offcanvas is closed change body scrolling timings * move common code to reusable functions * add aria-labelledby * Replace lorem ipsum text * fix focus when offcanvas is closed * updates * revert modal, add tests for scrollbar * show backdrop by default * Update offcanvas.md * Update offcanvas CSS to better match modals - Add background-clip for borders - Move from outline to border (less clever, more consistent) - Add scss-docs in vars * Revamp offcanvas docs - Add static example to show and explain the components - Split live examples and rename them - Simplify example content - Expand docs notes elsewhere - Add sass docs * Add .offcanvas-title instead of .modal-title * Rename offcanvas example to offcanvas-navbar to reflect it's purpose * labelledby references title and not header * Add default shadow to offcanvas * enable offcanvas-body to fill all the remaining wrapper area * Be more descriptive, on Accessibility area * remove redundant classes * ensure in case of an already open offcanvas, not to open another one * bring back backdrop|scroll combinations * bring back toggling class * refactor scrollbar method, plus tests * add check if element is not full-width, according to #30621 * revert all in modal * use documentElement innerWidth * Rename classes to -start and -end Also copyedit some docs wording * omit some things on scrollbar * PASS BrowserStack tests -- IOS devices, Android devices and Browsers on Mac, hide scrollbar by default and appear it, only while scrolling. * Rename '_handleClosing' to '_addEventListeners' * change pipe usage to comma * change Data.getData to Data.get Co-authored-by: XhmikosR Co-authored-by: Martijn Cuppens Co-authored-by: Mark Otto --- js/src/offcanvas.js | 239 +++++++++++++++++++++++++++++++++++++++++++++++ js/src/util/scrollbar.js | 70 ++++++++++++++ 2 files changed, 309 insertions(+) create mode 100644 js/src/offcanvas.js create mode 100644 js/src/util/scrollbar.js (limited to 'js/src') diff --git a/js/src/offcanvas.js b/js/src/offcanvas.js new file mode 100644 index 000000000..148f003e9 --- /dev/null +++ b/js/src/offcanvas.js @@ -0,0 +1,239 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v5.0.0-beta2): offcanvas.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + +import { + defineJQueryPlugin, + getElementFromSelector, + getSelectorFromElement, + getTransitionDurationFromElement, + isVisible +} from './util/index' +import { hide as scrollBarHide, reset as scrollBarReset } from './util/scrollbar' +import Data from './dom/data' +import EventHandler from './dom/event-handler' +import BaseComponent from './base-component' +import SelectorEngine from './dom/selector-engine' + +/** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + +const NAME = 'offcanvas' +const DATA_KEY = 'bs.offcanvas' +const EVENT_KEY = `.${DATA_KEY}` +const DATA_API_KEY = '.data-api' +const ESCAPE_KEY = 'Escape' +const DATA_BODY_ACTIONS = 'data-bs-body' + +const CLASS_NAME_BACKDROP_BODY = 'offcanvas-backdrop' +const CLASS_NAME_DISABLED = 'disabled' +const CLASS_NAME_SHOW = 'show' +const CLASS_NAME_TOGGLING = 'offcanvas-toggling' +const ACTIVE_SELECTOR = `.offcanvas.show, .${CLASS_NAME_TOGGLING}` + +const EVENT_SHOW = `show${EVENT_KEY}` +const EVENT_SHOWN = `shown${EVENT_KEY}` +const EVENT_HIDE = `hide${EVENT_KEY}` +const EVENT_HIDDEN = `hidden${EVENT_KEY}` +const EVENT_FOCUSIN = `focusin${EVENT_KEY}` +const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` +const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}` + +const SELECTOR_DATA_DISMISS = '[data-bs-dismiss="offcanvas"]' +const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="offcanvas"]' + +/** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + +class OffCanvas extends BaseComponent { + constructor(element) { + super(element) + + this._isShown = element.classList.contains(CLASS_NAME_SHOW) + this._bodyOptions = element.getAttribute(DATA_BODY_ACTIONS) || '' + this._addEventListeners() + } + + // Public + + toggle(relatedTarget) { + return this._isShown ? this.hide() : this.show(relatedTarget) + } + + show(relatedTarget) { + if (this._isShown) { + return + } + + const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, { relatedTarget }) + + if (showEvent.defaultPrevented) { + return + } + + this._isShown = true + this._element.style.visibility = 'visible' + + if (this._bodyOptionsHas('backdrop') || !this._bodyOptions.length) { + document.body.classList.add(CLASS_NAME_BACKDROP_BODY) + } + + if (!this._bodyOptionsHas('scroll')) { + scrollBarHide() + } + + this._element.classList.add(CLASS_NAME_TOGGLING) + this._element.removeAttribute('aria-hidden') + this._element.setAttribute('aria-modal', true) + this._element.setAttribute('role', 'dialog') + this._element.classList.add(CLASS_NAME_SHOW) + + const completeCallBack = () => { + this._element.classList.remove(CLASS_NAME_TOGGLING) + EventHandler.trigger(this._element, EVENT_SHOWN, { relatedTarget }) + this._enforceFocusOnElement(this._element) + } + + setTimeout(completeCallBack, getTransitionDurationFromElement(this._element)) + } + + hide() { + if (!this._isShown) { + return + } + + const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE) + + if (hideEvent.defaultPrevented) { + return + } + + this._element.classList.add(CLASS_NAME_TOGGLING) + EventHandler.off(document, EVENT_FOCUSIN) + this._element.blur() + this._isShown = false + this._element.classList.remove(CLASS_NAME_SHOW) + + const completeCallback = () => { + this._element.setAttribute('aria-hidden', true) + this._element.removeAttribute('aria-modal') + this._element.removeAttribute('role') + this._element.style.visibility = 'hidden' + + if (this._bodyOptionsHas('backdrop') || !this._bodyOptions.length) { + document.body.classList.remove(CLASS_NAME_BACKDROP_BODY) + } + + if (!this._bodyOptionsHas('scroll')) { + scrollBarReset() + } + + EventHandler.trigger(this._element, EVENT_HIDDEN) + this._element.classList.remove(CLASS_NAME_TOGGLING) + } + + setTimeout(completeCallback, getTransitionDurationFromElement(this._element)) + } + + _enforceFocusOnElement(element) { + EventHandler.off(document, EVENT_FOCUSIN) // guard against infinite focus loop + EventHandler.on(document, EVENT_FOCUSIN, event => { + if (document !== event.target && + element !== event.target && + !element.contains(event.target)) { + element.focus() + } + }) + element.focus() + } + + _bodyOptionsHas(option) { + return this._bodyOptions.split(',').includes(option) + } + + _addEventListeners() { + EventHandler.on(this._element, EVENT_CLICK_DISMISS, SELECTOR_DATA_DISMISS, () => this.hide()) + + EventHandler.on(document, 'keydown', event => { + if (event.key === ESCAPE_KEY) { + this.hide() + } + }) + + EventHandler.on(document, EVENT_CLICK_DATA_API, event => { + const target = SelectorEngine.findOne(getSelectorFromElement(event.target)) + if (!this._element.contains(event.target) && target !== this._element) { + this.hide() + } + }) + } + + // Static + + static jQueryInterface(config) { + return this.each(function () { + const data = Data.get(this, DATA_KEY) || new OffCanvas(this) + + if (typeof config === 'string') { + if (typeof data[config] === 'undefined') { + throw new TypeError(`No method named "${config}"`) + } + + data[config](this) + } + }) + } +} + +/** + * ------------------------------------------------------------------------ + * Data Api implementation + * ------------------------------------------------------------------------ + */ + +EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) { + const target = getElementFromSelector(this) + + if (['A', 'AREA'].includes(this.tagName)) { + event.preventDefault() + } + + if (this.disabled || this.classList.contains(CLASS_NAME_DISABLED)) { + return + } + + EventHandler.one(target, EVENT_HIDDEN, () => { + // focus on trigger when it is closed + if (isVisible(this)) { + this.focus() + } + }) + + // avoid conflict when clicking a toggler of an offcanvas, while another is open + const allReadyOpen = SelectorEngine.findOne(ACTIVE_SELECTOR) + if (allReadyOpen && allReadyOpen !== target) { + return + } + + const data = Data.get(target, DATA_KEY) || new OffCanvas(target) + data.toggle(this) +}) + +/** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + +defineJQueryPlugin(NAME, OffCanvas) + +export default OffCanvas diff --git a/js/src/util/scrollbar.js b/js/src/util/scrollbar.js new file mode 100644 index 000000000..d2f3919e6 --- /dev/null +++ b/js/src/util/scrollbar.js @@ -0,0 +1,70 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v5.0.0-beta2): util/scrollBar.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + +import SelectorEngine from '../dom/selector-engine' +import Manipulator from '../dom/manipulator' + +const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed' +const SELECTOR_STICKY_CONTENT = '.sticky-top' + +const getWidth = () => { + // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes + const documentWidth = document.documentElement.clientWidth + return Math.abs(window.innerWidth - documentWidth) +} + +const hide = (width = getWidth()) => { + document.body.style.overflow = 'hidden' + _setElementAttributes(SELECTOR_FIXED_CONTENT, 'paddingRight', calculatedValue => calculatedValue + width) + _setElementAttributes(SELECTOR_STICKY_CONTENT, 'marginRight', calculatedValue => calculatedValue - width) + _setElementAttributes('body', 'paddingRight', calculatedValue => calculatedValue + width) +} + +const _setElementAttributes = (selector, styleProp, callback) => { + const scrollbarWidth = getWidth() + SelectorEngine.find(selector) + .forEach(element => { + if (element !== document.body && window.innerWidth > element.clientWidth + scrollbarWidth) { + return + } + + const actualValue = element.style[styleProp] + const calculatedValue = window.getComputedStyle(element)[styleProp] + Manipulator.setDataAttribute(element, styleProp, actualValue) + element.style[styleProp] = callback(Number.parseFloat(calculatedValue)) + 'px' + }) +} + +const reset = () => { + document.body.style.overflow = 'auto' + _resetElementAttributes(SELECTOR_FIXED_CONTENT, 'paddingRight') + _resetElementAttributes(SELECTOR_STICKY_CONTENT, 'marginRight') + _resetElementAttributes('body', 'paddingRight') +} + +const _resetElementAttributes = (selector, styleProp) => { + SelectorEngine.find(selector).forEach(element => { + const value = Manipulator.getDataAttribute(element, styleProp) + if (typeof value === 'undefined' && element === document.body) { + element.style.removeProperty(styleProp) + } else { + Manipulator.removeDataAttribute(element, styleProp) + element.style[styleProp] = value + } + }) +} + +const isBodyOverflowing = () => { + return getWidth() > 0 +} + +export { + getWidth, + hide, + isBodyOverflowing, + reset +} -- cgit v1.2.3 From e163d988457c77f83296b0f950659cd9d2b7bb94 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Mon, 8 Mar 2021 18:35:59 +0200 Subject: modal: move common code to a new `isAnimated` method (#33056) --- js/src/modal.js | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) (limited to 'js/src') diff --git a/js/src/modal.js b/js/src/modal.js index 332d636d0..40fd226bf 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -113,7 +113,7 @@ class Modal extends BaseComponent { return } - if (this._element.classList.contains(CLASS_NAME_FADE)) { + if (this._isAnimated()) { this._isTransitioning = true } @@ -164,9 +164,9 @@ class Modal extends BaseComponent { } this._isShown = false - const transition = this._element.classList.contains(CLASS_NAME_FADE) + const isAnimated = this._isAnimated() - if (transition) { + if (isAnimated) { this._isTransitioning = true } @@ -180,7 +180,7 @@ class Modal extends BaseComponent { EventHandler.off(this._element, EVENT_CLICK_DISMISS) EventHandler.off(this._dialog, EVENT_MOUSEDOWN_DISMISS) - if (transition) { + if (isAnimated) { const transitionDuration = getTransitionDurationFromElement(this._element) EventHandler.one(this._element, 'transitionend', event => this._hideModal(event)) @@ -229,7 +229,7 @@ class Modal extends BaseComponent { } _showElement(relatedTarget) { - const transition = this._element.classList.contains(CLASS_NAME_FADE) + const isAnimated = this._isAnimated() const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog) if (!this._element.parentNode || this._element.parentNode.nodeType !== Node.ELEMENT_NODE) { @@ -247,7 +247,7 @@ class Modal extends BaseComponent { modalBody.scrollTop = 0 } - if (transition) { + if (isAnimated) { reflow(this._element) } @@ -268,7 +268,7 @@ class Modal extends BaseComponent { }) } - if (transition) { + if (isAnimated) { const transitionDuration = getTransitionDurationFromElement(this._dialog) EventHandler.one(this._dialog, 'transitionend', transitionComplete) @@ -332,16 +332,13 @@ class Modal extends BaseComponent { } _showBackdrop(callback) { - const animate = this._element.classList.contains(CLASS_NAME_FADE) ? - CLASS_NAME_FADE : - '' - + const isAnimated = this._isAnimated() if (this._isShown && this._config.backdrop) { this._backdrop = document.createElement('div') this._backdrop.className = CLASS_NAME_BACKDROP - if (animate) { - this._backdrop.classList.add(animate) + if (isAnimated) { + this._backdrop.classList.add(CLASS_NAME_FADE) } document.body.appendChild(this._backdrop) @@ -363,13 +360,13 @@ class Modal extends BaseComponent { } }) - if (animate) { + if (isAnimated) { reflow(this._backdrop) } this._backdrop.classList.add(CLASS_NAME_SHOW) - if (!animate) { + if (!isAnimated) { callback() return } @@ -386,7 +383,7 @@ class Modal extends BaseComponent { callback() } - if (this._element.classList.contains(CLASS_NAME_FADE)) { + if (isAnimated) { const backdropTransitionDuration = getTransitionDurationFromElement(this._backdrop) EventHandler.one(this._backdrop, 'transitionend', callbackRemove) emulateTransitionEnd(this._backdrop, backdropTransitionDuration) @@ -398,6 +395,10 @@ class Modal extends BaseComponent { } } + _isAnimated() { + return this._element.classList.contains(CLASS_NAME_FADE) + } + _triggerBackdropTransition() { const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED) if (hideEvent.defaultPrevented) { -- cgit v1.2.3 From 6ecd1c626e4129daf45a47b44c2e2eae60a09fa3 Mon Sep 17 00:00:00 2001 From: Rohit Sharma Date: Tue, 16 Mar 2021 10:51:04 +0530 Subject: Change the name of the `Offcanvas` constructor (#33261) --- js/src/offcanvas.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'js/src') diff --git a/js/src/offcanvas.js b/js/src/offcanvas.js index 148f003e9..f4927aacd 100644 --- a/js/src/offcanvas.js +++ b/js/src/offcanvas.js @@ -54,7 +54,7 @@ const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="offcanvas"]' * ------------------------------------------------------------------------ */ -class OffCanvas extends BaseComponent { +class Offcanvas extends BaseComponent { constructor(element) { super(element) @@ -181,7 +181,7 @@ class OffCanvas extends BaseComponent { static jQueryInterface(config) { return this.each(function () { - const data = Data.get(this, DATA_KEY) || new OffCanvas(this) + const data = Data.get(this, DATA_KEY) || new Offcanvas(this) if (typeof config === 'string') { if (typeof data[config] === 'undefined') { @@ -224,7 +224,7 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( return } - const data = Data.get(target, DATA_KEY) || new OffCanvas(target) + const data = Data.get(target, DATA_KEY) || new Offcanvas(target) data.toggle(this) }) @@ -234,6 +234,6 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( * ------------------------------------------------------------------------ */ -defineJQueryPlugin(NAME, OffCanvas) +defineJQueryPlugin(NAME, Offcanvas) -export default OffCanvas +export default Offcanvas -- cgit v1.2.3 From d491c29aa005177ef148c40d4b7b0a3decc7edef Mon Sep 17 00:00:00 2001 From: Ryan Berliner Date: Sat, 6 Mar 2021 03:35:28 +0200 Subject: prevent tooltip from being deleted on quick re-activations --- js/src/tooltip.js | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'js/src') diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 6f33245f8..e9f9bfff1 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -329,6 +329,10 @@ class Tooltip extends BaseComponent { const tip = this.getTipElement() const complete = () => { + if (this._isWithActiveTrigger()) { + return + } + if (this._hoverState !== HOVER_STATE_SHOW && tip.parentNode) { tip.parentNode.removeChild(tip) } -- cgit v1.2.3 From 6ef70b342c27445685715f51cfcafb719356870f Mon Sep 17 00:00:00 2001 From: Ryan Berliner Date: Sat, 6 Mar 2021 23:57:23 +0200 Subject: prevent quick interactions from misplacing tooltips --- js/src/tooltip.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'js/src') diff --git a/js/src/tooltip.js b/js/src/tooltip.js index e9f9bfff1..857f72c8a 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -283,6 +283,10 @@ class Tooltip extends BaseComponent { EventHandler.trigger(this._element, this.constructor.Event.INSERTED) + if (this._popper) { + this._popper.destroy() + } + this._popper = Popper.createPopper(this._element, tip, this._getPopperConfig(attachment)) tip.classList.add(CLASS_NAME_SHOW) @@ -650,7 +654,7 @@ class Tooltip extends BaseComponent { if (event) { context._activeTrigger[ event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER - ] = false + ] = context._element.contains(event.relatedTarget) } if (context._isWithActiveTrigger()) { -- cgit v1.2.3 From 72d23135799059d4282ea5764455f92f39ced5a5 Mon Sep 17 00:00:00 2001 From: Ryan Berliner Date: Sun, 7 Mar 2021 08:28:41 -0500 Subject: reuse existing popper on show during tooltip fadeout --- js/src/tooltip.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'js/src') diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 857f72c8a..de7dcca69 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -284,11 +284,11 @@ class Tooltip extends BaseComponent { EventHandler.trigger(this._element, this.constructor.Event.INSERTED) if (this._popper) { - this._popper.destroy() + this._popper.update() + } else { + this._popper = Popper.createPopper(this._element, tip, this._getPopperConfig(attachment)) } - this._popper = Popper.createPopper(this._element, tip, this._getPopperConfig(attachment)) - tip.classList.add(CLASS_NAME_SHOW) const customClass = typeof this.config.customClass === 'function' ? this.config.customClass() : this.config.customClass -- cgit v1.2.3 From 99b2c0b390660b2032c3129b8ebff02fa1e034c9 Mon Sep 17 00:00:00 2001 From: Ryan Berliner Date: Sun, 7 Mar 2021 17:09:17 +0200 Subject: only trigger tooltip inserted event on true dom insert --- js/src/tooltip.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'js/src') diff --git a/js/src/tooltip.js b/js/src/tooltip.js index de7dcca69..979bd0773 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -279,10 +279,9 @@ class Tooltip extends BaseComponent { if (!this._element.ownerDocument.documentElement.contains(this.tip)) { container.appendChild(tip) + EventHandler.trigger(this._element, this.constructor.Event.INSERTED) } - EventHandler.trigger(this._element, this.constructor.Event.INSERTED) - if (this._popper) { this._popper.update() } else { -- cgit v1.2.3 From ddf72bc6124618e3f4b6a056503d4f51d49c928e Mon Sep 17 00:00:00 2001 From: GeoSot Date: Tue, 16 Mar 2021 18:35:03 +0200 Subject: Accept data-bs-body option in the configuration object as well (#33248) * Accept data-bs-body option in the configuration object as well Tweak jqueryInterface, add some more tests * Fix Markdown table formatting and tweak the wording on backdrop Co-authored-by: Mark Otto Co-authored-by: XhmikosR --- js/src/offcanvas.js | 76 +++++++++++++++++++++++++++++++++++++--------------- js/src/util/index.js | 17 ++++++++++++ 2 files changed, 72 insertions(+), 21 deletions(-) (limited to 'js/src') diff --git a/js/src/offcanvas.js b/js/src/offcanvas.js index f4927aacd..4b98565e2 100644 --- a/js/src/offcanvas.js +++ b/js/src/offcanvas.js @@ -10,13 +10,16 @@ import { getElementFromSelector, getSelectorFromElement, getTransitionDurationFromElement, - isVisible + isDisabled, + isVisible, + typeCheckConfig } from './util/index' import { hide as scrollBarHide, reset as scrollBarReset } from './util/scrollbar' import Data from './dom/data' import EventHandler from './dom/event-handler' import BaseComponent from './base-component' import SelectorEngine from './dom/selector-engine' +import Manipulator from './dom/manipulator' /** * ------------------------------------------------------------------------ @@ -29,10 +32,20 @@ const DATA_KEY = 'bs.offcanvas' const EVENT_KEY = `.${DATA_KEY}` const DATA_API_KEY = '.data-api' const ESCAPE_KEY = 'Escape' -const DATA_BODY_ACTIONS = 'data-bs-body' + +const Default = { + backdrop: true, + keyboard: true, + scroll: false +} + +const DefaultType = { + backdrop: 'boolean', + keyboard: 'boolean', + scroll: 'boolean' +} const CLASS_NAME_BACKDROP_BODY = 'offcanvas-backdrop' -const CLASS_NAME_DISABLED = 'disabled' const CLASS_NAME_SHOW = 'show' const CLASS_NAME_TOGGLING = 'offcanvas-toggling' const ACTIVE_SELECTOR = `.offcanvas.show, .${CLASS_NAME_TOGGLING}` @@ -55,14 +68,24 @@ const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="offcanvas"]' */ class Offcanvas extends BaseComponent { - constructor(element) { + constructor(element, config) { super(element) + this._config = this._getConfig(config) this._isShown = element.classList.contains(CLASS_NAME_SHOW) - this._bodyOptions = element.getAttribute(DATA_BODY_ACTIONS) || '' this._addEventListeners() } + // Getters + + static get Default() { + return Default + } + + static get DATA_KEY() { + return DATA_KEY + } + // Public toggle(relatedTarget) { @@ -83,11 +106,11 @@ class Offcanvas extends BaseComponent { this._isShown = true this._element.style.visibility = 'visible' - if (this._bodyOptionsHas('backdrop') || !this._bodyOptions.length) { + if (this._config.backdrop) { document.body.classList.add(CLASS_NAME_BACKDROP_BODY) } - if (!this._bodyOptionsHas('scroll')) { + if (!this._config.scroll) { scrollBarHide() } @@ -129,11 +152,11 @@ class Offcanvas extends BaseComponent { this._element.removeAttribute('role') this._element.style.visibility = 'hidden' - if (this._bodyOptionsHas('backdrop') || !this._bodyOptions.length) { + if (this._config.backdrop) { document.body.classList.remove(CLASS_NAME_BACKDROP_BODY) } - if (!this._bodyOptionsHas('scroll')) { + if (!this._config.scroll) { scrollBarReset() } @@ -144,6 +167,18 @@ class Offcanvas extends BaseComponent { setTimeout(completeCallback, getTransitionDurationFromElement(this._element)) } + // Private + + _getConfig(config) { + config = { + ...Default, + ...Manipulator.getDataAttributes(this._element), + ...(typeof config === 'object' ? config : {}) + } + typeCheckConfig(NAME, config, DefaultType) + return config + } + _enforceFocusOnElement(element) { EventHandler.off(document, EVENT_FOCUSIN) // guard against infinite focus loop EventHandler.on(document, EVENT_FOCUSIN, event => { @@ -156,15 +191,11 @@ class Offcanvas extends BaseComponent { element.focus() } - _bodyOptionsHas(option) { - return this._bodyOptions.split(',').includes(option) - } - _addEventListeners() { EventHandler.on(this._element, EVENT_CLICK_DISMISS, SELECTOR_DATA_DISMISS, () => this.hide()) EventHandler.on(document, 'keydown', event => { - if (event.key === ESCAPE_KEY) { + if (this._config.keyboard && event.key === ESCAPE_KEY) { this.hide() } }) @@ -181,15 +212,17 @@ class Offcanvas extends BaseComponent { static jQueryInterface(config) { return this.each(function () { - const data = Data.get(this, DATA_KEY) || new Offcanvas(this) + const data = Data.get(this, DATA_KEY) || new Offcanvas(this, typeof config === 'object' ? config : {}) - if (typeof config === 'string') { - if (typeof data[config] === 'undefined') { - throw new TypeError(`No method named "${config}"`) - } + if (typeof config !== 'string') { + return + } - data[config](this) + if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { + throw new TypeError(`No method named "${config}"`) } + + data[config](this) }) } } @@ -207,7 +240,7 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( event.preventDefault() } - if (this.disabled || this.classList.contains(CLASS_NAME_DISABLED)) { + if (isDisabled(this)) { return } @@ -225,6 +258,7 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( } const data = Data.get(target, DATA_KEY) || new Offcanvas(target) + data.toggle(this) }) diff --git a/js/src/util/index.js b/js/src/util/index.js index ae3cd2ac0..e268b0728 100644 --- a/js/src/util/index.js +++ b/js/src/util/index.js @@ -153,6 +153,22 @@ const isVisible = element => { return false } +const isDisabled = element => { + if (!element || element.nodeType !== Node.ELEMENT_NODE) { + return true + } + + if (element.classList.contains('disabled')) { + return true + } + + if (typeof element.disabled !== 'undefined') { + return element.disabled + } + + return element.getAttribute('disabled') !== 'false' +} + const findShadowRoot = element => { if (!document.documentElement.attachShadow) { return null @@ -226,6 +242,7 @@ export { emulateTransitionEnd, typeCheckConfig, isVisible, + isDisabled, findShadowRoot, noop, reflow, -- cgit v1.2.3 From c5083d5fc372b750ceea35d72cafa26562762b0c Mon Sep 17 00:00:00 2001 From: GeoSot Date: Wed, 17 Mar 2021 07:44:15 +0200 Subject: Use more safe check for 'isDisabled' helper (#33385) --- js/src/util/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'js/src') diff --git a/js/src/util/index.js b/js/src/util/index.js index e268b0728..e9950c9e3 100644 --- a/js/src/util/index.js +++ b/js/src/util/index.js @@ -166,7 +166,7 @@ const isDisabled = element => { return element.disabled } - return element.getAttribute('disabled') !== 'false' + return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false' } const findShadowRoot = element => { -- cgit v1.2.3 From b9f30903a5a916904c873bd078240b3df743e093 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Wed, 17 Mar 2021 07:58:43 +0200 Subject: Fix carousel RTL and refactor code, fix rtl swipe issues (#32913) * move common code to reusable functions * add/re-factor tests, directionToOrder func * add _orderToDirection tests Co-authored-by: XhmikosR --- js/src/carousel.js | 95 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 50 insertions(+), 45 deletions(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index a825aaef4..b14cbd1a2 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -10,8 +10,8 @@ import { emulateTransitionEnd, getElementFromSelector, getTransitionDurationFromElement, - isVisible, isRTL, + isVisible, reflow, triggerTransitionEnd, typeCheckConfig @@ -56,8 +56,8 @@ const DefaultType = { touch: 'boolean' } -const DIRECTION_NEXT = 'next' -const DIRECTION_PREV = 'prev' +const ORDER_NEXT = 'next' +const ORDER_PREV = 'prev' const DIRECTION_LEFT = 'left' const DIRECTION_RIGHT = 'right' @@ -137,7 +137,7 @@ class Carousel extends BaseComponent { next() { if (!this._isSliding) { - this._slide(DIRECTION_NEXT) + this._slide(ORDER_NEXT) } } @@ -151,7 +151,7 @@ class Carousel extends BaseComponent { prev() { if (!this._isSliding) { - this._slide(DIRECTION_PREV) + this._slide(ORDER_PREV) } } @@ -208,11 +208,11 @@ class Carousel extends BaseComponent { return } - const direction = index > activeIndex ? - DIRECTION_NEXT : - DIRECTION_PREV + const order = index > activeIndex ? + ORDER_NEXT : + ORDER_PREV - this._slide(direction, this._items[index]) + this._slide(order, this._items[index]) } dispose() { @@ -251,23 +251,11 @@ class Carousel extends BaseComponent { this.touchDeltaX = 0 - // swipe left - if (direction > 0) { - if (isRTL()) { - this.next() - } else { - this.prev() - } + if (!direction) { + return } - // swipe right - if (direction < 0) { - if (isRTL()) { - this.prev() - } else { - this.next() - } - } + this._slide(direction > 0 ? DIRECTION_RIGHT : DIRECTION_LEFT) } _addEventListeners() { @@ -350,18 +338,10 @@ class Carousel extends BaseComponent { if (event.key === ARROW_LEFT_KEY) { event.preventDefault() - if (isRTL()) { - this.next() - } else { - this.prev() - } + this._slide(DIRECTION_LEFT) } else if (event.key === ARROW_RIGHT_KEY) { event.preventDefault() - if (isRTL()) { - this.prev() - } else { - this.next() - } + this._slide(DIRECTION_RIGHT) } } @@ -373,19 +353,18 @@ class Carousel extends BaseComponent { return this._items.indexOf(element) } - _getItemByDirection(direction, activeElement) { - const isNextDirection = direction === DIRECTION_NEXT - const isPrevDirection = direction === DIRECTION_PREV + _getItemByOrder(order, activeElement) { + const isNext = order === ORDER_NEXT + const isPrev = order === ORDER_PREV const activeIndex = this._getItemIndex(activeElement) const lastItemIndex = this._items.length - 1 - const isGoingToWrap = (isPrevDirection && activeIndex === 0) || - (isNextDirection && activeIndex === lastItemIndex) + const isGoingToWrap = (isPrev && activeIndex === 0) || (isNext && activeIndex === lastItemIndex) if (isGoingToWrap && !this._config.wrap) { return activeElement } - const delta = direction === DIRECTION_PREV ? -1 : 1 + const delta = isPrev ? -1 : 1 const itemIndex = (activeIndex + delta) % this._items.length return itemIndex === -1 ? @@ -441,17 +420,19 @@ class Carousel extends BaseComponent { } } - _slide(direction, element) { + _slide(directionOrOrder, element) { + const order = this._directionToOrder(directionOrOrder) const activeElement = SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element) const activeElementIndex = this._getItemIndex(activeElement) - const nextElement = element || (activeElement && this._getItemByDirection(direction, activeElement)) + const nextElement = element || this._getItemByOrder(order, activeElement) const nextElementIndex = this._getItemIndex(nextElement) const isCycling = Boolean(this._interval) - const directionalClassName = direction === DIRECTION_NEXT ? CLASS_NAME_START : CLASS_NAME_END - const orderClassName = direction === DIRECTION_NEXT ? CLASS_NAME_NEXT : CLASS_NAME_PREV - const eventDirectionName = direction === DIRECTION_NEXT ? DIRECTION_LEFT : DIRECTION_RIGHT + const isNext = order === ORDER_NEXT + const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END + const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV + const eventDirectionName = this._orderToDirection(order) if (nextElement && nextElement.classList.contains(CLASS_NAME_ACTIVE)) { this._isSliding = false @@ -524,6 +505,30 @@ class Carousel extends BaseComponent { } } + _directionToOrder(direction) { + if (![DIRECTION_RIGHT, DIRECTION_LEFT].includes(direction)) { + return direction + } + + if (isRTL()) { + return direction === DIRECTION_RIGHT ? ORDER_PREV : ORDER_NEXT + } + + return direction === DIRECTION_RIGHT ? ORDER_NEXT : ORDER_PREV + } + + _orderToDirection(order) { + if (![ORDER_NEXT, ORDER_PREV].includes(order)) { + return order + } + + if (isRTL()) { + return order === ORDER_NEXT ? DIRECTION_LEFT : DIRECTION_RIGHT + } + + return order === ORDER_NEXT ? DIRECTION_RIGHT : DIRECTION_LEFT + } + // Static static carouselInterface(element, config) { -- cgit v1.2.3 From 3ce0a8d3ecc05ac43fa5f5da0c5fc1aaea7742d5 Mon Sep 17 00:00:00 2001 From: "Patrick H. Lauke" Date: Wed, 17 Mar 2021 08:52:40 +0000 Subject: Dynamic tab should not show when triggered on `disabled` element (#33257) * show() should bail if the trigger has `disabled` attribute * use 'isDisabled' helper Co-authored-by: GeoSot Co-authored-by: XhmikosR --- js/src/tab.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'js/src') diff --git a/js/src/tab.js b/js/src/tab.js index 95968f4f8..ec3d790b0 100644 --- a/js/src/tab.js +++ b/js/src/tab.js @@ -10,6 +10,7 @@ import { emulateTransitionEnd, getElementFromSelector, getTransitionDurationFromElement, + isDisabled, reflow } from './util/index' import Data from './dom/data' @@ -36,7 +37,6 @@ const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` const CLASS_NAME_DROPDOWN_MENU = 'dropdown-menu' const CLASS_NAME_ACTIVE = 'active' -const CLASS_NAME_DISABLED = 'disabled' const CLASS_NAME_FADE = 'fade' const CLASS_NAME_SHOW = 'show' @@ -67,7 +67,7 @@ class Tab extends BaseComponent { if ((this._element.parentNode && this._element.parentNode.nodeType === Node.ELEMENT_NODE && this._element.classList.contains(CLASS_NAME_ACTIVE)) || - this._element.classList.contains(CLASS_NAME_DISABLED)) { + isDisabled(this._element)) { return } -- cgit v1.2.3 From 1c02ef4f971afe5df75d4e1889435f3edd9f2bbd Mon Sep 17 00:00:00 2001 From: GeoSot Date: Tue, 23 Mar 2021 08:22:59 +0200 Subject: Allow offcanvas to be initialized in open state (#33382) * Update docs to use new .show behavior and clarify some copy for first example Co-authored-by: Mark Otto Co-authored-by: XhmikosR --- js/src/offcanvas.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'js/src') diff --git a/js/src/offcanvas.js b/js/src/offcanvas.js index 4b98565e2..1824b3e3b 100644 --- a/js/src/offcanvas.js +++ b/js/src/offcanvas.js @@ -31,6 +31,7 @@ const NAME = 'offcanvas' const DATA_KEY = 'bs.offcanvas' const EVENT_KEY = `.${DATA_KEY}` const DATA_API_KEY = '.data-api' +const EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}` const ESCAPE_KEY = 'Escape' const Default = { @@ -48,7 +49,8 @@ const DefaultType = { const CLASS_NAME_BACKDROP_BODY = 'offcanvas-backdrop' const CLASS_NAME_SHOW = 'show' const CLASS_NAME_TOGGLING = 'offcanvas-toggling' -const ACTIVE_SELECTOR = `.offcanvas.show, .${CLASS_NAME_TOGGLING}` +const OPEN_SELECTOR = '.offcanvas.show' +const ACTIVE_SELECTOR = `${OPEN_SELECTOR}, .${CLASS_NAME_TOGGLING}` const EVENT_SHOW = `show${EVENT_KEY}` const EVENT_SHOWN = `shown${EVENT_KEY}` @@ -72,7 +74,7 @@ class Offcanvas extends BaseComponent { super(element) this._config = this._getConfig(config) - this._isShown = element.classList.contains(CLASS_NAME_SHOW) + this._isShown = false this._addEventListeners() } @@ -262,6 +264,10 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( data.toggle(this) }) +EventHandler.on(window, EVENT_LOAD_DATA_API, () => { + SelectorEngine.find(OPEN_SELECTOR).forEach(el => (Data.get(el, DATA_KEY) || new Offcanvas(el)).show()) +}) + /** * ------------------------------------------------------------------------ * jQuery -- cgit v1.2.3 From 9667438c1e8544b829b08c68d4ce1f36305297c8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Mar 2021 15:27:46 +0200 Subject: Bump eslint-plugin-unicorn from 28.0.2 to 29.0.0 (#33435) * Bump eslint-plugin-unicorn from 28.0.2 to 29.0.0 Bumps [eslint-plugin-unicorn](https://github.com/sindresorhus/eslint-plugin-unicorn) from 28.0.2 to 29.0.0. - [Release notes](https://github.com/sindresorhus/eslint-plugin-unicorn/releases) - [Commits](https://github.com/sindresorhus/eslint-plugin-unicorn/compare/v28.0.2...v29.0.0) Signed-off-by: dependabot[bot] * Fix lint failure Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: XhmikosR --- js/src/carousel.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index b14cbd1a2..fe53d583a 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -284,11 +284,9 @@ class Carousel extends BaseComponent { const move = event => { // ensure swiping with one touch and not pinching - if (event.touches && event.touches.length > 1) { - this.touchDeltaX = 0 - } else { - this.touchDeltaX = event.touches[0].clientX - this.touchStartX - } + this.touchDeltaX = event.touches && event.touches.length > 1 ? + 0 : + event.touches[0].clientX - this.touchStartX } const end = event => { -- cgit v1.2.3 From 16bc47da3c5296ee7d39b112831d557732fdda95 Mon Sep 17 00:00:00 2001 From: Casey Holzer Date: Wed, 6 Jan 2021 03:07:43 +0200 Subject: Allow data-toggle="dropdown" and form click events to bubble * remove stopPropagation from button click event * test for delegated click events * ensure button children can open menu * test to ensure clicking button opens the menu * check current element and parents * allow dropdown form click events to bubble --- js/src/dropdown.js | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index eeec9076d..ae1aacb6b 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -59,7 +59,6 @@ const CLASS_NAME_DROPSTART = 'dropstart' const CLASS_NAME_NAVBAR = 'navbar' const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="dropdown"]' -const SELECTOR_FORM_CHILD = '.dropdown form' const SELECTOR_MENU = '.dropdown-menu' const SELECTOR_NAVBAR_NAV = '.navbar-nav' const SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)' @@ -253,7 +252,6 @@ class Dropdown extends BaseComponent { _addEventListeners() { EventHandler.on(this._element, EVENT_CLICK, event => { event.preventDefault() - event.stopPropagation() this.toggle() }) } @@ -377,8 +375,14 @@ class Dropdown extends BaseComponent { } static clearMenus(event) { - if (event && (event.button === RIGHT_MOUSE_BUTTON || (event.type === 'keyup' && event.key !== TAB_KEY))) { - return + if (event) { + if (event.button === RIGHT_MOUSE_BUTTON || (event.type === 'keyup' && event.key !== TAB_KEY)) { + return + } + + if (/input|select|textarea|form/i.test(event.target.tagName)) { + return + } } const toggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE) @@ -402,11 +406,16 @@ class Dropdown extends BaseComponent { continue } - if (event && ((event.type === 'click' && - /input|textarea/i.test(event.target.tagName)) || - (event.type === 'keyup' && event.key === TAB_KEY)) && - dropdownMenu.contains(event.target)) { - continue + if (event) { + // Don't close the menu if the clicked element or one of its parents is the dropdown button + if ([context._element].some(element => event.composedPath().includes(element))) { + continue + } + + // Tab navigation through the dropdown menu shouldn't close the menu + if (event.type === 'keyup' && event.key === TAB_KEY && dropdownMenu.contains(event.target)) { + continue + } } const hideEvent = EventHandler.trigger(toggles[i], EVENT_HIDE, relatedTarget) @@ -519,10 +528,8 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, Dropdown.clearMenus) EventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus) EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) { event.preventDefault() - event.stopPropagation() - Dropdown.dropdownInterface(this, 'toggle') + Dropdown.dropdownInterface(this) }) -EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_FORM_CHILD, e => e.stopPropagation()) /** * ------------------------------------------------------------------------ -- cgit v1.2.3 From 220139a89ffc3864bbb6e1b35471667318eadc1f Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Tue, 23 Mar 2021 18:26:54 +0200 Subject: Release v5.0.0-beta3 (#33439) --- js/src/alert.js | 2 +- js/src/base-component.js | 4 ++-- js/src/button.js | 2 +- js/src/carousel.js | 2 +- js/src/collapse.js | 2 +- js/src/dom/data.js | 2 +- js/src/dom/event-handler.js | 2 +- js/src/dom/manipulator.js | 2 +- js/src/dom/selector-engine.js | 2 +- js/src/dropdown.js | 2 +- js/src/modal.js | 2 +- js/src/offcanvas.js | 2 +- js/src/popover.js | 2 +- js/src/scrollspy.js | 2 +- js/src/tab.js | 2 +- js/src/toast.js | 2 +- js/src/tooltip.js | 2 +- js/src/util/index.js | 2 +- js/src/util/sanitizer.js | 2 +- js/src/util/scrollbar.js | 2 +- 20 files changed, 21 insertions(+), 21 deletions(-) (limited to 'js/src') diff --git a/js/src/alert.js b/js/src/alert.js index d10e6c8da..a25c44ec3 100644 --- a/js/src/alert.js +++ b/js/src/alert.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta2): alert.js + * Bootstrap (v5.0.0-beta3): alert.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/base-component.js b/js/src/base-component.js index 2a9e29c2a..14e13a0eb 100644 --- a/js/src/base-component.js +++ b/js/src/base-component.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta2): base-component.js + * Bootstrap (v5.0.0-beta3): base-component.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ @@ -13,7 +13,7 @@ import Data from './dom/data' * ------------------------------------------------------------------------ */ -const VERSION = '5.0.0-beta2' +const VERSION = '5.0.0-beta3' class BaseComponent { constructor(element) { diff --git a/js/src/button.js b/js/src/button.js index 7a9449f07..093679e90 100644 --- a/js/src/button.js +++ b/js/src/button.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta2): button.js + * Bootstrap (v5.0.0-beta3): button.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/carousel.js b/js/src/carousel.js index fe53d583a..76581ca5d 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta2): carousel.js + * Bootstrap (v5.0.0-beta3): carousel.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/collapse.js b/js/src/collapse.js index 036ffcf24..6cb14cdd2 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta2): collapse.js + * Bootstrap (v5.0.0-beta3): collapse.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/dom/data.js b/js/src/dom/data.js index 1d283d68b..41ad08ab3 100644 --- a/js/src/dom/data.js +++ b/js/src/dom/data.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta2): dom/data.js + * Bootstrap (v5.0.0-beta3): dom/data.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/dom/event-handler.js b/js/src/dom/event-handler.js index 5b11ae3d0..26f6a1e3f 100644 --- a/js/src/dom/event-handler.js +++ b/js/src/dom/event-handler.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta2): dom/event-handler.js + * Bootstrap (v5.0.0-beta3): dom/event-handler.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/dom/manipulator.js b/js/src/dom/manipulator.js index 509797bc0..73b409f7e 100644 --- a/js/src/dom/manipulator.js +++ b/js/src/dom/manipulator.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta2): dom/manipulator.js + * Bootstrap (v5.0.0-beta3): dom/manipulator.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/dom/selector-engine.js b/js/src/dom/selector-engine.js index b310098b5..116b02741 100644 --- a/js/src/dom/selector-engine.js +++ b/js/src/dom/selector-engine.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta2): dom/selector-engine.js + * Bootstrap (v5.0.0-beta3): dom/selector-engine.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/dropdown.js b/js/src/dropdown.js index ae1aacb6b..d00cff65c 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta2): dropdown.js + * Bootstrap (v5.0.0-beta3): dropdown.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/modal.js b/js/src/modal.js index 40fd226bf..2966f03fb 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta2): modal.js + * Bootstrap (v5.0.0-beta3): modal.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/offcanvas.js b/js/src/offcanvas.js index 1824b3e3b..02b0b58a9 100644 --- a/js/src/offcanvas.js +++ b/js/src/offcanvas.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta2): offcanvas.js + * Bootstrap (v5.0.0-beta3): offcanvas.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/popover.js b/js/src/popover.js index 553544754..fa8d2c961 100644 --- a/js/src/popover.js +++ b/js/src/popover.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta2): popover.js + * Bootstrap (v5.0.0-beta3): popover.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index c7472439b..3667b9a12 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta2): scrollspy.js + * Bootstrap (v5.0.0-beta3): scrollspy.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/tab.js b/js/src/tab.js index ec3d790b0..3c5ced502 100644 --- a/js/src/tab.js +++ b/js/src/tab.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta2): tab.js + * Bootstrap (v5.0.0-beta3): tab.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/toast.js b/js/src/toast.js index ea91163d8..01b994e28 100644 --- a/js/src/toast.js +++ b/js/src/toast.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta2): toast.js + * Bootstrap (v5.0.0-beta3): toast.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 979bd0773..4fea1c964 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta2): tooltip.js + * Bootstrap (v5.0.0-beta3): tooltip.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/util/index.js b/js/src/util/index.js index e9950c9e3..a7578b180 100644 --- a/js/src/util/index.js +++ b/js/src/util/index.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta2): util/index.js + * Bootstrap (v5.0.0-beta3): util/index.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/util/sanitizer.js b/js/src/util/sanitizer.js index 57653a891..232a55e6b 100644 --- a/js/src/util/sanitizer.js +++ b/js/src/util/sanitizer.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta2): util/sanitizer.js + * Bootstrap (v5.0.0-beta3): util/sanitizer.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/util/scrollbar.js b/js/src/util/scrollbar.js index d2f3919e6..3e619ef51 100644 --- a/js/src/util/scrollbar.js +++ b/js/src/util/scrollbar.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta2): util/scrollBar.js + * Bootstrap (v5.0.0-beta3): util/scrollBar.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -- cgit v1.2.3 From 8c3e6ebc6e3e4fe6e0c31470195704872ec125f7 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Tue, 30 Mar 2021 07:42:23 +0300 Subject: Use our `isDisabled` util on dropdown (#33456) --- js/src/dropdown.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index d00cff65c..97bf6e109 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -10,6 +10,7 @@ import * as Popper from '@popperjs/core' import { defineJQueryPlugin, getElementFromSelector, + isDisabled, isElement, isVisible, isRTL, @@ -51,7 +52,6 @@ const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` const EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY}${DATA_API_KEY}` const EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}` -const CLASS_NAME_DISABLED = 'disabled' const CLASS_NAME_SHOW = 'show' const CLASS_NAME_DROPUP = 'dropup' const CLASS_NAME_DROPEND = 'dropend' @@ -121,7 +121,7 @@ class Dropdown extends BaseComponent { // Public toggle() { - if (this._element.disabled || this._element.classList.contains(CLASS_NAME_DISABLED)) { + if (isDisabled(this._element)) { return } @@ -137,7 +137,7 @@ class Dropdown extends BaseComponent { } show() { - if (this._element.disabled || this._element.classList.contains(CLASS_NAME_DISABLED) || this._menu.classList.contains(CLASS_NAME_SHOW)) { + if (isDisabled(this._element) || this._menu.classList.contains(CLASS_NAME_SHOW)) { return } @@ -204,7 +204,7 @@ class Dropdown extends BaseComponent { } hide() { - if (this._element.disabled || this._element.classList.contains(CLASS_NAME_DISABLED) || !this._menu.classList.contains(CLASS_NAME_SHOW)) { + if (isDisabled(this._element) || !this._menu.classList.contains(CLASS_NAME_SHOW)) { return } @@ -466,7 +466,7 @@ class Dropdown extends BaseComponent { event.preventDefault() event.stopPropagation() - if (this.disabled || this.classList.contains(CLASS_NAME_DISABLED)) { + if (isDisabled(this)) { return } -- cgit v1.2.3 From 5cc53a0ef0718b43e376462f156d39bf5542fbf9 Mon Sep 17 00:00:00 2001 From: Rohit Sharma Date: Tue, 30 Mar 2021 11:27:05 +0530 Subject: Use template literals instead of concatenation (#33497) --- js/src/modal.js | 2 +- js/src/util/index.js | 6 ++---- js/src/util/scrollbar.js | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) (limited to 'js/src') diff --git a/js/src/modal.js b/js/src/modal.js index 2966f03fb..4ce910dc6 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -474,7 +474,7 @@ class Modal extends BaseComponent { const actualValue = element.style[styleProp] const calculatedValue = window.getComputedStyle(element)[styleProp] Manipulator.setDataAttribute(element, styleProp, actualValue) - element.style[styleProp] = callback(Number.parseFloat(calculatedValue)) + 'px' + element.style[styleProp] = `${callback(Number.parseFloat(calculatedValue))}px` }) } diff --git a/js/src/util/index.js b/js/src/util/index.js index a7578b180..cc35d8a37 100644 --- a/js/src/util/index.js +++ b/js/src/util/index.js @@ -48,7 +48,7 @@ const getSelector = element => { // Just in case some CMS puts out a full URL with the anchor appended if (hrefAttr.includes('#') && !hrefAttr.startsWith('#')) { - hrefAttr = '#' + hrefAttr.split('#')[1] + hrefAttr = `#${hrefAttr.split('#')[1]}` } selector = hrefAttr && hrefAttr !== '#' ? hrefAttr.trim() : null @@ -128,9 +128,7 @@ const typeCheckConfig = (componentName, config, configTypes) => { if (!new RegExp(expectedTypes).test(valueType)) { throw new TypeError( - `${componentName.toUpperCase()}: ` + - `Option "${property}" provided type "${valueType}" ` + - `but expected type "${expectedTypes}".` + `${componentName.toUpperCase()}: Option "${property}" provided type "${valueType}" but expected type "${expectedTypes}".` ) } }) diff --git a/js/src/util/scrollbar.js b/js/src/util/scrollbar.js index 3e619ef51..e63a66bf2 100644 --- a/js/src/util/scrollbar.js +++ b/js/src/util/scrollbar.js @@ -35,7 +35,7 @@ const _setElementAttributes = (selector, styleProp, callback) => { const actualValue = element.style[styleProp] const calculatedValue = window.getComputedStyle(element)[styleProp] Manipulator.setDataAttribute(element, styleProp, actualValue) - element.style[styleProp] = callback(Number.parseFloat(calculatedValue)) + 'px' + element.style[styleProp] = `${callback(Number.parseFloat(calculatedValue))}px` }) } -- cgit v1.2.3 From f36f8344533d3179b8d82af96e005b3106d9ab46 Mon Sep 17 00:00:00 2001 From: alpadev <2838324+alpadev@users.noreply.github.com> Date: Thu, 1 Apr 2021 20:44:04 +0200 Subject: Fix dropdown escape propagation (#33479) --- js/src/dropdown.js | 63 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 28 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 97bf6e109..605cbc64d 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -443,6 +443,31 @@ class Dropdown extends BaseComponent { } } + static selectMenuItem(parent, event) { + const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, parent).filter(isVisible) + + if (!items.length) { + return + } + + let index = items.indexOf(event.target) + + // Up + if (event.key === ARROW_UP_KEY && index > 0) { + index-- + } + + // Down + if (event.key === ARROW_DOWN_KEY && index < items.length - 1) { + index++ + } + + // index is -1 if the first keydown is an ArrowUp + index = index === -1 ? 0 : index + + items[index].focus() + } + static getParentFromElement(element) { return getElementFromSelector(element) || element.parentNode } @@ -463,6 +488,12 @@ class Dropdown extends BaseComponent { return } + const isActive = this.classList.contains(CLASS_NAME_SHOW) + + if (!isActive && event.key === ESCAPE_KEY) { + return + } + event.preventDefault() event.stopPropagation() @@ -470,19 +501,16 @@ class Dropdown extends BaseComponent { return } - const parent = Dropdown.getParentFromElement(this) - const isActive = this.classList.contains(CLASS_NAME_SHOW) + const getToggleButton = () => this.matches(SELECTOR_DATA_TOGGLE) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] if (event.key === ESCAPE_KEY) { - const button = this.matches(SELECTOR_DATA_TOGGLE) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] - button.focus() + getToggleButton().focus() Dropdown.clearMenus() return } if (!isActive && (event.key === ARROW_UP_KEY || event.key === ARROW_DOWN_KEY)) { - const button = this.matches(SELECTOR_DATA_TOGGLE) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] - button.click() + getToggleButton().click() return } @@ -491,28 +519,7 @@ class Dropdown extends BaseComponent { return } - const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, parent).filter(isVisible) - - if (!items.length) { - return - } - - let index = items.indexOf(event.target) - - // Up - if (event.key === ARROW_UP_KEY && index > 0) { - index-- - } - - // Down - if (event.key === ARROW_DOWN_KEY && index < items.length - 1) { - index++ - } - - // index is -1 if the first keydown is an ArrowUp - index = index === -1 ? 0 : index - - items[index].focus() + Dropdown.selectMenuItem(Dropdown.getParentFromElement(this), event) } } -- cgit v1.2.3 From 20cfbdff79e19e77c24596cdca59c6694283e242 Mon Sep 17 00:00:00 2001 From: Rohit Sharma Date: Fri, 2 Apr 2021 00:21:55 +0530 Subject: Add missing things in `hide` method of dropdown (#33451) * Update `aria-expanded` attribute in `hide` method * Remove empty mouseover listeners added for iOS --- js/src/dropdown.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 605cbc64d..6b541ed15 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -218,12 +218,20 @@ class Dropdown extends BaseComponent { return } + // If this is a touch-enabled device we remove the extra + // empty mouseover listeners we added for iOS support + if ('ontouchstart' in document.documentElement) { + [].concat(...document.body.children) + .forEach(elem => EventHandler.off(elem, 'mouseover', null, noop())) + } + if (this._popper) { this._popper.destroy() } this._menu.classList.toggle(CLASS_NAME_SHOW) this._element.classList.toggle(CLASS_NAME_SHOW) + this._element.setAttribute('aria-expanded', 'false') Manipulator.removeDataAttribute(this._menu, 'popper') EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget) } @@ -430,14 +438,13 @@ class Dropdown extends BaseComponent { .forEach(elem => EventHandler.off(elem, 'mouseover', null, noop())) } - toggles[i].setAttribute('aria-expanded', 'false') - if (context._popper) { context._popper.destroy() } dropdownMenu.classList.remove(CLASS_NAME_SHOW) toggles[i].classList.remove(CLASS_NAME_SHOW) + toggles[i].setAttribute('aria-expanded', 'false') Manipulator.removeDataAttribute(dropdownMenu, 'popper') EventHandler.trigger(toggles[i], EVENT_HIDDEN, relatedTarget) } -- cgit v1.2.3 From 0b34ff2faedb0f5f13d91d279dbe3f8b3ae1fda7 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Tue, 6 Apr 2021 18:28:10 +0300 Subject: Simplify ScrollSpy config (#33250) --- js/src/scrollspy.js | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) (limited to 'js/src') diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index 3667b9a12..4e830b530 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -12,7 +12,6 @@ import { isElement, typeCheckConfig } from './util/index' -import Data from './dom/data' import EventHandler from './dom/event-handler' import Manipulator from './dom/manipulator' import SelectorEngine from './dom/selector-engine' @@ -155,6 +154,7 @@ class ScrollSpy extends BaseComponent { _getConfig(config) { config = { ...Default, + ...Manipulator.getDataAttributes(this._element), ...(typeof config === 'object' && config ? config : {}) } @@ -278,20 +278,17 @@ class ScrollSpy extends BaseComponent { static jQueryInterface(config) { return this.each(function () { - let data = Data.get(this, DATA_KEY) - const _config = typeof config === 'object' && config + const data = ScrollSpy.getInstance(this) || new ScrollSpy(this, typeof config === 'object' ? config : {}) - if (!data) { - data = new ScrollSpy(this, _config) + if (typeof config !== 'string') { + return } - if (typeof config === 'string') { - if (typeof data[config] === 'undefined') { - throw new TypeError(`No method named "${config}"`) - } - - data[config]() + if (typeof data[config] === 'undefined') { + throw new TypeError(`No method named "${config}"`) } + + data[config]() }) } } @@ -304,7 +301,7 @@ class ScrollSpy extends BaseComponent { EventHandler.on(window, EVENT_LOAD_DATA_API, () => { SelectorEngine.find(SELECTOR_DATA_SPY) - .forEach(spy => new ScrollSpy(spy, Manipulator.getDataAttributes(spy))) + .forEach(spy => new ScrollSpy(spy)) }) /** -- cgit v1.2.3 From 752b001b0a65a595d1844b01226f1402562a8a7f Mon Sep 17 00:00:00 2001 From: GeoSot Date: Wed, 3 Mar 2021 02:17:48 +0200 Subject: Simplify Modal config --- js/src/modal.js | 37 +++++++++++-------------------------- 1 file changed, 11 insertions(+), 26 deletions(-) (limited to 'js/src') diff --git a/js/src/modal.js b/js/src/modal.js index 4ce910dc6..aa6a8ffb2 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -10,12 +10,11 @@ import { emulateTransitionEnd, getElementFromSelector, getTransitionDurationFromElement, - isVisible, isRTL, + isVisible, reflow, typeCheckConfig } from './util/index' -import Data from './dom/data' import EventHandler from './dom/event-handler' import Manipulator from './dom/manipulator' import SelectorEngine from './dom/selector-engine' @@ -222,6 +221,7 @@ class Modal extends BaseComponent { _getConfig(config) { config = { ...Default, + ...Manipulator.getDataAttributes(this._element), ...config } typeCheckConfig(NAME, config, DefaultType) @@ -509,24 +509,17 @@ class Modal extends BaseComponent { static jQueryInterface(config, relatedTarget) { return this.each(function () { - let data = Data.get(this, DATA_KEY) - const _config = { - ...Default, - ...Manipulator.getDataAttributes(this), - ...(typeof config === 'object' && config ? config : {}) - } + const data = Modal.getInstance(this) || new Modal(this, typeof config === 'object' ? config : {}) - if (!data) { - data = new Modal(this, _config) + if (typeof config !== 'string') { + return } - if (typeof config === 'string') { - if (typeof data[config] === 'undefined') { - throw new TypeError(`No method named "${config}"`) - } - - data[config](relatedTarget) + if (typeof data[config] === 'undefined') { + throw new TypeError(`No method named "${config}"`) } + + data[config](relatedTarget) }) } } @@ -540,7 +533,7 @@ class Modal extends BaseComponent { EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) { const target = getElementFromSelector(this) - if (this.tagName === 'A' || this.tagName === 'AREA') { + if (['A', 'AREA'].includes(this.tagName)) { event.preventDefault() } @@ -557,15 +550,7 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( }) }) - let data = Data.get(target, DATA_KEY) - if (!data) { - const config = { - ...Manipulator.getDataAttributes(target), - ...Manipulator.getDataAttributes(this) - } - - data = new Modal(target, config) - } + const data = Modal.getInstance(target) || new Modal(target, Manipulator.getDataAttributes(this)) data.toggle(this) }) -- cgit v1.2.3 From 317c2cd08e91d118132c36648ba0bbed7812357a Mon Sep 17 00:00:00 2001 From: GeoSot Date: Fri, 5 Mar 2021 22:06:18 +0200 Subject: Omit getting data-attributes from toggle element --- js/src/modal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'js/src') diff --git a/js/src/modal.js b/js/src/modal.js index aa6a8ffb2..b2a2e80eb 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -550,7 +550,7 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( }) }) - const data = Modal.getInstance(target) || new Modal(target, Manipulator.getDataAttributes(this)) + const data = Modal.getInstance(target) || new Modal(target) data.toggle(this) }) -- cgit v1.2.3 From 0795a778f2b9e90a92ac5a240811cc2427dc268d Mon Sep 17 00:00:00 2001 From: GeoSot Date: Wed, 7 Apr 2021 08:29:31 +0300 Subject: Fix wrong carousel transformation, direction to order (#33499) --- js/src/carousel.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index 76581ca5d..e336abb1e 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -336,10 +336,10 @@ class Carousel extends BaseComponent { if (event.key === ARROW_LEFT_KEY) { event.preventDefault() - this._slide(DIRECTION_LEFT) + this._slide(DIRECTION_RIGHT) } else if (event.key === ARROW_RIGHT_KEY) { event.preventDefault() - this._slide(DIRECTION_RIGHT) + this._slide(DIRECTION_LEFT) } } @@ -509,10 +509,10 @@ class Carousel extends BaseComponent { } if (isRTL()) { - return direction === DIRECTION_RIGHT ? ORDER_PREV : ORDER_NEXT + return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT } - return direction === DIRECTION_RIGHT ? ORDER_NEXT : ORDER_PREV + return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV } _orderToDirection(order) { @@ -521,10 +521,10 @@ class Carousel extends BaseComponent { } if (isRTL()) { - return order === ORDER_NEXT ? DIRECTION_LEFT : DIRECTION_RIGHT + return order === ORDER_PREV ? DIRECTION_LEFT : DIRECTION_RIGHT } - return order === ORDER_NEXT ? DIRECTION_RIGHT : DIRECTION_LEFT + return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT } // Static -- cgit v1.2.3 From 7b7f4a5ced176ae3d7d9d16583795245cb9c7df3 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Sun, 11 Apr 2021 09:37:59 +0300 Subject: Decouple Modal's scrollbar functionality (#33245) --- js/src/modal.js | 82 +++++++----------------------------------------- js/src/util/scrollbar.js | 5 +-- 2 files changed, 14 insertions(+), 73 deletions(-) (limited to 'js/src') diff --git a/js/src/modal.js b/js/src/modal.js index b2a2e80eb..dea90ec0a 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -18,6 +18,7 @@ import { import EventHandler from './dom/event-handler' import Manipulator from './dom/manipulator' import SelectorEngine from './dom/selector-engine' +import { getWidth as getScrollBarWidth, hide as scrollBarHide, reset as scrollBarReset } from './util/scrollbar' import BaseComponent from './base-component' /** @@ -57,7 +58,6 @@ const EVENT_MOUSEUP_DISMISS = `mouseup.dismiss${EVENT_KEY}` const EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY}` const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` -const CLASS_NAME_SCROLLBAR_MEASURER = 'modal-scrollbar-measure' const CLASS_NAME_BACKDROP = 'modal-backdrop' const CLASS_NAME_OPEN = 'modal-open' const CLASS_NAME_FADE = 'fade' @@ -68,8 +68,6 @@ const SELECTOR_DIALOG = '.modal-dialog' const SELECTOR_MODAL_BODY = '.modal-body' const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="modal"]' const SELECTOR_DATA_DISMISS = '[data-bs-dismiss="modal"]' -const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top' -const SELECTOR_STICKY_CONTENT = '.sticky-top' /** * ------------------------------------------------------------------------ @@ -85,10 +83,8 @@ class Modal extends BaseComponent { this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element) this._backdrop = null this._isShown = false - this._isBodyOverflowing = false this._ignoreBackdropClick = false this._isTransitioning = false - this._scrollbarWidth = 0 } // Getters @@ -126,8 +122,9 @@ class Modal extends BaseComponent { this._isShown = true - this._checkScrollbar() - this._setScrollbar() + scrollBarHide() + + document.body.classList.add(CLASS_NAME_OPEN) this._adjustDialog() @@ -206,10 +203,8 @@ class Modal extends BaseComponent { this._dialog = null this._backdrop = null this._isShown = null - this._isBodyOverflowing = null this._ignoreBackdropClick = null this._isTransitioning = null - this._scrollbarWidth = null } handleUpdate() { @@ -321,7 +316,7 @@ class Modal extends BaseComponent { this._showBackdrop(() => { document.body.classList.remove(CLASS_NAME_OPEN) this._resetAdjustments() - this._resetScrollbar() + scrollBarReset() EventHandler.trigger(this._element, EVENT_HIDDEN) }) } @@ -433,13 +428,15 @@ class Modal extends BaseComponent { _adjustDialog() { const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight + const scrollbarWidth = getScrollBarWidth() + const isBodyOverflowing = scrollbarWidth > 0 - if ((!this._isBodyOverflowing && isModalOverflowing && !isRTL()) || (this._isBodyOverflowing && !isModalOverflowing && isRTL())) { - this._element.style.paddingLeft = `${this._scrollbarWidth}px` + if ((!isBodyOverflowing && isModalOverflowing && !isRTL()) || (isBodyOverflowing && !isModalOverflowing && isRTL())) { + this._element.style.paddingLeft = `${scrollbarWidth}px` } - if ((this._isBodyOverflowing && !isModalOverflowing && !isRTL()) || (!this._isBodyOverflowing && isModalOverflowing && isRTL())) { - this._element.style.paddingRight = `${this._scrollbarWidth}px` + if ((isBodyOverflowing && !isModalOverflowing && !isRTL()) || (!isBodyOverflowing && isModalOverflowing && isRTL())) { + this._element.style.paddingRight = `${scrollbarWidth}px` } } @@ -448,63 +445,6 @@ class Modal extends BaseComponent { this._element.style.paddingRight = '' } - _checkScrollbar() { - const rect = document.body.getBoundingClientRect() - this._isBodyOverflowing = Math.round(rect.left + rect.right) < window.innerWidth - this._scrollbarWidth = this._getScrollbarWidth() - } - - _setScrollbar() { - if (this._isBodyOverflowing) { - this._setElementAttributes(SELECTOR_FIXED_CONTENT, 'paddingRight', calculatedValue => calculatedValue + this._scrollbarWidth) - this._setElementAttributes(SELECTOR_STICKY_CONTENT, 'marginRight', calculatedValue => calculatedValue - this._scrollbarWidth) - this._setElementAttributes('body', 'paddingRight', calculatedValue => calculatedValue + this._scrollbarWidth) - } - - document.body.classList.add(CLASS_NAME_OPEN) - } - - _setElementAttributes(selector, styleProp, callback) { - SelectorEngine.find(selector) - .forEach(element => { - if (element !== document.body && window.innerWidth > element.clientWidth + this._scrollbarWidth) { - return - } - - const actualValue = element.style[styleProp] - const calculatedValue = window.getComputedStyle(element)[styleProp] - Manipulator.setDataAttribute(element, styleProp, actualValue) - element.style[styleProp] = `${callback(Number.parseFloat(calculatedValue))}px` - }) - } - - _resetScrollbar() { - this._resetElementAttributes(SELECTOR_FIXED_CONTENT, 'paddingRight') - this._resetElementAttributes(SELECTOR_STICKY_CONTENT, 'marginRight') - this._resetElementAttributes('body', 'paddingRight') - } - - _resetElementAttributes(selector, styleProp) { - SelectorEngine.find(selector).forEach(element => { - const value = Manipulator.getDataAttribute(element, styleProp) - if (typeof value === 'undefined' && element === document.body) { - element.style[styleProp] = '' - } else { - Manipulator.removeDataAttribute(element, styleProp) - element.style[styleProp] = value - } - }) - } - - _getScrollbarWidth() { // thx d.walsh - const scrollDiv = document.createElement('div') - scrollDiv.className = CLASS_NAME_SCROLLBAR_MEASURER - document.body.appendChild(scrollDiv) - const scrollbarWidth = scrollDiv.getBoundingClientRect().width - scrollDiv.clientWidth - document.body.removeChild(scrollDiv) - return scrollbarWidth - } - // Static static jQueryInterface(config, relatedTarget) { diff --git a/js/src/util/scrollbar.js b/js/src/util/scrollbar.js index e63a66bf2..31b614375 100644 --- a/js/src/util/scrollbar.js +++ b/js/src/util/scrollbar.js @@ -8,7 +8,7 @@ import SelectorEngine from '../dom/selector-engine' import Manipulator from '../dom/manipulator' -const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed' +const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top' const SELECTOR_STICKY_CONTENT = '.sticky-top' const getWidth = () => { @@ -19,6 +19,7 @@ const getWidth = () => { const hide = (width = getWidth()) => { document.body.style.overflow = 'hidden' + // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements, to keep shown fullwidth _setElementAttributes(SELECTOR_FIXED_CONTENT, 'paddingRight', calculatedValue => calculatedValue + width) _setElementAttributes(SELECTOR_STICKY_CONTENT, 'marginRight', calculatedValue => calculatedValue - width) _setElementAttributes('body', 'paddingRight', calculatedValue => calculatedValue + width) @@ -49,7 +50,7 @@ const reset = () => { const _resetElementAttributes = (selector, styleProp) => { SelectorEngine.find(selector).forEach(element => { const value = Manipulator.getDataAttribute(element, styleProp) - if (typeof value === 'undefined' && element === document.body) { + if (typeof value === 'undefined') { element.style.removeProperty(styleProp) } else { Manipulator.removeDataAttribute(element, styleProp) -- cgit v1.2.3 From b2bc159d722a640b684f12a1171d70c8d5284b4e Mon Sep 17 00:00:00 2001 From: Rohit Sharma Date: Sat, 27 Mar 2021 21:38:45 +0530 Subject: Use cached `noop` function everywhere --- js/src/dropdown.js | 6 +++--- js/src/tooltip.js | 2 +- js/src/util/index.js | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 6b541ed15..3e1ef5fd4 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -192,7 +192,7 @@ class Dropdown extends BaseComponent { if ('ontouchstart' in document.documentElement && !parent.closest(SELECTOR_NAVBAR_NAV)) { [].concat(...document.body.children) - .forEach(elem => EventHandler.on(elem, 'mouseover', null, noop())) + .forEach(elem => EventHandler.on(elem, 'mouseover', null, noop)) } this._element.focus() @@ -222,7 +222,7 @@ class Dropdown extends BaseComponent { // empty mouseover listeners we added for iOS support if ('ontouchstart' in document.documentElement) { [].concat(...document.body.children) - .forEach(elem => EventHandler.off(elem, 'mouseover', null, noop())) + .forEach(elem => EventHandler.off(elem, 'mouseover', null, noop)) } if (this._popper) { @@ -435,7 +435,7 @@ class Dropdown extends BaseComponent { // empty mouseover listeners we added for iOS support if ('ontouchstart' in document.documentElement) { [].concat(...document.body.children) - .forEach(elem => EventHandler.off(elem, 'mouseover', null, noop())) + .forEach(elem => EventHandler.off(elem, 'mouseover', null, noop)) } if (context._popper) { diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 4fea1c964..a66e1ad41 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -301,7 +301,7 @@ class Tooltip extends BaseComponent { // 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()) + EventHandler.on(element, 'mouseover', noop) }) } diff --git a/js/src/util/index.js b/js/src/util/index.js index cc35d8a37..f19d76e03 100644 --- a/js/src/util/index.js +++ b/js/src/util/index.js @@ -190,7 +190,7 @@ const findShadowRoot = element => { return findShadowRoot(element.parentNode) } -const noop = () => function () {} +const noop = () => {} const reflow = element => element.offsetHeight -- cgit v1.2.3 From 5f946d3a86caa8c62a3a2bb279b4640f28c6930a Mon Sep 17 00:00:00 2001 From: Rohit Sharma Date: Sat, 27 Mar 2021 22:51:08 +0530 Subject: =?UTF-8?q?Dropdown=20=E2=80=94=20Don't=20use=20event=20delegation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- js/src/dropdown.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 3e1ef5fd4..7d5421a56 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -192,7 +192,7 @@ class Dropdown extends BaseComponent { if ('ontouchstart' in document.documentElement && !parent.closest(SELECTOR_NAVBAR_NAV)) { [].concat(...document.body.children) - .forEach(elem => EventHandler.on(elem, 'mouseover', null, noop)) + .forEach(elem => EventHandler.on(elem, 'mouseover', noop)) } this._element.focus() @@ -222,7 +222,7 @@ class Dropdown extends BaseComponent { // empty mouseover listeners we added for iOS support if ('ontouchstart' in document.documentElement) { [].concat(...document.body.children) - .forEach(elem => EventHandler.off(elem, 'mouseover', null, noop)) + .forEach(elem => EventHandler.off(elem, 'mouseover', noop)) } if (this._popper) { @@ -435,7 +435,7 @@ class Dropdown extends BaseComponent { // empty mouseover listeners we added for iOS support if ('ontouchstart' in document.documentElement) { [].concat(...document.body.children) - .forEach(elem => EventHandler.off(elem, 'mouseover', null, noop)) + .forEach(elem => EventHandler.off(elem, 'mouseover', noop)) } if (context._popper) { -- cgit v1.2.3 From 566451230f5c87c3d7515af02995895df610b8ac Mon Sep 17 00:00:00 2001 From: GeoSot Date: Sun, 11 Apr 2021 09:54:48 +0300 Subject: Remove element event listeners through base component (#33429) After some research, I found out that EventHandler saves all the custom events per element using namespace, and is capable of removing handlers using only the element and its namespace (`DATA_KEY`). So, probably is better to utilize the base-component to do the same job. --- js/src/base-component.js | 2 ++ js/src/carousel.js | 2 -- js/src/dropdown.js | 1 - js/src/modal.js | 2 +- js/src/toast.js | 2 -- js/src/tooltip.js | 1 - 6 files changed, 3 insertions(+), 7 deletions(-) (limited to 'js/src') diff --git a/js/src/base-component.js b/js/src/base-component.js index 14e13a0eb..77d54faad 100644 --- a/js/src/base-component.js +++ b/js/src/base-component.js @@ -6,6 +6,7 @@ */ import Data from './dom/data' +import EventHandler from './dom/event-handler' /** * ------------------------------------------------------------------------ @@ -29,6 +30,7 @@ class BaseComponent { dispose() { Data.remove(this._element, this.constructor.DATA_KEY) + EventHandler.off(this._element, `.${this.constructor.DATA_KEY}`) this._element = null } diff --git a/js/src/carousel.js b/js/src/carousel.js index e336abb1e..ebb0b7b20 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -216,8 +216,6 @@ class Carousel extends BaseComponent { } dispose() { - EventHandler.off(this._element, EVENT_KEY) - this._items = null this._config = null this._interval = null diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 7d5421a56..b126d3196 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -237,7 +237,6 @@ class Dropdown extends BaseComponent { } dispose() { - EventHandler.off(this._element, EVENT_KEY) this._menu = null if (this._popper) { diff --git a/js/src/modal.js b/js/src/modal.js index dea90ec0a..c6d67ac95 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -187,7 +187,7 @@ class Modal extends BaseComponent { } dispose() { - [window, this._element, this._dialog] + [window, this._dialog] .forEach(htmlElement => EventHandler.off(htmlElement, EVENT_KEY)) super.dispose() diff --git a/js/src/toast.js b/js/src/toast.js index 01b994e28..5d762b29d 100644 --- a/js/src/toast.js +++ b/js/src/toast.js @@ -156,8 +156,6 @@ class Toast extends BaseComponent { this._element.classList.remove(CLASS_NAME_SHOW) } - EventHandler.off(this._element, EVENT_CLICK_DISMISS) - super.dispose() this._config = null } diff --git a/js/src/tooltip.js b/js/src/tooltip.js index a66e1ad41..2bfa42fc7 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -215,7 +215,6 @@ class Tooltip extends BaseComponent { dispose() { clearTimeout(this._timeout) - 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) { -- cgit v1.2.3 From ad10f00d5e073f60b8d86d03812742b97037c336 Mon Sep 17 00:00:00 2001 From: alpadev <2838324+alpadev@users.noreply.github.com> Date: Sun, 11 Apr 2021 17:34:46 +0200 Subject: refactor: make static `selectMenuItem` method private (#33589) --- js/src/dropdown.js | 52 ++++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index b126d3196..ae440e472 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -356,6 +356,31 @@ class Dropdown extends BaseComponent { } } + _selectMenuItem(event) { + const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(isVisible) + + if (!items.length) { + return + } + + let index = items.indexOf(event.target) + + // Up + if (event.key === ARROW_UP_KEY && index > 0) { + index-- + } + + // Down + if (event.key === ARROW_DOWN_KEY && index < items.length - 1) { + index++ + } + + // index is -1 if the first keydown is an ArrowUp + index = index === -1 ? 0 : index + + items[index].focus() + } + // Static static dropdownInterface(element, config) { @@ -449,31 +474,6 @@ class Dropdown extends BaseComponent { } } - static selectMenuItem(parent, event) { - const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, parent).filter(isVisible) - - if (!items.length) { - return - } - - let index = items.indexOf(event.target) - - // Up - if (event.key === ARROW_UP_KEY && index > 0) { - index-- - } - - // Down - if (event.key === ARROW_DOWN_KEY && index < items.length - 1) { - index++ - } - - // index is -1 if the first keydown is an ArrowUp - index = index === -1 ? 0 : index - - items[index].focus() - } - static getParentFromElement(element) { return getElementFromSelector(element) || element.parentNode } @@ -525,7 +525,7 @@ class Dropdown extends BaseComponent { return } - Dropdown.selectMenuItem(Dropdown.getParentFromElement(this), event) + Dropdown.getInstance(getToggleButton())._selectMenuItem(event) } } -- cgit v1.2.3 From db32b2380c3040936b8e88f6d6dae5998750ddf6 Mon Sep 17 00:00:00 2001 From: alpadev <2838324+alpadev@users.noreply.github.com> Date: Tue, 13 Apr 2021 05:25:58 +0200 Subject: fix: make EventHandler better handle mouseenter/mouseleave events (#33310) * fix: make EventHandler better handle mouseenter/mouseleave events * refactor: simplify custom events regex and move it to a variable --- js/src/dom/event-handler.js | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) (limited to 'js/src') diff --git a/js/src/dom/event-handler.js b/js/src/dom/event-handler.js index 26f6a1e3f..8ccb887fc 100644 --- a/js/src/dom/event-handler.js +++ b/js/src/dom/event-handler.js @@ -22,6 +22,7 @@ const customEvents = { mouseenter: 'mouseover', mouseleave: 'mouseout' } +const customEventsRegex = /^(mouseenter|mouseleave)/i const nativeEvents = new Set([ 'click', 'dblclick', @@ -113,7 +114,7 @@ function bootstrapDelegationHandler(element, selector, fn) { if (handler.oneOff) { // eslint-disable-next-line unicorn/consistent-destructuring - EventHandler.off(element, event.type, fn) + EventHandler.off(element, event.type, selector, fn) } return fn.apply(target, [event]) @@ -144,14 +145,7 @@ function normalizeParams(originalTypeEvent, handler, delegationFn) { const delegation = typeof handler === 'string' const originalHandler = delegation ? delegationFn : handler - // allow to get the native events from namespaced events ('click.bs.button' --> 'click') - let typeEvent = originalTypeEvent.replace(stripNameRegex, '') - const custom = customEvents[typeEvent] - - if (custom) { - typeEvent = custom - } - + let typeEvent = getTypeEvent(originalTypeEvent) const isNative = nativeEvents.has(typeEvent) if (!isNative) { @@ -171,6 +165,24 @@ function addHandler(element, originalTypeEvent, handler, delegationFn, oneOff) { delegationFn = null } + // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position + // this prevents the handler from being dispatched the same way as mouseover or mouseout does + if (customEventsRegex.test(originalTypeEvent)) { + const wrapFn = fn => { + return function (event) { + if (!event.relatedTarget || (event.relatedTarget !== event.delegateTarget && event.relatedTarget.contains(event.delegateTarget))) { + return fn.call(this, event) + } + } + } + + if (delegationFn) { + delegationFn = wrapFn(delegationFn) + } else { + handler = wrapFn(handler) + } + } + const [delegation, originalHandler, typeEvent] = normalizeParams(originalTypeEvent, handler, delegationFn) const events = getEvent(element) const handlers = events[typeEvent] || (events[typeEvent] = {}) @@ -219,6 +231,12 @@ function removeNamespacedHandlers(element, events, typeEvent, namespace) { }) } +function getTypeEvent(event) { + // allow to get the native events from namespaced events ('click.bs.button' --> 'click') + event = event.replace(stripNameRegex, '') + return customEvents[event] || event +} + const EventHandler = { on(element, event, handler, delegationFn) { addHandler(element, event, handler, delegationFn, false) @@ -272,7 +290,7 @@ const EventHandler = { } const $ = getjQuery() - const typeEvent = event.replace(stripNameRegex, '') + const typeEvent = getTypeEvent(event) const inNamespace = event !== typeEvent const isNative = nativeEvents.has(typeEvent) -- cgit v1.2.3 From 80085a12f6936bef11aa72631392e3e9b2646f17 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Wed, 14 Apr 2021 23:28:50 +0300 Subject: Decouple BackDrop from modal (#32439) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Create backdrop.js util * revert breaking changes remove PromiseTimout usage revert class name * one more test | change bundlewatch.config * add config obj to backdrop helper | tests for rootElement | use transitionend helper * Minor tweaks — Renaming Co-authored-by: Rohit Sharma --- js/src/modal.js | 84 +++++++++------------------------ js/src/util/backdrop.js | 123 ++++++++++++++++++++++++++++++++++++++++++++++++ js/src/util/index.js | 9 +++- 3 files changed, 153 insertions(+), 63 deletions(-) create mode 100644 js/src/util/backdrop.js (limited to 'js/src') diff --git a/js/src/modal.js b/js/src/modal.js index c6d67ac95..fabb151cb 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -20,6 +20,7 @@ import Manipulator from './dom/manipulator' import SelectorEngine from './dom/selector-engine' import { getWidth as getScrollBarWidth, hide as scrollBarHide, reset as scrollBarReset } from './util/scrollbar' import BaseComponent from './base-component' +import Backdrop from './util/backdrop' /** * ------------------------------------------------------------------------ @@ -58,7 +59,6 @@ const EVENT_MOUSEUP_DISMISS = `mouseup.dismiss${EVENT_KEY}` const EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY}` const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` -const CLASS_NAME_BACKDROP = 'modal-backdrop' const CLASS_NAME_OPEN = 'modal-open' const CLASS_NAME_FADE = 'fade' const CLASS_NAME_SHOW = 'show' @@ -81,7 +81,7 @@ class Modal extends BaseComponent { this._config = this._getConfig(config) this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element) - this._backdrop = null + this._backdrop = this._initializeBackDrop() this._isShown = false this._ignoreBackdropClick = false this._isTransitioning = false @@ -201,6 +201,7 @@ class Modal extends BaseComponent { this._config = null this._dialog = null + this._backdrop.dispose() this._backdrop = null this._isShown = null this._ignoreBackdropClick = null @@ -213,6 +214,13 @@ class Modal extends BaseComponent { // Private + _initializeBackDrop() { + return new Backdrop({ + isVisible: Boolean(this._config.backdrop), // 'static' option will be translated to true, and booleans will keep their value + isAnimated: this._isAnimated() + }) + } + _getConfig(config) { config = { ...Default, @@ -313,7 +321,7 @@ class Modal extends BaseComponent { this._element.removeAttribute('aria-modal') this._element.removeAttribute('role') this._isTransitioning = false - this._showBackdrop(() => { + this._backdrop.hide(() => { document.body.classList.remove(CLASS_NAME_OPEN) this._resetAdjustments() scrollBarReset() @@ -321,73 +329,25 @@ class Modal extends BaseComponent { }) } - _removeBackdrop() { - this._backdrop.parentNode.removeChild(this._backdrop) - this._backdrop = null - } - _showBackdrop(callback) { - const isAnimated = this._isAnimated() - if (this._isShown && this._config.backdrop) { - this._backdrop = document.createElement('div') - this._backdrop.className = CLASS_NAME_BACKDROP - - if (isAnimated) { - this._backdrop.classList.add(CLASS_NAME_FADE) - } - - document.body.appendChild(this._backdrop) - - EventHandler.on(this._element, EVENT_CLICK_DISMISS, event => { - if (this._ignoreBackdropClick) { - this._ignoreBackdropClick = false - return - } - - if (event.target !== event.currentTarget) { - return - } - - if (this._config.backdrop === 'static') { - this._triggerBackdropTransition() - } else { - this.hide() - } - }) - - if (isAnimated) { - reflow(this._backdrop) + EventHandler.on(this._element, EVENT_CLICK_DISMISS, event => { + if (this._ignoreBackdropClick) { + this._ignoreBackdropClick = false + return } - this._backdrop.classList.add(CLASS_NAME_SHOW) - - if (!isAnimated) { - callback() + if (event.target !== event.currentTarget) { return } - const backdropTransitionDuration = getTransitionDurationFromElement(this._backdrop) - - EventHandler.one(this._backdrop, 'transitionend', callback) - emulateTransitionEnd(this._backdrop, backdropTransitionDuration) - } else if (!this._isShown && this._backdrop) { - this._backdrop.classList.remove(CLASS_NAME_SHOW) - - const callbackRemove = () => { - this._removeBackdrop() - callback() + if (this._config.backdrop === true) { + this.hide() + } else if (this._config.backdrop === 'static') { + this._triggerBackdropTransition() } + }) - if (isAnimated) { - const backdropTransitionDuration = getTransitionDurationFromElement(this._backdrop) - EventHandler.one(this._backdrop, 'transitionend', callbackRemove) - emulateTransitionEnd(this._backdrop, backdropTransitionDuration) - } else { - callbackRemove() - } - } else { - callback() - } + this._backdrop.show(callback) } _isAnimated() { diff --git a/js/src/util/backdrop.js b/js/src/util/backdrop.js new file mode 100644 index 000000000..ab14c23fe --- /dev/null +++ b/js/src/util/backdrop.js @@ -0,0 +1,123 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v5.0.0-beta3): util/backdrop.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + +import EventHandler from '../dom/event-handler' +import { emulateTransitionEnd, execute, getTransitionDurationFromElement, reflow, typeCheckConfig } from './index' + +const Default = { + isVisible: true, // if false, we use the backdrop helper without adding any element to the dom + isAnimated: false, + rootElement: document.body // give the choice to place backdrop under different elements +} + +const DefaultType = { + isVisible: 'boolean', + isAnimated: 'boolean', + rootElement: 'element' +} +const NAME = 'backdrop' +const CLASS_NAME_BACKDROP = 'modal-backdrop' +const CLASS_NAME_FADE = 'fade' +const CLASS_NAME_SHOW = 'show' + +class Backdrop { + constructor(config) { + this._config = this._getConfig(config) + this._isAppended = false + this._element = null + } + + show(callback) { + if (!this._config.isVisible) { + execute(callback) + return + } + + this._append() + + if (this._config.isAnimated) { + reflow(this._getElement()) + } + + this._getElement().classList.add(CLASS_NAME_SHOW) + + this._emulateAnimation(() => { + execute(callback) + }) + } + + hide(callback) { + if (!this._config.isVisible) { + execute(callback) + return + } + + this._getElement().classList.remove(CLASS_NAME_SHOW) + + this._emulateAnimation(() => { + this.dispose() + execute(callback) + }) + } + + // Private + + _getElement() { + if (!this._element) { + const backdrop = document.createElement('div') + backdrop.className = CLASS_NAME_BACKDROP + if (this._config.isAnimated) { + backdrop.classList.add(CLASS_NAME_FADE) + } + + this._element = backdrop + } + + return this._element + } + + _getConfig(config) { + config = { + ...Default, + ...(typeof config === 'object' ? config : {}) + } + typeCheckConfig(NAME, config, DefaultType) + return config + } + + _append() { + if (this._isAppended) { + return + } + + this._config.rootElement.appendChild(this._getElement()) + + this._isAppended = true + } + + dispose() { + if (!this._isAppended) { + return + } + + this._getElement().parentNode.removeChild(this._element) + this._isAppended = false + } + + _emulateAnimation(callback) { + if (!this._config.isAnimated) { + execute(callback) + return + } + + const backdropTransitionDuration = getTransitionDurationFromElement(this._getElement()) + EventHandler.one(this._getElement(), 'transitionend', () => execute(callback)) + emulateTransitionEnd(this._getElement(), backdropTransitionDuration) + } +} + +export default Backdrop diff --git a/js/src/util/index.js b/js/src/util/index.js index f19d76e03..c27c470e9 100644 --- a/js/src/util/index.js +++ b/js/src/util/index.js @@ -230,6 +230,12 @@ const defineJQueryPlugin = (name, plugin) => { }) } +const execute = callback => { + if (typeof callback === 'function') { + callback() + } +} + export { getUID, getSelectorFromElement, @@ -247,5 +253,6 @@ export { getjQuery, onDOMContentLoaded, isRTL, - defineJQueryPlugin + defineJQueryPlugin, + execute } -- cgit v1.2.3 From 69f5c0130b4f13e0dc22d86bf1bbe1b362891cd7 Mon Sep 17 00:00:00 2001 From: Carson Sievert Date: Thu, 15 Apr 2021 05:53:55 -0500 Subject: Fix v5 regressions in tab dropdown functionality (#33626) Scope selector to `dropdownElement` when adding active classes --- js/src/tab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'js/src') diff --git a/js/src/tab.js b/js/src/tab.js index 3c5ced502..ffca5f299 100644 --- a/js/src/tab.js +++ b/js/src/tab.js @@ -166,7 +166,7 @@ class Tab extends BaseComponent { const dropdownElement = element.closest(SELECTOR_DROPDOWN) if (dropdownElement) { - SelectorEngine.find(SELECTOR_DROPDOWN_TOGGLE) + SelectorEngine.find(SELECTOR_DROPDOWN_TOGGLE, dropdownElement) .forEach(dropdown => dropdown.classList.add(CLASS_NAME_ACTIVE)) } -- cgit v1.2.3 From 38a79ec64cf148cae16cd98f9f2c5bc174175cc9 Mon Sep 17 00:00:00 2001 From: Rohit Sharma Date: Wed, 24 Mar 2021 23:55:00 +0530 Subject: Refactor dropdown's hide functionality --- js/src/dropdown.js | 83 ++++++++++++++++++++++-------------------------------- 1 file changed, 33 insertions(+), 50 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index ae440e472..7561f9812 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -212,28 +212,7 @@ class Dropdown extends BaseComponent { relatedTarget: this._element } - const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE, relatedTarget) - - if (hideEvent.defaultPrevented) { - return - } - - // If this is a touch-enabled device we remove the extra - // empty mouseover listeners we added for iOS support - if ('ontouchstart' in document.documentElement) { - [].concat(...document.body.children) - .forEach(elem => EventHandler.off(elem, 'mouseover', noop)) - } - - if (this._popper) { - this._popper.destroy() - } - - this._menu.classList.toggle(CLASS_NAME_SHOW) - this._element.classList.toggle(CLASS_NAME_SHOW) - this._element.setAttribute('aria-expanded', 'false') - Manipulator.removeDataAttribute(this._menu, 'popper') - EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget) + this._completeHide(relatedTarget) } dispose() { @@ -263,6 +242,30 @@ class Dropdown extends BaseComponent { }) } + _completeHide(relatedTarget) { + const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE, relatedTarget) + if (hideEvent.defaultPrevented) { + return + } + + // If this is a touch-enabled device we remove the extra + // empty mouseover listeners we added for iOS support + if ('ontouchstart' in document.documentElement) { + [].concat(...document.body.children) + .forEach(elem => EventHandler.off(elem, 'mouseover', noop)) + } + + if (this._popper) { + this._popper.destroy() + } + + this._menu.classList.toggle(CLASS_NAME_SHOW) + this._element.classList.toggle(CLASS_NAME_SHOW) + this._element.setAttribute('aria-expanded', 'false') + Manipulator.removeDataAttribute(this._menu, 'popper') + EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget) + } + _getConfig(config) { config = { ...this.constructor.Default, @@ -421,14 +424,6 @@ class Dropdown extends BaseComponent { for (let i = 0, len = toggles.length; i < len; i++) { const context = Data.get(toggles[i], DATA_KEY) - const relatedTarget = { - relatedTarget: toggles[i] - } - - if (event && event.type === 'click') { - relatedTarget.clickEvent = event - } - if (!context) { continue } @@ -438,6 +433,10 @@ class Dropdown extends BaseComponent { continue } + const relatedTarget = { + relatedTarget: toggles[i] + } + if (event) { // Don't close the menu if the clicked element or one of its parents is the dropdown button if ([context._element].some(element => event.composedPath().includes(element))) { @@ -448,29 +447,13 @@ class Dropdown extends BaseComponent { if (event.type === 'keyup' && event.key === TAB_KEY && dropdownMenu.contains(event.target)) { continue } - } - - const hideEvent = EventHandler.trigger(toggles[i], EVENT_HIDE, relatedTarget) - if (hideEvent.defaultPrevented) { - continue - } - // If this is a touch-enabled device we remove the extra - // empty mouseover listeners we added for iOS support - if ('ontouchstart' in document.documentElement) { - [].concat(...document.body.children) - .forEach(elem => EventHandler.off(elem, 'mouseover', noop)) - } - - if (context._popper) { - context._popper.destroy() + if (event.type === 'click') { + relatedTarget.clickEvent = event + } } - dropdownMenu.classList.remove(CLASS_NAME_SHOW) - toggles[i].classList.remove(CLASS_NAME_SHOW) - toggles[i].setAttribute('aria-expanded', 'false') - Manipulator.removeDataAttribute(dropdownMenu, 'popper') - EventHandler.trigger(toggles[i], EVENT_HIDDEN, relatedTarget) + context._completeHide(relatedTarget) } } -- cgit v1.2.3 From bce4684d3400c8ed49060d5888904d0486e51b0f Mon Sep 17 00:00:00 2001 From: Rohit Sharma Date: Wed, 14 Apr 2021 14:54:39 +0530 Subject: Use context properties --- js/src/dropdown.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 7561f9812..dac7368c2 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -428,13 +428,12 @@ class Dropdown extends BaseComponent { continue } - const dropdownMenu = context._menu - if (!toggles[i].classList.contains(CLASS_NAME_SHOW)) { + if (!context._element.classList.contains(CLASS_NAME_SHOW)) { continue } const relatedTarget = { - relatedTarget: toggles[i] + relatedTarget: context._element } if (event) { @@ -444,7 +443,7 @@ class Dropdown extends BaseComponent { } // Tab navigation through the dropdown menu shouldn't close the menu - if (event.type === 'keyup' && event.key === TAB_KEY && dropdownMenu.contains(event.target)) { + if (event.type === 'keyup' && event.key === TAB_KEY && context._menu.contains(event.target)) { continue } -- cgit v1.2.3 From 7eadf73f03eeb51160a455c6438a9ec16ad107a4 Mon Sep 17 00:00:00 2001 From: Rohit Sharma Date: Wed, 14 Apr 2021 15:27:33 +0530 Subject: Change `toggle` method to `remove` Since the class `.show` must be removed --- js/src/dropdown.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index dac7368c2..c314cbcdd 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -259,8 +259,8 @@ class Dropdown extends BaseComponent { this._popper.destroy() } - this._menu.classList.toggle(CLASS_NAME_SHOW) - this._element.classList.toggle(CLASS_NAME_SHOW) + this._menu.classList.remove(CLASS_NAME_SHOW) + this._element.classList.remove(CLASS_NAME_SHOW) this._element.setAttribute('aria-expanded', 'false') Manipulator.removeDataAttribute(this._menu, 'popper') EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget) -- cgit v1.2.3 From 6d312b37c264ac1e4ac244674e6fe700e75f6aa2 Mon Sep 17 00:00:00 2001 From: alpadev <2838324+alpadev@users.noreply.github.com> Date: Sun, 18 Apr 2021 08:01:23 +0200 Subject: fix: clicking an item in navbar dropdown collapses the dropdown in firefox (#33643) --- js/src/dropdown.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index c314cbcdd..2fc2cde21 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -415,7 +415,7 @@ class Dropdown extends BaseComponent { return } - if (/input|select|textarea|form/i.test(event.target.tagName)) { + if (/input|select|option|textarea|form/i.test(event.target.tagName)) { return } } -- cgit v1.2.3 From a9d7a62658c5d93dcba5ed5fc47d84f3ddd3e0a3 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Mon, 19 Apr 2021 08:20:25 +0300 Subject: Use the backdrop util in offcanvas, enforcing consistency (#33545) * respect /share modal's backdrop functionality, keeping consistency * listen click events over backdrop (only) and trigger `hide()` without add/remove event tricks * achieve to hide foreign open offcanvas instances without glitches `if (allReadyOpen && allReadyOpen !== target)`, in case another is going to be open, when user clicks on trigger button --- js/src/offcanvas.js | 60 +++++++++++++++++++++++++++---------------------- js/src/util/backdrop.js | 14 ++++++++++-- 2 files changed, 45 insertions(+), 29 deletions(-) (limited to 'js/src') diff --git a/js/src/offcanvas.js b/js/src/offcanvas.js index 02b0b58a9..2b6335b39 100644 --- a/js/src/offcanvas.js +++ b/js/src/offcanvas.js @@ -7,8 +7,8 @@ import { defineJQueryPlugin, + emulateTransitionEnd, getElementFromSelector, - getSelectorFromElement, getTransitionDurationFromElement, isDisabled, isVisible, @@ -20,6 +20,7 @@ import EventHandler from './dom/event-handler' import BaseComponent from './base-component' import SelectorEngine from './dom/selector-engine' import Manipulator from './dom/manipulator' +import Backdrop from './util/backdrop' /** * ------------------------------------------------------------------------ @@ -46,11 +47,8 @@ const DefaultType = { scroll: 'boolean' } -const CLASS_NAME_BACKDROP_BODY = 'offcanvas-backdrop' const CLASS_NAME_SHOW = 'show' -const CLASS_NAME_TOGGLING = 'offcanvas-toggling' const OPEN_SELECTOR = '.offcanvas.show' -const ACTIVE_SELECTOR = `${OPEN_SELECTOR}, .${CLASS_NAME_TOGGLING}` const EVENT_SHOW = `show${EVENT_KEY}` const EVENT_SHOWN = `shown${EVENT_KEY}` @@ -59,6 +57,7 @@ const EVENT_HIDDEN = `hidden${EVENT_KEY}` const EVENT_FOCUSIN = `focusin${EVENT_KEY}` const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}` +const EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}` const SELECTOR_DATA_DISMISS = '[data-bs-dismiss="offcanvas"]' const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="offcanvas"]' @@ -75,6 +74,7 @@ class Offcanvas extends BaseComponent { this._config = this._getConfig(config) this._isShown = false + this._backdrop = this._initializeBackDrop() this._addEventListeners() } @@ -108,27 +108,25 @@ class Offcanvas extends BaseComponent { this._isShown = true this._element.style.visibility = 'visible' - if (this._config.backdrop) { - document.body.classList.add(CLASS_NAME_BACKDROP_BODY) - } + this._backdrop.show() if (!this._config.scroll) { scrollBarHide() } - this._element.classList.add(CLASS_NAME_TOGGLING) this._element.removeAttribute('aria-hidden') this._element.setAttribute('aria-modal', true) this._element.setAttribute('role', 'dialog') this._element.classList.add(CLASS_NAME_SHOW) const completeCallBack = () => { - this._element.classList.remove(CLASS_NAME_TOGGLING) EventHandler.trigger(this._element, EVENT_SHOWN, { relatedTarget }) this._enforceFocusOnElement(this._element) } - setTimeout(completeCallBack, getTransitionDurationFromElement(this._element)) + const transitionDuration = getTransitionDurationFromElement(this._element) + EventHandler.one(this._element, 'transitionend', completeCallBack) + emulateTransitionEnd(this._element, transitionDuration) } hide() { @@ -142,11 +140,11 @@ class Offcanvas extends BaseComponent { return } - this._element.classList.add(CLASS_NAME_TOGGLING) EventHandler.off(document, EVENT_FOCUSIN) this._element.blur() this._isShown = false this._element.classList.remove(CLASS_NAME_SHOW) + this._backdrop.hide() const completeCallback = () => { this._element.setAttribute('aria-hidden', true) @@ -154,19 +152,25 @@ class Offcanvas extends BaseComponent { this._element.removeAttribute('role') this._element.style.visibility = 'hidden' - if (this._config.backdrop) { - document.body.classList.remove(CLASS_NAME_BACKDROP_BODY) - } - if (!this._config.scroll) { scrollBarReset() } EventHandler.trigger(this._element, EVENT_HIDDEN) - this._element.classList.remove(CLASS_NAME_TOGGLING) } - setTimeout(completeCallback, getTransitionDurationFromElement(this._element)) + const transitionDuration = getTransitionDurationFromElement(this._element) + EventHandler.one(this._element, 'transitionend', completeCallback) + emulateTransitionEnd(this._element, transitionDuration) + } + + dispose() { + this._backdrop.dispose() + super.dispose() + EventHandler.off(document, EVENT_FOCUSIN) + + this._config = null + this._backdrop = null } // Private @@ -181,6 +185,15 @@ class Offcanvas extends BaseComponent { return config } + _initializeBackDrop() { + return new Backdrop({ + isVisible: this._config.backdrop, + isAnimated: true, + rootElement: this._element.parentNode, + clickCallback: () => this.hide() + }) + } + _enforceFocusOnElement(element) { EventHandler.off(document, EVENT_FOCUSIN) // guard against infinite focus loop EventHandler.on(document, EVENT_FOCUSIN, event => { @@ -196,18 +209,11 @@ class Offcanvas extends BaseComponent { _addEventListeners() { EventHandler.on(this._element, EVENT_CLICK_DISMISS, SELECTOR_DATA_DISMISS, () => this.hide()) - EventHandler.on(document, 'keydown', event => { + EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => { if (this._config.keyboard && event.key === ESCAPE_KEY) { this.hide() } }) - - EventHandler.on(document, EVENT_CLICK_DATA_API, event => { - const target = SelectorEngine.findOne(getSelectorFromElement(event.target)) - if (!this._element.contains(event.target) && target !== this._element) { - this.hide() - } - }) } // Static @@ -254,9 +260,9 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( }) // avoid conflict when clicking a toggler of an offcanvas, while another is open - const allReadyOpen = SelectorEngine.findOne(ACTIVE_SELECTOR) + const allReadyOpen = SelectorEngine.findOne(OPEN_SELECTOR) if (allReadyOpen && allReadyOpen !== target) { - return + Offcanvas.getInstance(allReadyOpen).hide() } const data = Data.get(target, DATA_KEY) || new Offcanvas(target) diff --git a/js/src/util/backdrop.js b/js/src/util/backdrop.js index ab14c23fe..a9d28bd10 100644 --- a/js/src/util/backdrop.js +++ b/js/src/util/backdrop.js @@ -11,19 +11,23 @@ import { emulateTransitionEnd, execute, getTransitionDurationFromElement, reflow const Default = { isVisible: true, // if false, we use the backdrop helper without adding any element to the dom isAnimated: false, - rootElement: document.body // give the choice to place backdrop under different elements + rootElement: document.body, // give the choice to place backdrop under different elements + clickCallback: null } const DefaultType = { isVisible: 'boolean', isAnimated: 'boolean', - rootElement: 'element' + rootElement: 'element', + clickCallback: '(function|null)' } const NAME = 'backdrop' const CLASS_NAME_BACKDROP = 'modal-backdrop' const CLASS_NAME_FADE = 'fade' const CLASS_NAME_SHOW = 'show' +const EVENT_MOUSEDOWN = `mousedown.bs.${NAME}` + class Backdrop { constructor(config) { this._config = this._getConfig(config) @@ -96,6 +100,10 @@ class Backdrop { this._config.rootElement.appendChild(this._getElement()) + EventHandler.on(this._getElement(), EVENT_MOUSEDOWN, () => { + execute(this._config.clickCallback) + }) + this._isAppended = true } @@ -104,6 +112,8 @@ class Backdrop { return } + EventHandler.off(this._element, EVENT_MOUSEDOWN) + this._getElement().parentNode.removeChild(this._element) this._isAppended = false } -- cgit v1.2.3 From d67121db28595d3c73da3128615142b2496f2af6 Mon Sep 17 00:00:00 2001 From: alpadev <2838324+alpadev@users.noreply.github.com> Date: Mon, 19 Apr 2021 07:30:33 +0200 Subject: Fix regression for handling `mouseenter`/`mouseleave` events introduced by #33310 (#33679) * test: update spec for sibling adjacent mouseenter/mouseleave events there is a regression introduced by #33310 - this would have catched that * fix: fixup regression for mouseenter/mouseleave events introduced by #33310 the old logic only worked for parent-child movement since it checked for the relatedTarget to contain the delegateTarget - this should be fixed with this Co-authored-by: XhmikosR --- js/src/dom/event-handler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'js/src') diff --git a/js/src/dom/event-handler.js b/js/src/dom/event-handler.js index 8ccb887fc..3293f397d 100644 --- a/js/src/dom/event-handler.js +++ b/js/src/dom/event-handler.js @@ -170,7 +170,7 @@ function addHandler(element, originalTypeEvent, handler, delegationFn, oneOff) { if (customEventsRegex.test(originalTypeEvent)) { const wrapFn = fn => { return function (event) { - if (!event.relatedTarget || (event.relatedTarget !== event.delegateTarget && event.relatedTarget.contains(event.delegateTarget))) { + if (!event.relatedTarget || (event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget))) { return fn.call(this, event) } } -- cgit v1.2.3 From a22f4d3cfd46ad040e0eb636c378fdb832704e07 Mon Sep 17 00:00:00 2001 From: Rohit Sharma Date: Mon, 19 Apr 2021 22:28:45 +0530 Subject: Don't change the value for `altBoundary` option (#33684) - Since bootstrap is not changing the default value of `elementContext` option, changing the value of `altBoundary` option is not needed for any modifier in real Co-authored-by: XhmikosR --- js/src/tooltip.js | 1 - 1 file changed, 1 deletion(-) (limited to 'js/src') diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 2bfa42fc7..ecea04390 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -502,7 +502,6 @@ class Tooltip extends BaseComponent { { name: 'flip', options: { - altBoundary: true, fallbackPlacements: this.config.fallbackPlacements } }, -- cgit v1.2.3 From b59b75bc55ad9d5d2d0259f6b3364a7bcb82b033 Mon Sep 17 00:00:00 2001 From: Rohit Sharma Date: Tue, 20 Apr 2021 10:49:57 +0530 Subject: =?UTF-8?q?Dropdown=20=E2=80=94=20Add=20option=20to=20make=20the?= =?UTF-8?q?=20dropdown=20menu=20clickable=20(#33389)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- js/src/dropdown.js | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 2fc2cde21..d26fa96ca 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -75,7 +75,8 @@ const Default = { boundary: 'clippingParents', reference: 'toggle', display: 'dynamic', - popperConfig: null + popperConfig: null, + autoClose: true } const DefaultType = { @@ -83,7 +84,8 @@ const DefaultType = { boundary: '(string|element)', reference: '(string|element|object)', display: 'string', - popperConfig: '(null|object|function)' + popperConfig: '(null|object|function)', + autoClose: '(boolean|string)' } /** @@ -127,9 +129,8 @@ class Dropdown extends BaseComponent { const isActive = this._element.classList.contains(CLASS_NAME_SHOW) - Dropdown.clearMenus() - if (isActive) { + this.hide() return } @@ -424,7 +425,7 @@ class Dropdown extends BaseComponent { for (let i = 0, len = toggles.length; i < len; i++) { const context = Data.get(toggles[i], DATA_KEY) - if (!context) { + if (!context || context._config.autoClose === false) { continue } @@ -437,8 +438,13 @@ class Dropdown extends BaseComponent { } if (event) { - // Don't close the menu if the clicked element or one of its parents is the dropdown button - if ([context._element].some(element => event.composedPath().includes(element))) { + const composedPath = event.composedPath() + const isMenuTarget = composedPath.includes(context._menu) + if ( + composedPath.includes(context._element) || + (context._config.autoClose === 'inside' && !isMenuTarget) || + (context._config.autoClose === 'outside' && isMenuTarget) + ) { continue } -- cgit v1.2.3 From 0bbe45cd977ae69f11b4cdd55e5d5be08e4a137a Mon Sep 17 00:00:00 2001 From: GeoSot Date: Tue, 20 Apr 2021 08:26:58 +0300 Subject: Tab.js: Fixes on click handling (#33586) * use prevent default only if triggered by anchor * disable auto-initialization if trigger is disabled --- js/src/tab.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'js/src') diff --git a/js/src/tab.js b/js/src/tab.js index ffca5f299..4d823cc61 100644 --- a/js/src/tab.js +++ b/js/src/tab.js @@ -66,8 +66,7 @@ class Tab extends BaseComponent { show() { if ((this._element.parentNode && this._element.parentNode.nodeType === Node.ELEMENT_NODE && - this._element.classList.contains(CLASS_NAME_ACTIVE)) || - isDisabled(this._element)) { + this._element.classList.contains(CLASS_NAME_ACTIVE))) { return } @@ -202,7 +201,13 @@ class Tab extends BaseComponent { */ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) { - event.preventDefault() + if (['A', 'AREA'].includes(this.tagName)) { + event.preventDefault() + } + + if (isDisabled(this)) { + return + } const data = Data.get(this, DATA_KEY) || new Tab(this) data.show() -- cgit v1.2.3 From 079f2cd90c247225eb71dff4d514faf50f653416 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Tue, 20 Apr 2021 08:32:52 +0300 Subject: Offcanvas.js: If scroll is allowed, should allow focus on other elements (#33677) --- js/src/offcanvas.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'js/src') diff --git a/js/src/offcanvas.js b/js/src/offcanvas.js index 2b6335b39..7fcdfb48a 100644 --- a/js/src/offcanvas.js +++ b/js/src/offcanvas.js @@ -112,6 +112,7 @@ class Offcanvas extends BaseComponent { if (!this._config.scroll) { scrollBarHide() + this._enforceFocusOnElement(this._element) } this._element.removeAttribute('aria-hidden') @@ -121,7 +122,6 @@ class Offcanvas extends BaseComponent { const completeCallBack = () => { EventHandler.trigger(this._element, EVENT_SHOWN, { relatedTarget }) - this._enforceFocusOnElement(this._element) } const transitionDuration = getTransitionDurationFromElement(this._element) -- cgit v1.2.3 From 2cbb0a941c0a33fc11518061cfd6fad5c0d1c3c0 Mon Sep 17 00:00:00 2001 From: Carson Sievert Date: Wed, 21 Apr 2021 00:30:19 -0500 Subject: Dropdown: support `.dropdown-item` wrapped in `
  • ` tags (#33634) Co-authored-by: XhmikosR --- js/src/tab.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'js/src') diff --git a/js/src/tab.js b/js/src/tab.js index 4d823cc61..7301779d6 100644 --- a/js/src/tab.js +++ b/js/src/tab.js @@ -161,7 +161,12 @@ class Tab extends BaseComponent { element.classList.add(CLASS_NAME_SHOW) } - if (element.parentNode && element.parentNode.classList.contains(CLASS_NAME_DROPDOWN_MENU)) { + let parent = element.parentNode + if (parent && parent.nodeName === 'LI') { + parent = parent.parentNode + } + + if (parent && parent.classList.contains(CLASS_NAME_DROPDOWN_MENU)) { const dropdownElement = element.closest(SELECTOR_DROPDOWN) if (dropdownElement) { -- cgit v1.2.3 From d381820d1678044257fe2cb7b2f178d1f001ed30 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Sun, 25 Apr 2021 06:50:16 +0300 Subject: Scrollbar: respect the initial body overflow value (#33706) * add method to handle overflow on body element & tests * replace duplicated code on modal/offcanvas tests --- js/src/util/scrollbar.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) (limited to 'js/src') diff --git a/js/src/util/scrollbar.js b/js/src/util/scrollbar.js index 31b614375..352e3e11d 100644 --- a/js/src/util/scrollbar.js +++ b/js/src/util/scrollbar.js @@ -18,11 +18,21 @@ const getWidth = () => { } const hide = (width = getWidth()) => { - document.body.style.overflow = 'hidden' + _disableOverFlow() + // give padding to element to balances the hidden scrollbar width + _setElementAttributes('body', 'paddingRight', calculatedValue => calculatedValue + width) // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements, to keep shown fullwidth _setElementAttributes(SELECTOR_FIXED_CONTENT, 'paddingRight', calculatedValue => calculatedValue + width) _setElementAttributes(SELECTOR_STICKY_CONTENT, 'marginRight', calculatedValue => calculatedValue - width) - _setElementAttributes('body', 'paddingRight', calculatedValue => calculatedValue + width) +} + +const _disableOverFlow = () => { + const actualValue = document.body.style.overflow + if (actualValue) { + Manipulator.setDataAttribute(document.body, 'overflow', actualValue) + } + + document.body.style.overflow = 'hidden' } const _setElementAttributes = (selector, styleProp, callback) => { @@ -41,10 +51,10 @@ const _setElementAttributes = (selector, styleProp, callback) => { } const reset = () => { - document.body.style.overflow = 'auto' + _resetElementAttributes('body', 'overflow') + _resetElementAttributes('body', 'paddingRight') _resetElementAttributes(SELECTOR_FIXED_CONTENT, 'paddingRight') _resetElementAttributes(SELECTOR_STICKY_CONTENT, 'marginRight') - _resetElementAttributes('body', 'paddingRight') } const _resetElementAttributes = (selector, styleProp) => { -- cgit v1.2.3 From bf0936748602c8109fd916c64b4560799fa1c3f8 Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Wed, 5 May 2021 22:32:12 +0300 Subject: Release v5.0.0 (#33647) * Bump version to 5.0.0 * Fix npm tag * Dist --- js/src/alert.js | 2 +- js/src/base-component.js | 4 ++-- js/src/button.js | 2 +- js/src/carousel.js | 2 +- js/src/collapse.js | 2 +- js/src/dom/data.js | 2 +- js/src/dom/event-handler.js | 2 +- js/src/dom/manipulator.js | 2 +- js/src/dom/selector-engine.js | 2 +- js/src/dropdown.js | 2 +- js/src/modal.js | 2 +- js/src/offcanvas.js | 2 +- js/src/popover.js | 2 +- js/src/scrollspy.js | 2 +- js/src/tab.js | 2 +- js/src/toast.js | 2 +- js/src/tooltip.js | 2 +- js/src/util/backdrop.js | 2 +- js/src/util/index.js | 2 +- js/src/util/sanitizer.js | 2 +- js/src/util/scrollbar.js | 2 +- 21 files changed, 22 insertions(+), 22 deletions(-) (limited to 'js/src') diff --git a/js/src/alert.js b/js/src/alert.js index a25c44ec3..884041580 100644 --- a/js/src/alert.js +++ b/js/src/alert.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta3): alert.js + * Bootstrap (v5.0.0): alert.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/base-component.js b/js/src/base-component.js index 77d54faad..a0bb62319 100644 --- a/js/src/base-component.js +++ b/js/src/base-component.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta3): base-component.js + * Bootstrap (v5.0.0): base-component.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ @@ -14,7 +14,7 @@ import EventHandler from './dom/event-handler' * ------------------------------------------------------------------------ */ -const VERSION = '5.0.0-beta3' +const VERSION = '5.0.0' class BaseComponent { constructor(element) { diff --git a/js/src/button.js b/js/src/button.js index 093679e90..45c691dd4 100644 --- a/js/src/button.js +++ b/js/src/button.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta3): button.js + * Bootstrap (v5.0.0): button.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/carousel.js b/js/src/carousel.js index ebb0b7b20..5bf7225f3 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta3): carousel.js + * Bootstrap (v5.0.0): carousel.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/collapse.js b/js/src/collapse.js index 6cb14cdd2..947b6e658 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta3): collapse.js + * Bootstrap (v5.0.0): collapse.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/dom/data.js b/js/src/dom/data.js index 41ad08ab3..5e47365e2 100644 --- a/js/src/dom/data.js +++ b/js/src/dom/data.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta3): dom/data.js + * Bootstrap (v5.0.0): dom/data.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/dom/event-handler.js b/js/src/dom/event-handler.js index 3293f397d..3729f38bf 100644 --- a/js/src/dom/event-handler.js +++ b/js/src/dom/event-handler.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta3): dom/event-handler.js + * Bootstrap (v5.0.0): dom/event-handler.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/dom/manipulator.js b/js/src/dom/manipulator.js index 73b409f7e..093f96cf7 100644 --- a/js/src/dom/manipulator.js +++ b/js/src/dom/manipulator.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta3): dom/manipulator.js + * Bootstrap (v5.0.0): dom/manipulator.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/dom/selector-engine.js b/js/src/dom/selector-engine.js index 116b02741..343dfbb92 100644 --- a/js/src/dom/selector-engine.js +++ b/js/src/dom/selector-engine.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta3): dom/selector-engine.js + * Bootstrap (v5.0.0): dom/selector-engine.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/dropdown.js b/js/src/dropdown.js index d26fa96ca..bb2d01c27 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta3): dropdown.js + * Bootstrap (v5.0.0): dropdown.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/modal.js b/js/src/modal.js index fabb151cb..773e4263f 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta3): modal.js + * Bootstrap (v5.0.0): modal.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/offcanvas.js b/js/src/offcanvas.js index 7fcdfb48a..f3459e667 100644 --- a/js/src/offcanvas.js +++ b/js/src/offcanvas.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta3): offcanvas.js + * Bootstrap (v5.0.0): offcanvas.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/popover.js b/js/src/popover.js index fa8d2c961..58b362328 100644 --- a/js/src/popover.js +++ b/js/src/popover.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta3): popover.js + * Bootstrap (v5.0.0): popover.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index 4e830b530..0f5969660 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta3): scrollspy.js + * Bootstrap (v5.0.0): scrollspy.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/tab.js b/js/src/tab.js index 7301779d6..73eb5a820 100644 --- a/js/src/tab.js +++ b/js/src/tab.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta3): tab.js + * Bootstrap (v5.0.0): tab.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/toast.js b/js/src/toast.js index 5d762b29d..364de29b4 100644 --- a/js/src/toast.js +++ b/js/src/toast.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta3): toast.js + * Bootstrap (v5.0.0): toast.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/tooltip.js b/js/src/tooltip.js index ecea04390..37ef8cb89 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta3): tooltip.js + * Bootstrap (v5.0.0): tooltip.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/util/backdrop.js b/js/src/util/backdrop.js index a9d28bd10..775c09ec0 100644 --- a/js/src/util/backdrop.js +++ b/js/src/util/backdrop.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta3): util/backdrop.js + * Bootstrap (v5.0.0): util/backdrop.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/util/index.js b/js/src/util/index.js index c27c470e9..a5144f15e 100644 --- a/js/src/util/index.js +++ b/js/src/util/index.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta3): util/index.js + * Bootstrap (v5.0.0): util/index.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/util/sanitizer.js b/js/src/util/sanitizer.js index 232a55e6b..9da1b355d 100644 --- a/js/src/util/sanitizer.js +++ b/js/src/util/sanitizer.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta3): util/sanitizer.js + * Bootstrap (v5.0.0): util/sanitizer.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/util/scrollbar.js b/js/src/util/scrollbar.js index 352e3e11d..87ea1390a 100644 --- a/js/src/util/scrollbar.js +++ b/js/src/util/scrollbar.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-beta3): util/scrollBar.js + * Bootstrap (v5.0.0): util/scrollBar.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -- cgit v1.2.3 From 741fa589d027c2d16bff844e45a08c842f5f7e04 Mon Sep 17 00:00:00 2001 From: Nagarjun Bodduna Date: Mon, 10 May 2021 23:47:53 +0530 Subject: Fix backdrop `rootElement` not initialized in Modal (#33853) * Initialize default value of rootElement before using * Remove redundant test | put rootElement tests together Co-authored-by: GeoSot --- js/src/util/backdrop.js | 2 ++ 1 file changed, 2 insertions(+) (limited to 'js/src') diff --git a/js/src/util/backdrop.js b/js/src/util/backdrop.js index 775c09ec0..ad9fcb92f 100644 --- a/js/src/util/backdrop.js +++ b/js/src/util/backdrop.js @@ -89,6 +89,8 @@ class Backdrop { ...Default, ...(typeof config === 'object' ? config : {}) } + + config.rootElement = config.rootElement || document.body typeCheckConfig(NAME, config, DefaultType) return config } -- cgit v1.2.3 From 90b1a6907ed7bb3397fe6bd223f09eb12122d7a3 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Sun, 11 Apr 2021 02:27:18 +0300 Subject: Merge js-components 'transitionend' listener callbacks into one method --- js/src/alert.js | 15 +++------------ js/src/base-component.js | 17 +++++++++++++++++ js/src/carousel.js | 35 ++++++++++++++--------------------- js/src/collapse.js | 11 ++--------- js/src/modal.js | 18 ++---------------- js/src/offcanvas.js | 10 ++-------- js/src/tab.js | 7 +------ js/src/toast.js | 19 ++----------------- js/src/tooltip.js | 22 ++++------------------ 9 files changed, 47 insertions(+), 107 deletions(-) (limited to 'js/src') diff --git a/js/src/alert.js b/js/src/alert.js index 884041580..8d0838737 100644 --- a/js/src/alert.js +++ b/js/src/alert.js @@ -7,9 +7,7 @@ import { defineJQueryPlugin, - emulateTransitionEnd, - getElementFromSelector, - getTransitionDurationFromElement + getElementFromSelector } from './util/index' import Data from './dom/data' import EventHandler from './dom/event-handler' @@ -75,15 +73,8 @@ class Alert extends BaseComponent { _removeElement(element) { element.classList.remove(CLASS_NAME_SHOW) - if (!element.classList.contains(CLASS_NAME_FADE)) { - this._destroyElement(element) - return - } - - const transitionDuration = getTransitionDurationFromElement(element) - - EventHandler.one(element, 'transitionend', () => this._destroyElement(element)) - emulateTransitionEnd(element, transitionDuration) + const isAnimated = element.classList.contains(CLASS_NAME_FADE) + this._queueCallback(() => this._destroyElement(element), element, isAnimated) } _destroyElement(element) { diff --git a/js/src/base-component.js b/js/src/base-component.js index a0bb62319..7d2a5b1e8 100644 --- a/js/src/base-component.js +++ b/js/src/base-component.js @@ -6,6 +6,11 @@ */ import Data from './dom/data' +import { + emulateTransitionEnd, + execute, + getTransitionDurationFromElement +} from './util/index' import EventHandler from './dom/event-handler' /** @@ -34,6 +39,18 @@ class BaseComponent { this._element = null } + _queueCallback(callback, element, isAnimated = true) { + if (!isAnimated) { + execute(callback) + return + } + + const transitionDuration = getTransitionDurationFromElement(element) + EventHandler.one(element, 'transitionend', () => execute(callback)) + + emulateTransitionEnd(element, transitionDuration) + } + /** Static */ static getInstance(element) { diff --git a/js/src/carousel.js b/js/src/carousel.js index 5bf7225f3..2f5cd9de9 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -7,9 +7,7 @@ import { defineJQueryPlugin, - emulateTransitionEnd, getElementFromSelector, - getTransitionDurationFromElement, isRTL, isVisible, reflow, @@ -454,6 +452,15 @@ class Carousel extends BaseComponent { this._setActiveIndicatorElement(nextElement) this._activeElement = nextElement + const triggerSlidEvent = () => { + EventHandler.trigger(this._element, EVENT_SLID, { + relatedTarget: nextElement, + direction: eventDirectionName, + from: activeElementIndex, + to: nextElementIndex + }) + } + if (this._element.classList.contains(CLASS_NAME_SLIDE)) { nextElement.classList.add(orderClassName) @@ -462,9 +469,7 @@ class Carousel extends BaseComponent { activeElement.classList.add(directionalClassName) nextElement.classList.add(directionalClassName) - const transitionDuration = getTransitionDurationFromElement(activeElement) - - EventHandler.one(activeElement, 'transitionend', () => { + const completeCallBack = () => { nextElement.classList.remove(directionalClassName, orderClassName) nextElement.classList.add(CLASS_NAME_ACTIVE) @@ -472,28 +477,16 @@ class Carousel extends BaseComponent { this._isSliding = false - setTimeout(() => { - EventHandler.trigger(this._element, EVENT_SLID, { - relatedTarget: nextElement, - direction: eventDirectionName, - from: activeElementIndex, - to: nextElementIndex - }) - }, 0) - }) + setTimeout(triggerSlidEvent, 0) + } - emulateTransitionEnd(activeElement, transitionDuration) + this._queueCallback(completeCallBack, activeElement, true) } else { activeElement.classList.remove(CLASS_NAME_ACTIVE) nextElement.classList.add(CLASS_NAME_ACTIVE) this._isSliding = false - EventHandler.trigger(this._element, EVENT_SLID, { - relatedTarget: nextElement, - direction: eventDirectionName, - from: activeElementIndex, - to: nextElementIndex - }) + triggerSlidEvent() } if (isCycling) { diff --git a/js/src/collapse.js b/js/src/collapse.js index 947b6e658..bd3846e05 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -7,10 +7,8 @@ import { defineJQueryPlugin, - emulateTransitionEnd, getSelectorFromElement, getElementFromSelector, - getTransitionDurationFromElement, isElement, reflow, typeCheckConfig @@ -200,11 +198,8 @@ class Collapse extends BaseComponent { const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1) const scrollSize = `scroll${capitalizedDimension}` - const transitionDuration = getTransitionDurationFromElement(this._element) - EventHandler.one(this._element, 'transitionend', complete) - - emulateTransitionEnd(this._element, transitionDuration) + this._queueCallback(complete, this._element, true) this._element.style[dimension] = `${this._element[scrollSize]}px` } @@ -250,10 +245,8 @@ class Collapse extends BaseComponent { } this._element.style[dimension] = '' - const transitionDuration = getTransitionDurationFromElement(this._element) - EventHandler.one(this._element, 'transitionend', complete) - emulateTransitionEnd(this._element, transitionDuration) + this._queueCallback(complete, this._element, true) } setTransitioning(isTransitioning) { diff --git a/js/src/modal.js b/js/src/modal.js index 773e4263f..6701c896f 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -176,14 +176,7 @@ class Modal extends BaseComponent { EventHandler.off(this._element, EVENT_CLICK_DISMISS) EventHandler.off(this._dialog, EVENT_MOUSEDOWN_DISMISS) - if (isAnimated) { - const transitionDuration = getTransitionDurationFromElement(this._element) - - EventHandler.one(this._element, 'transitionend', event => this._hideModal(event)) - emulateTransitionEnd(this._element, transitionDuration) - } else { - this._hideModal() - } + this._queueCallback(() => this._hideModal(), this._element, isAnimated) } dispose() { @@ -271,14 +264,7 @@ class Modal extends BaseComponent { }) } - if (isAnimated) { - const transitionDuration = getTransitionDurationFromElement(this._dialog) - - EventHandler.one(this._dialog, 'transitionend', transitionComplete) - emulateTransitionEnd(this._dialog, transitionDuration) - } else { - transitionComplete() - } + this._queueCallback(transitionComplete, this._dialog, isAnimated) } _enforceFocus() { diff --git a/js/src/offcanvas.js b/js/src/offcanvas.js index f3459e667..68f8e8142 100644 --- a/js/src/offcanvas.js +++ b/js/src/offcanvas.js @@ -7,9 +7,7 @@ import { defineJQueryPlugin, - emulateTransitionEnd, getElementFromSelector, - getTransitionDurationFromElement, isDisabled, isVisible, typeCheckConfig @@ -124,9 +122,7 @@ class Offcanvas extends BaseComponent { EventHandler.trigger(this._element, EVENT_SHOWN, { relatedTarget }) } - const transitionDuration = getTransitionDurationFromElement(this._element) - EventHandler.one(this._element, 'transitionend', completeCallBack) - emulateTransitionEnd(this._element, transitionDuration) + this._queueCallback(completeCallBack, this._element, true) } hide() { @@ -159,9 +155,7 @@ class Offcanvas extends BaseComponent { EventHandler.trigger(this._element, EVENT_HIDDEN) } - const transitionDuration = getTransitionDurationFromElement(this._element) - EventHandler.one(this._element, 'transitionend', completeCallback) - emulateTransitionEnd(this._element, transitionDuration) + this._queueCallback(completeCallback, this._element, true) } dispose() { diff --git a/js/src/tab.js b/js/src/tab.js index 73eb5a820..8ee273811 100644 --- a/js/src/tab.js +++ b/js/src/tab.js @@ -7,9 +7,7 @@ import { defineJQueryPlugin, - emulateTransitionEnd, getElementFromSelector, - getTransitionDurationFromElement, isDisabled, reflow } from './util/index' @@ -125,11 +123,8 @@ class Tab extends BaseComponent { const complete = () => this._transitionComplete(element, active, callback) if (active && isTransitioning) { - const transitionDuration = getTransitionDurationFromElement(active) active.classList.remove(CLASS_NAME_SHOW) - - EventHandler.one(active, 'transitionend', complete) - emulateTransitionEnd(active, transitionDuration) + this._queueCallback(complete, element, true) } else { complete() } diff --git a/js/src/toast.js b/js/src/toast.js index 364de29b4..c8539b3a9 100644 --- a/js/src/toast.js +++ b/js/src/toast.js @@ -7,8 +7,6 @@ import { defineJQueryPlugin, - emulateTransitionEnd, - getTransitionDurationFromElement, reflow, typeCheckConfig } from './util/index' @@ -112,14 +110,8 @@ class Toast extends BaseComponent { this._element.classList.remove(CLASS_NAME_HIDE) reflow(this._element) this._element.classList.add(CLASS_NAME_SHOWING) - if (this._config.animation) { - const transitionDuration = getTransitionDurationFromElement(this._element) - EventHandler.one(this._element, 'transitionend', complete) - emulateTransitionEnd(this._element, transitionDuration) - } else { - complete() - } + this._queueCallback(complete, this._element, this._config.animation) } hide() { @@ -139,14 +131,7 @@ class Toast extends BaseComponent { } this._element.classList.remove(CLASS_NAME_SHOW) - if (this._config.animation) { - const transitionDuration = getTransitionDurationFromElement(this._element) - - EventHandler.one(this._element, 'transitionend', complete) - emulateTransitionEnd(this._element, transitionDuration) - } else { - complete() - } + this._queueCallback(complete, this._element, this._config.animation) } dispose() { diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 37ef8cb89..65eb7a11c 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -9,9 +9,7 @@ import * as Popper from '@popperjs/core' import { defineJQueryPlugin, - emulateTransitionEnd, findShadowRoot, - getTransitionDurationFromElement, getUID, isElement, isRTL, @@ -315,13 +313,8 @@ class Tooltip extends BaseComponent { } } - 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() - } + const isAnimated = this.tip.classList.contains(CLASS_NAME_FADE) + this._queueCallback(complete, this.tip, isAnimated) } hide() { @@ -367,15 +360,8 @@ class Tooltip extends BaseComponent { this._activeTrigger[TRIGGER_FOCUS] = false this._activeTrigger[TRIGGER_HOVER] = false - if (this.tip.classList.contains(CLASS_NAME_FADE)) { - const transitionDuration = getTransitionDurationFromElement(tip) - - EventHandler.one(tip, 'transitionend', complete) - emulateTransitionEnd(tip, transitionDuration) - } else { - complete() - } - + const isAnimated = this.tip.classList.contains(CLASS_NAME_FADE) + this._queueCallback(complete, this.tip, isAnimated) this._hoverState = '' } -- cgit v1.2.3 From 9c3ab6557ec89ae11cc27da1b4680b3e69381edf Mon Sep 17 00:00:00 2001 From: Ryan Berliner <22206986+RyanBerliner@users.noreply.github.com> Date: Tue, 11 May 2021 01:37:57 -0400 Subject: Prevent toast autohiding if focusing or hovering (#33221) --- js/src/toast.js | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 5 deletions(-) (limited to 'js/src') diff --git a/js/src/toast.js b/js/src/toast.js index c8539b3a9..94a9084ce 100644 --- a/js/src/toast.js +++ b/js/src/toast.js @@ -26,6 +26,10 @@ const DATA_KEY = 'bs.toast' const EVENT_KEY = `.${DATA_KEY}` const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}` +const EVENT_MOUSEOVER = `mouseover${EVENT_KEY}` +const EVENT_MOUSEOUT = `mouseout${EVENT_KEY}` +const EVENT_FOCUSIN = `focusin${EVENT_KEY}` +const EVENT_FOCUSOUT = `focusout${EVENT_KEY}` const EVENT_HIDE = `hide${EVENT_KEY}` const EVENT_HIDDEN = `hidden${EVENT_KEY}` const EVENT_SHOW = `show${EVENT_KEY}` @@ -62,6 +66,8 @@ class Toast extends BaseComponent { this._config = this._getConfig(config) this._timeout = null + this._hasMouseInteraction = false + this._hasKeyboardInteraction = false this._setListeners() } @@ -100,11 +106,7 @@ class Toast extends BaseComponent { EventHandler.trigger(this._element, EVENT_SHOWN) - if (this._config.autohide) { - this._timeout = setTimeout(() => { - this.hide() - }, this._config.delay) - } + this._maybeScheduleHide() } this._element.classList.remove(CLASS_NAME_HIDE) @@ -159,8 +161,53 @@ class Toast extends BaseComponent { return config } + _maybeScheduleHide() { + if (!this._config.autohide) { + return + } + + if (this._hasMouseInteraction || this._hasKeyboardInteraction) { + return + } + + this._timeout = setTimeout(() => { + this.hide() + }, this._config.delay) + } + + _onInteraction(event, isInteracting) { + switch (event.type) { + case 'mouseover': + case 'mouseout': + this._hasMouseInteraction = isInteracting + break + case 'focusin': + case 'focusout': + this._hasKeyboardInteraction = isInteracting + break + default: + break + } + + if (isInteracting) { + this._clearTimeout() + return + } + + const nextElement = event.relatedTarget + if (this._element === nextElement || this._element.contains(nextElement)) { + return + } + + this._maybeScheduleHide() + } + _setListeners() { EventHandler.on(this._element, EVENT_CLICK_DISMISS, SELECTOR_DATA_DISMISS, () => this.hide()) + EventHandler.on(this._element, EVENT_MOUSEOVER, event => this._onInteraction(event, true)) + EventHandler.on(this._element, EVENT_MOUSEOUT, event => this._onInteraction(event, false)) + EventHandler.on(this._element, EVENT_FOCUSIN, event => this._onInteraction(event, true)) + EventHandler.on(this._element, EVENT_FOCUSOUT, event => this._onInteraction(event, false)) } _clearTimeout() { -- cgit v1.2.3 From 03842b5f259d6007db02c465e6c55929e551e9cd Mon Sep 17 00:00:00 2001 From: GeoSot Date: Tue, 11 May 2021 09:04:42 +0300 Subject: Refactor: move disposing properties into the base class (#33740) Moves more functionality to `base-component`, transferring the responsibility of disposal to parent class. Each component, dusting disposal, sets its protected properties to `null`. So the same can be done in one place for all children components . --- js/src/base-component.js | 5 ++++- js/src/carousel.js | 12 ------------ js/src/collapse.js | 8 -------- js/src/dropdown.js | 3 --- js/src/modal.js | 9 +-------- js/src/offcanvas.js | 3 --- js/src/scrollspy.js | 10 +--------- js/src/toast.js | 1 - js/src/tooltip.js | 7 ------- 9 files changed, 6 insertions(+), 52 deletions(-) (limited to 'js/src') diff --git a/js/src/base-component.js b/js/src/base-component.js index 7d2a5b1e8..588a59d75 100644 --- a/js/src/base-component.js +++ b/js/src/base-component.js @@ -36,7 +36,10 @@ class BaseComponent { dispose() { Data.remove(this._element, this.constructor.DATA_KEY) EventHandler.off(this._element, `.${this.constructor.DATA_KEY}`) - this._element = null + + Object.getOwnPropertyNames(this).forEach(propertyName => { + this[propertyName] = null + }) } _queueCallback(callback, element, isAnimated = true) { diff --git a/js/src/carousel.js b/js/src/carousel.js index 2f5cd9de9..92733637e 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -213,18 +213,6 @@ class Carousel extends BaseComponent { this._slide(order, this._items[index]) } - dispose() { - this._items = null - this._config = null - this._interval = null - this._isPaused = null - this._isSliding = null - this._activeElement = null - this._indicatorsElement = null - - super.dispose() - } - // Private _getConfig(config) { diff --git a/js/src/collapse.js b/js/src/collapse.js index bd3846e05..0d1b91be5 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -253,14 +253,6 @@ class Collapse extends BaseComponent { this._isTransitioning = isTransitioning } - dispose() { - super.dispose() - this._config = null - this._parent = null - this._triggerArray = null - this._isTransitioning = null - } - // Private _getConfig(config) { diff --git a/js/src/dropdown.js b/js/src/dropdown.js index bb2d01c27..8bd3f01e7 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -217,11 +217,8 @@ class Dropdown extends BaseComponent { } dispose() { - this._menu = null - if (this._popper) { this._popper.destroy() - this._popper = null } super.dispose() diff --git a/js/src/modal.js b/js/src/modal.js index 6701c896f..44f2a0cbb 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -183,6 +183,7 @@ class Modal extends BaseComponent { [window, this._dialog] .forEach(htmlElement => EventHandler.off(htmlElement, EVENT_KEY)) + this._backdrop.dispose() super.dispose() /** @@ -191,14 +192,6 @@ class Modal extends BaseComponent { * It will remove `EVENT_CLICK_DATA_API` event that should remain */ EventHandler.off(document, EVENT_FOCUSIN) - - this._config = null - this._dialog = null - this._backdrop.dispose() - this._backdrop = null - this._isShown = null - this._ignoreBackdropClick = null - this._isTransitioning = null } handleUpdate() { diff --git a/js/src/offcanvas.js b/js/src/offcanvas.js index 68f8e8142..8ddb776b1 100644 --- a/js/src/offcanvas.js +++ b/js/src/offcanvas.js @@ -162,9 +162,6 @@ class Offcanvas extends BaseComponent { this._backdrop.dispose() super.dispose() EventHandler.off(document, EVENT_FOCUSIN) - - this._config = null - this._backdrop = null } // Private diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index 0f5969660..7c59dabcf 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -137,16 +137,8 @@ class ScrollSpy extends BaseComponent { } dispose() { - super.dispose() EventHandler.off(this._scrollElement, EVENT_KEY) - - this._scrollElement = null - this._config = null - this._selector = null - this._offsets = null - this._targets = null - this._activeTarget = null - this._scrollHeight = null + super.dispose() } // Private diff --git a/js/src/toast.js b/js/src/toast.js index 94a9084ce..cf869cd5a 100644 --- a/js/src/toast.js +++ b/js/src/toast.js @@ -144,7 +144,6 @@ class Toast extends BaseComponent { } super.dispose() - this._config = null } // Private diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 65eb7a11c..7226d3123 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -219,17 +219,10 @@ class Tooltip extends BaseComponent { 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.config = null - this.tip = null super.dispose() } -- cgit v1.2.3 From 7647b8fe5b77120ba319e7119bb0515d91f734da Mon Sep 17 00:00:00 2001 From: alpadev <2838324+alpadev@users.noreply.github.com> Date: Tue, 11 May 2021 08:09:00 +0200 Subject: Fix: Click on input outside of dropdown-menu prevents dropdown from closing (#33920) * test: add test if user clicks on input not contained within dropdown-menu * fix: click on inputs that are not contained within dropdown-menu prevent dropdown from closing --- js/src/dropdown.js | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 8bd3f01e7..f56ab201b 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -408,14 +408,8 @@ class Dropdown extends BaseComponent { } static clearMenus(event) { - if (event) { - if (event.button === RIGHT_MOUSE_BUTTON || (event.type === 'keyup' && event.key !== TAB_KEY)) { - return - } - - if (/input|select|option|textarea|form/i.test(event.target.tagName)) { - return - } + if (event && (event.button === RIGHT_MOUSE_BUTTON || (event.type === 'keyup' && event.key !== TAB_KEY))) { + return } const toggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE) @@ -445,8 +439,8 @@ class Dropdown extends BaseComponent { continue } - // Tab navigation through the dropdown menu shouldn't close the menu - if (event.type === 'keyup' && event.key === TAB_KEY && context._menu.contains(event.target)) { + // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu + if (context._menu.contains(event.target) && ((event.type === 'keyup' && event.key === TAB_KEY) || /input|select|option|textarea|form/i.test(event.target.tagName))) { continue } -- cgit v1.2.3 From 9fe36edf683af02574bf6bbd6c9b27de93bd31b1 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Tue, 11 May 2021 10:49:30 +0300 Subject: Extract static `DATA_KEY` & `EVENT_KEY` to base-component (#33635) * Force each plugin that extends base-components to implement a static method `NAME()` * Remove redundant `NAME` argument from 'Utils.defineJQueryPlugin' & fix test --- js/src/alert.js | 6 +++--- js/src/base-component.js | 14 +++++++++++++- js/src/button.js | 6 +++--- js/src/carousel.js | 6 +++--- js/src/collapse.js | 6 +++--- js/src/dropdown.js | 6 +++--- js/src/modal.js | 6 +++--- js/src/offcanvas.js | 10 +++++----- js/src/popover.js | 10 +--------- js/src/scrollspy.js | 6 +++--- js/src/tab.js | 6 +++--- js/src/toast.js | 6 +++--- js/src/tooltip.js | 10 +--------- js/src/util/index.js | 3 ++- 14 files changed, 49 insertions(+), 52 deletions(-) (limited to 'js/src') diff --git a/js/src/alert.js b/js/src/alert.js index 8d0838737..db58d7426 100644 --- a/js/src/alert.js +++ b/js/src/alert.js @@ -43,8 +43,8 @@ const CLASS_NAME_SHOW = 'show' class Alert extends BaseComponent { // Getters - static get DATA_KEY() { - return DATA_KEY + static get NAME() { + return NAME } // Public @@ -127,6 +127,6 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DISMISS, Alert.handleDi * add .Alert to jQuery only if jQuery is present */ -defineJQueryPlugin(NAME, Alert) +defineJQueryPlugin(Alert) export default Alert diff --git a/js/src/base-component.js b/js/src/base-component.js index 588a59d75..eacc8420b 100644 --- a/js/src/base-component.js +++ b/js/src/base-component.js @@ -35,7 +35,7 @@ class BaseComponent { dispose() { Data.remove(this._element, this.constructor.DATA_KEY) - EventHandler.off(this._element, `.${this.constructor.DATA_KEY}`) + EventHandler.off(this._element, this.constructor.EVENT_KEY) Object.getOwnPropertyNames(this).forEach(propertyName => { this[propertyName] = null @@ -63,6 +63,18 @@ class BaseComponent { static get VERSION() { return VERSION } + + static get NAME() { + throw new Error('You have to implement the static method "NAME", for each component!') + } + + static get DATA_KEY() { + return `bs.${this.NAME}` + } + + static get EVENT_KEY() { + return `.${this.DATA_KEY}` + } } export default BaseComponent diff --git a/js/src/button.js b/js/src/button.js index 45c691dd4..4932c1955 100644 --- a/js/src/button.js +++ b/js/src/button.js @@ -36,8 +36,8 @@ const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` class Button extends BaseComponent { // Getters - static get DATA_KEY() { - return DATA_KEY + static get NAME() { + return NAME } // Public @@ -90,6 +90,6 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, event => { * add .Button to jQuery only if jQuery is present */ -defineJQueryPlugin(NAME, Button) +defineJQueryPlugin(Button) export default Button diff --git a/js/src/carousel.js b/js/src/carousel.js index 92733637e..0e45fed76 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -127,8 +127,8 @@ class Carousel extends BaseComponent { return Default } - static get DATA_KEY() { - return DATA_KEY + static get NAME() { + return NAME } // Public @@ -598,6 +598,6 @@ EventHandler.on(window, EVENT_LOAD_DATA_API, () => { * add .Carousel to jQuery only if jQuery is present */ -defineJQueryPlugin(NAME, Carousel) +defineJQueryPlugin(Carousel) export default Carousel diff --git a/js/src/collapse.js b/js/src/collapse.js index 0d1b91be5..1c8f53ebd 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -105,8 +105,8 @@ class Collapse extends BaseComponent { return Default } - static get DATA_KEY() { - return DATA_KEY + static get NAME() { + return NAME } // Public @@ -390,6 +390,6 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( * add .Collapse to jQuery only if jQuery is present */ -defineJQueryPlugin(NAME, Collapse) +defineJQueryPlugin(Collapse) export default Collapse diff --git a/js/src/dropdown.js b/js/src/dropdown.js index f56ab201b..8f501d811 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -116,8 +116,8 @@ class Dropdown extends BaseComponent { return DefaultType } - static get DATA_KEY() { - return DATA_KEY + static get NAME() { + return NAME } // Public @@ -530,6 +530,6 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( * add .Dropdown to jQuery only if jQuery is present */ -defineJQueryPlugin(NAME, Dropdown) +defineJQueryPlugin(Dropdown) export default Dropdown diff --git a/js/src/modal.js b/js/src/modal.js index 44f2a0cbb..058d4a36b 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -93,8 +93,8 @@ class Modal extends BaseComponent { return Default } - static get DATA_KEY() { - return DATA_KEY + static get NAME() { + return NAME } // Public @@ -441,6 +441,6 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( * add .Modal to jQuery only if jQuery is present */ -defineJQueryPlugin(NAME, Modal) +defineJQueryPlugin(Modal) export default Modal diff --git a/js/src/offcanvas.js b/js/src/offcanvas.js index 8ddb776b1..65d1e6ba7 100644 --- a/js/src/offcanvas.js +++ b/js/src/offcanvas.js @@ -78,12 +78,12 @@ class Offcanvas extends BaseComponent { // Getters - static get Default() { - return Default + static get NAME() { + return NAME } - static get DATA_KEY() { - return DATA_KEY + static get Default() { + return Default } // Public @@ -271,6 +271,6 @@ EventHandler.on(window, EVENT_LOAD_DATA_API, () => { * ------------------------------------------------------------------------ */ -defineJQueryPlugin(NAME, Offcanvas) +defineJQueryPlugin(Offcanvas) export default Offcanvas diff --git a/js/src/popover.js b/js/src/popover.js index 58b362328..c105ee2a1 100644 --- a/js/src/popover.js +++ b/js/src/popover.js @@ -76,18 +76,10 @@ class Popover extends Tooltip { 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 } @@ -166,6 +158,6 @@ class Popover extends Tooltip { * add .Popover to jQuery only if jQuery is present */ -defineJQueryPlugin(NAME, Popover) +defineJQueryPlugin(Popover) export default Popover diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index 7c59dabcf..d22f48957 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -87,8 +87,8 @@ class ScrollSpy extends BaseComponent { return Default } - static get DATA_KEY() { - return DATA_KEY + static get NAME() { + return NAME } // Public @@ -303,6 +303,6 @@ EventHandler.on(window, EVENT_LOAD_DATA_API, () => { * add .ScrollSpy to jQuery only if jQuery is present */ -defineJQueryPlugin(NAME, ScrollSpy) +defineJQueryPlugin(ScrollSpy) export default ScrollSpy diff --git a/js/src/tab.js b/js/src/tab.js index 8ee273811..34107bac0 100644 --- a/js/src/tab.js +++ b/js/src/tab.js @@ -55,8 +55,8 @@ const SELECTOR_DROPDOWN_ACTIVE_CHILD = ':scope > .dropdown-menu .active' class Tab extends BaseComponent { // Getters - static get DATA_KEY() { - return DATA_KEY + static get NAME() { + return NAME } // Public @@ -220,6 +220,6 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( * add .Tab to jQuery only if jQuery is present */ -defineJQueryPlugin(NAME, Tab) +defineJQueryPlugin(Tab) export default Tab diff --git a/js/src/toast.js b/js/src/toast.js index cf869cd5a..e427ea656 100644 --- a/js/src/toast.js +++ b/js/src/toast.js @@ -81,8 +81,8 @@ class Toast extends BaseComponent { return Default } - static get DATA_KEY() { - return DATA_KEY + static get NAME() { + return NAME } // Public @@ -243,6 +243,6 @@ class Toast extends BaseComponent { * add .Toast to jQuery only if jQuery is present */ -defineJQueryPlugin(NAME, Toast) +defineJQueryPlugin(Toast) export default Toast diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 7226d3123..632115d72 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -155,18 +155,10 @@ class Tooltip extends BaseComponent { 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 } @@ -774,6 +766,6 @@ class Tooltip extends BaseComponent { * add .Tooltip to jQuery only if jQuery is present */ -defineJQueryPlugin(NAME, Tooltip) +defineJQueryPlugin(Tooltip) export default Tooltip diff --git a/js/src/util/index.js b/js/src/util/index.js index a5144f15e..0dd6b1d45 100644 --- a/js/src/util/index.js +++ b/js/src/util/index.js @@ -214,11 +214,12 @@ const onDOMContentLoaded = callback => { const isRTL = () => document.documentElement.dir === 'rtl' -const defineJQueryPlugin = (name, plugin) => { +const defineJQueryPlugin = plugin => { onDOMContentLoaded(() => { const $ = getjQuery() /* istanbul ignore if */ if ($) { + const name = plugin.NAME const JQUERY_NO_CONFLICT = $.fn[name] $.fn[name] = plugin.jQueryInterface $.fn[name].Constructor = plugin -- cgit v1.2.3 From 9a9e22475c9c9d6a4a621daeba5bbfbe8dd280a7 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Wed, 12 May 2021 12:15:59 +0300 Subject: Popover/Tooltip: streamline config property to start with underscore (#33381) --- js/src/popover.js | 2 +- js/src/tooltip.js | 74 +++++++++++++++++++++++++++---------------------------- 2 files changed, 38 insertions(+), 38 deletions(-) (limited to 'js/src') diff --git a/js/src/popover.js b/js/src/popover.js index c105ee2a1..ab57ad570 100644 --- a/js/src/popover.js +++ b/js/src/popover.js @@ -112,7 +112,7 @@ class Popover extends Tooltip { } _getContent() { - return this._element.getAttribute('data-bs-content') || this.config.content + return this._element.getAttribute('data-bs-content') || this._config.content } _cleanTipClass() { diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 632115d72..e44057382 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -139,7 +139,7 @@ class Tooltip extends BaseComponent { this._popper = null // Protected - this.config = this._getConfig(config) + this._config = this._getConfig(config) this.tip = null this._setListeners() @@ -245,13 +245,13 @@ class Tooltip extends BaseComponent { this.setContent() - if (this.config.animation) { + 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 + 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) @@ -272,7 +272,7 @@ class Tooltip extends BaseComponent { tip.classList.add(CLASS_NAME_SHOW) - const customClass = typeof this.config.customClass === 'function' ? this.config.customClass() : this.config.customClass + const customClass = typeof this._config.customClass === 'function' ? this._config.customClass() : this._config.customClass if (customClass) { tip.classList.add(...customClass.split(' ')) } @@ -368,7 +368,7 @@ class Tooltip extends BaseComponent { } const element = document.createElement('div') - element.innerHTML = this.config.template + element.innerHTML = this._config.template this.tip = element.children[0] return this.tip @@ -391,7 +391,7 @@ class Tooltip extends BaseComponent { } // content is a DOM node or a jQuery - if (this.config.html) { + if (this._config.html) { if (content.parentNode !== element) { element.innerHTML = '' element.appendChild(content) @@ -403,9 +403,9 @@ class Tooltip extends BaseComponent { return } - if (this.config.html) { - if (this.config.sanitize) { - content = sanitizeHtml(content, this.config.allowList, this.config.sanitizeFn) + if (this._config.html) { + if (this._config.sanitize) { + content = sanitizeHtml(content, this._config.allowList, this._config.sanitizeFn) } element.innerHTML = content @@ -418,9 +418,9 @@ class Tooltip extends BaseComponent { let title = this._element.getAttribute('data-bs-original-title') if (!title) { - title = typeof this.config.title === 'function' ? - this.config.title.call(this._element) : - this.config.title + title = typeof this._config.title === 'function' ? + this._config.title.call(this._element) : + this._config.title } return title @@ -453,7 +453,7 @@ class Tooltip extends BaseComponent { } _getOffset() { - const { offset } = this.config + const { offset } = this._config if (typeof offset === 'string') { return offset.split(',').map(val => Number.parseInt(val, 10)) @@ -473,7 +473,7 @@ class Tooltip extends BaseComponent { { name: 'flip', options: { - fallbackPlacements: this.config.fallbackPlacements + fallbackPlacements: this._config.fallbackPlacements } }, { @@ -485,7 +485,7 @@ class Tooltip extends BaseComponent { { name: 'preventOverflow', options: { - boundary: this.config.boundary + boundary: this._config.boundary } }, { @@ -510,7 +510,7 @@ class Tooltip extends BaseComponent { return { ...defaultBsPopperConfig, - ...(typeof this.config.popperConfig === 'function' ? this.config.popperConfig(defaultBsPopperConfig) : this.config.popperConfig) + ...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig) } } @@ -519,15 +519,15 @@ class Tooltip extends BaseComponent { } _getContainer() { - if (this.config.container === false) { + if (this._config.container === false) { return document.body } - if (isElement(this.config.container)) { - return this.config.container + if (isElement(this._config.container)) { + return this._config.container } - return SelectorEngine.findOne(this.config.container) + return SelectorEngine.findOne(this._config.container) } _getAttachment(placement) { @@ -535,11 +535,11 @@ class Tooltip extends BaseComponent { } _setListeners() { - const triggers = this.config.trigger.split(' ') + const triggers = this._config.trigger.split(' ') triggers.forEach(trigger => { if (trigger === 'click') { - EventHandler.on(this._element, this.constructor.Event.CLICK, this.config.selector, event => this.toggle(event)) + EventHandler.on(this._element, this.constructor.Event.CLICK, this._config.selector, event => this.toggle(event)) } else if (trigger !== TRIGGER_MANUAL) { const eventIn = trigger === TRIGGER_HOVER ? this.constructor.Event.MOUSEENTER : @@ -548,8 +548,8 @@ class Tooltip extends BaseComponent { this.constructor.Event.MOUSELEAVE : this.constructor.Event.FOCUSOUT - EventHandler.on(this._element, eventIn, this.config.selector, event => this._enter(event)) - EventHandler.on(this._element, eventOut, this.config.selector, event => this._leave(event)) + EventHandler.on(this._element, eventIn, this._config.selector, event => this._enter(event)) + EventHandler.on(this._element, eventOut, this._config.selector, event => this._leave(event)) } }) @@ -561,9 +561,9 @@ class Tooltip extends BaseComponent { EventHandler.on(this._element.closest(`.${CLASS_NAME_MODAL}`), 'hide.bs.modal', this._hideModalHandler) - if (this.config.selector) { - this.config = { - ...this.config, + if (this._config.selector) { + this._config = { + ...this._config, trigger: 'manual', selector: '' } @@ -604,7 +604,7 @@ class Tooltip extends BaseComponent { context._hoverState = HOVER_STATE_SHOW - if (!context.config.delay || !context.config.delay.show) { + if (!context._config.delay || !context._config.delay.show) { context.show() return } @@ -613,7 +613,7 @@ class Tooltip extends BaseComponent { if (context._hoverState === HOVER_STATE_SHOW) { context.show() } - }, context.config.delay.show) + }, context._config.delay.show) } _leave(event, context) { @@ -633,7 +633,7 @@ class Tooltip extends BaseComponent { context._hoverState = HOVER_STATE_OUT - if (!context.config.delay || !context.config.delay.hide) { + if (!context._config.delay || !context._config.delay.hide) { context.hide() return } @@ -642,7 +642,7 @@ class Tooltip extends BaseComponent { if (context._hoverState === HOVER_STATE_OUT) { context.hide() } - }, context.config.delay.hide) + }, context._config.delay.hide) } _isWithActiveTrigger() { @@ -701,10 +701,10 @@ class Tooltip extends BaseComponent { _getDelegateConfig() { const config = {} - if (this.config) { - for (const key in this.config) { - if (this.constructor.Default[key] !== this.config[key]) { - config[key] = this.config[key] + if (this._config) { + for (const key in this._config) { + if (this.constructor.Default[key] !== this._config[key]) { + config[key] = this._config[key] } } } -- cgit v1.2.3 From 6e1c9096f08ed21063fd6ba16aa998a3ac4149f9 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 13 May 2021 18:17:20 +0300 Subject: Move get element functionality to a helper (#33327) Looking around on js components I found out many checks, different expressed but with same purpose. Some of them are trying to parse string to element, others, jQuery element to js simple nodeElement etc With this Pr, I am trying to give a standard way to parse an element So this pr: * Creates `getElement` helper that tries to parse an argument to element or null * Changes `isElement` to make explicit checks and return Boolean * fixes tests deficiencies --- js/src/base-component.js | 3 ++- js/src/collapse.js | 11 ++--------- js/src/dropdown.js | 8 ++------ js/src/tooltip.js | 27 ++++++--------------------- js/src/util/index.js | 27 ++++++++++++++++++++++++++- 5 files changed, 38 insertions(+), 38 deletions(-) (limited to 'js/src') diff --git a/js/src/base-component.js b/js/src/base-component.js index eacc8420b..05b0adcd2 100644 --- a/js/src/base-component.js +++ b/js/src/base-component.js @@ -9,6 +9,7 @@ import Data from './dom/data' import { emulateTransitionEnd, execute, + getElement, getTransitionDurationFromElement } from './util/index' import EventHandler from './dom/event-handler' @@ -23,7 +24,7 @@ const VERSION = '5.0.0' class BaseComponent { constructor(element) { - element = typeof element === 'string' ? document.querySelector(element) : element + element = getElement(element) if (!element) { return diff --git a/js/src/collapse.js b/js/src/collapse.js index 1c8f53ebd..3241a7132 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -7,9 +7,9 @@ import { defineJQueryPlugin, + getElement, getSelectorFromElement, getElementFromSelector, - isElement, reflow, typeCheckConfig } from './util/index' @@ -272,14 +272,7 @@ class Collapse extends BaseComponent { _getParent() { let { parent } = this._config - if (isElement(parent)) { - // it's a jQuery object - if (typeof parent.jquery !== 'undefined' || typeof parent[0] !== 'undefined') { - parent = parent[0] - } - } else { - parent = SelectorEngine.findOne(parent) - } + parent = getElement(parent) const selector = `${SELECTOR_DATA_TOGGLE}[data-bs-parent="${parent}"]` diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 8f501d811..3eb5891f8 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -9,6 +9,7 @@ import * as Popper from '@popperjs/core' import { defineJQueryPlugin, + getElement, getElementFromSelector, isDisabled, isElement, @@ -166,12 +167,7 @@ class Dropdown extends BaseComponent { if (this._config.reference === 'parent') { referenceElement = parent } else if (isElement(this._config.reference)) { - referenceElement = this._config.reference - - // Check if it's jQuery element - if (typeof this._config.reference.jquery !== 'undefined') { - referenceElement = this._config.reference[0] - } + referenceElement = getElement(this._config.reference) } else if (typeof this._config.reference === 'object') { referenceElement = this._config.reference } diff --git a/js/src/tooltip.js b/js/src/tooltip.js index e44057382..fb4657839 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -10,6 +10,7 @@ import * as Popper from '@popperjs/core' import { defineJQueryPlugin, findShadowRoot, + getElement, getUID, isElement, isRTL, @@ -256,7 +257,7 @@ class Tooltip extends BaseComponent { const attachment = this._getAttachment(placement) this._addAttachmentClass(attachment) - const container = this._getContainer() + const { container } = this._config Data.set(tip, this.constructor.DATA_KEY, this) if (!this._element.ownerDocument.documentElement.contains(this.tip)) { @@ -385,10 +386,8 @@ class Tooltip extends BaseComponent { return } - if (typeof content === 'object' && isElement(content)) { - if (content.jquery) { - content = content[0] - } + if (isElement(content)) { + content = getElement(content) // content is a DOM node or a jQuery if (this._config.html) { @@ -518,18 +517,6 @@ class Tooltip extends BaseComponent { this.getTipElement().classList.add(`${CLASS_PREFIX}-${this.updateAttachment(attachment)}`) } - _getContainer() { - if (this._config.container === false) { - return document.body - } - - if (isElement(this._config.container)) { - return this._config.container - } - - return SelectorEngine.findOne(this._config.container) - } - _getAttachment(placement) { return AttachmentMap[placement.toUpperCase()] } @@ -664,16 +651,14 @@ class Tooltip extends BaseComponent { } }) - if (config && typeof config.container === 'object' && config.container.jquery) { - config.container = config.container[0] - } - config = { ...this.constructor.Default, ...dataAttributes, ...(typeof config === 'object' && config ? config : {}) } + config.container = config.container === false ? document.body : getElement(config.container) + if (typeof config.delay === 'number') { config.delay = { show: config.delay, diff --git a/js/src/util/index.js b/js/src/util/index.js index 0dd6b1d45..5ee211c73 100644 --- a/js/src/util/index.js +++ b/js/src/util/index.js @@ -1,3 +1,5 @@ +import SelectorEngine from '../dom/selector-engine' + /** * -------------------------------------------------------------------------- * Bootstrap (v5.0.0): util/index.js @@ -100,7 +102,29 @@ const triggerTransitionEnd = element => { element.dispatchEvent(new Event(TRANSITION_END)) } -const isElement = obj => (obj[0] || obj).nodeType +const isElement = obj => { + if (!obj || typeof obj !== 'object') { + return false + } + + if (typeof obj.jquery !== 'undefined') { + obj = obj[0] + } + + return typeof obj.nodeType !== 'undefined' +} + +const getElement = obj => { + if (isElement(obj)) { // it's a jQuery object or a node element + return obj.jquery ? obj[0] : obj + } + + if (typeof obj === 'string' && obj.length > 0) { + return SelectorEngine.findOne(obj) + } + + return null +} const emulateTransitionEnd = (element, duration) => { let called = false @@ -238,6 +262,7 @@ const execute = callback => { } export { + getElement, getUID, getSelectorFromElement, getElementFromSelector, -- cgit v1.2.3 From 58b1be927f43c779377e478df2d119f2ddf956ca Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Thu, 13 May 2021 19:22:20 +0300 Subject: Release v5.0.1 (#33972) * Bump version to 5.0.1. * Dist --- js/src/alert.js | 2 +- js/src/base-component.js | 4 ++-- js/src/button.js | 2 +- js/src/carousel.js | 2 +- js/src/collapse.js | 2 +- js/src/dom/data.js | 2 +- js/src/dom/event-handler.js | 2 +- js/src/dom/manipulator.js | 2 +- js/src/dom/selector-engine.js | 2 +- js/src/dropdown.js | 2 +- js/src/modal.js | 2 +- js/src/offcanvas.js | 2 +- js/src/popover.js | 2 +- js/src/scrollspy.js | 2 +- js/src/tab.js | 2 +- js/src/toast.js | 2 +- js/src/tooltip.js | 2 +- js/src/util/backdrop.js | 2 +- js/src/util/index.js | 2 +- js/src/util/sanitizer.js | 2 +- js/src/util/scrollbar.js | 2 +- 21 files changed, 22 insertions(+), 22 deletions(-) (limited to 'js/src') diff --git a/js/src/alert.js b/js/src/alert.js index db58d7426..87cc7e731 100644 --- a/js/src/alert.js +++ b/js/src/alert.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0): alert.js + * Bootstrap (v5.0.1): alert.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/base-component.js b/js/src/base-component.js index 05b0adcd2..a5f1b36a0 100644 --- a/js/src/base-component.js +++ b/js/src/base-component.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0): base-component.js + * Bootstrap (v5.0.1): base-component.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ @@ -20,7 +20,7 @@ import EventHandler from './dom/event-handler' * ------------------------------------------------------------------------ */ -const VERSION = '5.0.0' +const VERSION = '5.0.1' class BaseComponent { constructor(element) { diff --git a/js/src/button.js b/js/src/button.js index 4932c1955..6ef753136 100644 --- a/js/src/button.js +++ b/js/src/button.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0): button.js + * Bootstrap (v5.0.1): button.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/carousel.js b/js/src/carousel.js index 0e45fed76..bb894e9c3 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0): carousel.js + * Bootstrap (v5.0.1): carousel.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/collapse.js b/js/src/collapse.js index 3241a7132..fd85fbde2 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0): collapse.js + * Bootstrap (v5.0.1): collapse.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/dom/data.js b/js/src/dom/data.js index 5e47365e2..897998e43 100644 --- a/js/src/dom/data.js +++ b/js/src/dom/data.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0): dom/data.js + * Bootstrap (v5.0.1): dom/data.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/dom/event-handler.js b/js/src/dom/event-handler.js index 3729f38bf..6e808f402 100644 --- a/js/src/dom/event-handler.js +++ b/js/src/dom/event-handler.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0): dom/event-handler.js + * Bootstrap (v5.0.1): dom/event-handler.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/dom/manipulator.js b/js/src/dom/manipulator.js index 093f96cf7..2cd7098a5 100644 --- a/js/src/dom/manipulator.js +++ b/js/src/dom/manipulator.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0): dom/manipulator.js + * Bootstrap (v5.0.1): dom/manipulator.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/dom/selector-engine.js b/js/src/dom/selector-engine.js index 343dfbb92..8476a4643 100644 --- a/js/src/dom/selector-engine.js +++ b/js/src/dom/selector-engine.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0): dom/selector-engine.js + * Bootstrap (v5.0.1): dom/selector-engine.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 3eb5891f8..565edb892 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0): dropdown.js + * Bootstrap (v5.0.1): dropdown.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/modal.js b/js/src/modal.js index 058d4a36b..d4436bf4f 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0): modal.js + * Bootstrap (v5.0.1): modal.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/offcanvas.js b/js/src/offcanvas.js index 65d1e6ba7..fed892f4c 100644 --- a/js/src/offcanvas.js +++ b/js/src/offcanvas.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0): offcanvas.js + * Bootstrap (v5.0.1): offcanvas.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/popover.js b/js/src/popover.js index ab57ad570..b39985124 100644 --- a/js/src/popover.js +++ b/js/src/popover.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0): popover.js + * Bootstrap (v5.0.1): popover.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index d22f48957..3297c45c2 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0): scrollspy.js + * Bootstrap (v5.0.1): scrollspy.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/tab.js b/js/src/tab.js index 34107bac0..51deb170f 100644 --- a/js/src/tab.js +++ b/js/src/tab.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0): tab.js + * Bootstrap (v5.0.1): tab.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/toast.js b/js/src/toast.js index e427ea656..40cd40d9a 100644 --- a/js/src/toast.js +++ b/js/src/toast.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0): toast.js + * Bootstrap (v5.0.1): toast.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/tooltip.js b/js/src/tooltip.js index fb4657839..d164da2b3 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0): tooltip.js + * Bootstrap (v5.0.1): tooltip.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/util/backdrop.js b/js/src/util/backdrop.js index ad9fcb92f..4c18e99c0 100644 --- a/js/src/util/backdrop.js +++ b/js/src/util/backdrop.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0): util/backdrop.js + * Bootstrap (v5.0.1): util/backdrop.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/util/index.js b/js/src/util/index.js index 5ee211c73..9441d0a44 100644 --- a/js/src/util/index.js +++ b/js/src/util/index.js @@ -2,7 +2,7 @@ import SelectorEngine from '../dom/selector-engine' /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0): util/index.js + * Bootstrap (v5.0.1): util/index.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/util/sanitizer.js b/js/src/util/sanitizer.js index 9da1b355d..5fc94275c 100644 --- a/js/src/util/sanitizer.js +++ b/js/src/util/sanitizer.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0): util/sanitizer.js + * Bootstrap (v5.0.1): util/sanitizer.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/util/scrollbar.js b/js/src/util/scrollbar.js index 87ea1390a..98c076f25 100644 --- a/js/src/util/scrollbar.js +++ b/js/src/util/scrollbar.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0): util/scrollBar.js + * Bootstrap (v5.0.1): util/scrollBar.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -- cgit v1.2.3 From 153cf3a235ec7fd86c09cbe5f31d7eebd0aab661 Mon Sep 17 00:00:00 2001 From: Rohit Sharma Date: Tue, 18 May 2021 11:32:39 +0530 Subject: Don't add empty content holder when there is no content available (#33982) * Remove content holder when there is no content * Add tests to check the removal of header/content Co-authored-by: XhmikosR --- js/src/popover.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) (limited to 'js/src') diff --git a/js/src/popover.js b/js/src/popover.js index b39985124..6ab31d6e4 100644 --- a/js/src/popover.js +++ b/js/src/popover.js @@ -30,7 +30,7 @@ const Default = { content: '', template: '' } @@ -90,6 +90,24 @@ class Popover extends Tooltip { return this.getTitle() || this._getContent() } + getTipElement() { + if (this.tip) { + return this.tip + } + + this.tip = super.getTipElement() + + if (!this.getTitle()) { + this.tip.removeChild(SelectorEngine.findOne(SELECTOR_TITLE, this.tip)) + } + + if (!this._getContent()) { + this.tip.removeChild(SelectorEngine.findOne(SELECTOR_CONTENT, this.tip)) + } + + return this.tip + } + setContent() { const tip = this.getTipElement() -- cgit v1.2.3 From 2757fbe28e1039fb629f4c289aacf4f1cc6009c2 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Tue, 18 May 2021 09:26:22 +0300 Subject: Reset inside a Dialog does not work if `data-dismiss="modal"` is set (#33928) --- js/src/modal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'js/src') diff --git a/js/src/modal.js b/js/src/modal.js index d4436bf4f..2dc7e75d9 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -145,7 +145,7 @@ class Modal extends BaseComponent { } hide(event) { - if (event) { + if (event && ['A', 'AREA'].includes(event.target.tagName)) { event.preventDefault() } -- cgit v1.2.3 From df72a21fa89a4885bb666f4a3bc0a9e757b870c2 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Wed, 19 May 2021 01:23:52 +0300 Subject: Add `getNextActiveElement` helper function to utils, replacing custom implementation through components (#33608) --- js/src/carousel.js | 17 ++--------------- js/src/dropdown.js | 22 ++++++---------------- js/src/util/index.js | 29 +++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 31 deletions(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index bb894e9c3..7d197ab1e 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -10,6 +10,7 @@ import { getElementFromSelector, isRTL, isVisible, + getNextActiveElement, reflow, triggerTransitionEnd, typeCheckConfig @@ -337,21 +338,7 @@ class Carousel extends BaseComponent { _getItemByOrder(order, activeElement) { const isNext = order === ORDER_NEXT - const isPrev = order === ORDER_PREV - const activeIndex = this._getItemIndex(activeElement) - const lastItemIndex = this._items.length - 1 - const isGoingToWrap = (isPrev && activeIndex === 0) || (isNext && activeIndex === lastItemIndex) - - if (isGoingToWrap && !this._config.wrap) { - return activeElement - } - - const delta = isPrev ? -1 : 1 - const itemIndex = (activeIndex + delta) % this._items.length - - return itemIndex === -1 ? - this._items[this._items.length - 1] : - this._items[itemIndex] + return getNextActiveElement(this._items, activeElement, isNext, this._config.wrap) } _triggerSlideEvent(relatedTarget, eventDirectionName) { diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 565edb892..cab2d018b 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -16,6 +16,7 @@ import { isVisible, isRTL, noop, + getNextActiveElement, typeCheckConfig } from './util/index' import Data from './dom/data' @@ -354,28 +355,17 @@ class Dropdown extends BaseComponent { } _selectMenuItem(event) { - const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(isVisible) - - if (!items.length) { + if (![ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key)) { return } - let index = items.indexOf(event.target) - - // Up - if (event.key === ARROW_UP_KEY && index > 0) { - index-- - } + const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(isVisible) - // Down - if (event.key === ARROW_DOWN_KEY && index < items.length - 1) { - index++ + if (!items.length) { + return } - // index is -1 if the first keydown is an ArrowUp - index = index === -1 ? 0 : index - - items[index].focus() + getNextActiveElement(items, event.target, event.key === ARROW_DOWN_KEY, false).focus() } // Static diff --git a/js/src/util/index.js b/js/src/util/index.js index 9441d0a44..6b38a05e9 100644 --- a/js/src/util/index.js +++ b/js/src/util/index.js @@ -261,6 +261,34 @@ const execute = callback => { } } +/** + * Return the previous/next element of a list. + * + * @param {array} list The list of elements + * @param activeElement The active element + * @param shouldGetNext Choose to get next or previous element + * @param isCycleAllowed + * @return {Element|elem} The proper element + */ +const getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => { + let index = list.indexOf(activeElement) + + // if the element does not exist in the list initialize it as the first element + if (index === -1) { + return list[0] + } + + const listLength = list.length + + index += shouldGetNext ? 1 : -1 + + if (isCycleAllowed) { + index = (index + listLength) % listLength + } + + return list[Math.max(0, Math.min(index, listLength - 1))] +} + export { getElement, getUID, @@ -275,6 +303,7 @@ export { isDisabled, findShadowRoot, noop, + getNextActiveElement, reflow, getjQuery, onDOMContentLoaded, -- cgit v1.2.3 From 9e4f87ae8f2e32bb31795ece8f9ab3e7b823dd7d Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 20 May 2021 16:16:55 +0300 Subject: Allow use of `dispose/hide` methods on Tooltip & Popover from jQueryInterface, when component does not exists. (#33371) --- js/src/popover.js | 4 ---- js/src/tooltip.js | 4 ---- 2 files changed, 8 deletions(-) (limited to 'js/src') diff --git a/js/src/popover.js b/js/src/popover.js index 6ab31d6e4..e8614156c 100644 --- a/js/src/popover.js +++ b/js/src/popover.js @@ -149,10 +149,6 @@ class Popover extends Tooltip { let data = Data.get(this, DATA_KEY) const _config = typeof config === 'object' ? config : null - if (!data && /dispose|hide/.test(config)) { - return - } - if (!data) { data = new Popover(this, _config) Data.set(this, DATA_KEY, data) diff --git a/js/src/tooltip.js b/js/src/tooltip.js index d164da2b3..84379c88f 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -725,10 +725,6 @@ class Tooltip extends BaseComponent { let data = Data.get(this, DATA_KEY) const _config = typeof config === 'object' && config - if (!data && /dispose|hide/.test(config)) { - return - } - if (!data) { data = new Tooltip(this, _config) } -- cgit v1.2.3 From 79c3bf47bc125fd64cf8cac6cd34b03270d34e2d Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 20 May 2021 16:29:04 +0300 Subject: Add Tests on scrollbar.js & better handling if a style property doesn't exists (#33948) * scrollbar.js: add some tests transfer test from modal.spec. to scrollbar.spec proper handling if style property doesn't exist --- js/src/util/scrollbar.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'js/src') diff --git a/js/src/util/scrollbar.js b/js/src/util/scrollbar.js index 98c076f25..79a3b12c7 100644 --- a/js/src/util/scrollbar.js +++ b/js/src/util/scrollbar.js @@ -44,8 +44,11 @@ const _setElementAttributes = (selector, styleProp, callback) => { } const actualValue = element.style[styleProp] + if (actualValue) { + Manipulator.setDataAttribute(element, styleProp, actualValue) + } + const calculatedValue = window.getComputedStyle(element)[styleProp] - Manipulator.setDataAttribute(element, styleProp, actualValue) element.style[styleProp] = `${callback(Number.parseFloat(calculatedValue))}px` }) } -- cgit v1.2.3 From 4ac711b5b4c0f733870cd8dd1f18b73f964275fc Mon Sep 17 00:00:00 2001 From: Ryan Berliner <22206986+RyanBerliner@users.noreply.github.com> Date: Thu, 20 May 2021 09:50:53 -0400 Subject: Refactor `isVisible` helper, fixing false positives from deep nesting or alternate means (#33960) --- js/src/util/index.js | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) (limited to 'js/src') diff --git a/js/src/util/index.js b/js/src/util/index.js index 6b38a05e9..77bdc072f 100644 --- a/js/src/util/index.js +++ b/js/src/util/index.js @@ -159,20 +159,11 @@ const typeCheckConfig = (componentName, config, configTypes) => { } const isVisible = element => { - if (!element) { + if (!isElement(element) || element.getClientRects().length === 0) { return false } - if (element.style && element.parentNode && element.parentNode.style) { - const elementStyle = getComputedStyle(element) - const parentNodeStyle = getComputedStyle(element.parentNode) - - return elementStyle.display !== 'none' && - parentNodeStyle.display !== 'none' && - elementStyle.visibility !== 'hidden' - } - - return false + return getComputedStyle(element).getPropertyValue('visibility') === 'visible' } const isDisabled = element => { -- cgit v1.2.3 From a2b5901efc6de12bb828f8dda118ddccbcd545cf Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Fri, 21 May 2021 18:16:05 -0400 Subject: Fix bug where backdrop calls method on null if it is already removed from the body (#34014) Co-authored-by: Rohit Sharma --- js/src/util/backdrop.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'js/src') diff --git a/js/src/util/backdrop.js b/js/src/util/backdrop.js index 4c18e99c0..c05c221dd 100644 --- a/js/src/util/backdrop.js +++ b/js/src/util/backdrop.js @@ -116,7 +116,11 @@ class Backdrop { EventHandler.off(this._element, EVENT_MOUSEDOWN) - this._getElement().parentNode.removeChild(this._element) + const { parentNode } = this._getElement() + if (parentNode) { + parentNode.removeChild(this._element) + } + this._isAppended = false } -- cgit v1.2.3 From b39b665072a2d36914e741b2c11620323924be89 Mon Sep 17 00:00:00 2001 From: alpadev <2838324+alpadev@users.noreply.github.com> Date: Sat, 22 May 2021 09:58:52 +0200 Subject: Automatically select an item in the dropdown when using arrow keys (#34052) --- js/src/dropdown.js | 21 ++++++++++----------- js/src/util/index.js | 4 ++-- 2 files changed, 12 insertions(+), 13 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index cab2d018b..34beb6512 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -354,18 +354,16 @@ class Dropdown extends BaseComponent { } } - _selectMenuItem(event) { - if (![ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key)) { - return - } - + _selectMenuItem({ key, target }) { const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(isVisible) if (!items.length) { return } - getNextActiveElement(items, event.target, event.key === ARROW_DOWN_KEY, false).focus() + // if target isn't included in items (e.g. when expanding the dropdown) + // allow cycling to get the last item in case key equals ARROW_UP_KEY + getNextActiveElement(items, target, key === ARROW_DOWN_KEY, !items.includes(target)).focus() } // Static @@ -480,17 +478,18 @@ class Dropdown extends BaseComponent { return } - if (!isActive && (event.key === ARROW_UP_KEY || event.key === ARROW_DOWN_KEY)) { - getToggleButton().click() + if (event.key === ARROW_UP_KEY || event.key === ARROW_DOWN_KEY) { + if (!isActive) { + getToggleButton().click() + } + + Dropdown.getInstance(getToggleButton())._selectMenuItem(event) return } if (!isActive || event.key === SPACE_KEY) { Dropdown.clearMenus() - return } - - Dropdown.getInstance(getToggleButton())._selectMenuItem(event) } } diff --git a/js/src/util/index.js b/js/src/util/index.js index 77bdc072f..4d077b21f 100644 --- a/js/src/util/index.js +++ b/js/src/util/index.js @@ -264,9 +264,9 @@ const execute = callback => { const getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => { let index = list.indexOf(activeElement) - // if the element does not exist in the list initialize it as the first element + // if the element does not exist in the list return an element depending on the direction and if cycle is allowed if (index === -1) { - return list[0] + return list[!shouldGetNext && isCycleAllowed ? list.length - 1 : 0] } const listLength = list.length -- cgit v1.2.3 From b513a19003815b9c300df6b54419fa0123bff9c6 Mon Sep 17 00:00:00 2001 From: alpadev <2838324+alpadev@users.noreply.github.com> Date: Mon, 24 May 2021 17:52:36 +0200 Subject: Fix prevented show event disables modals with fade class from being displayed again (#34085) Fix modal, in case is faded, a prevented show event can cause show method to not be executed again. --- js/src/modal.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'js/src') diff --git a/js/src/modal.js b/js/src/modal.js index 2dc7e75d9..b05fe8de7 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -108,20 +108,20 @@ class Modal extends BaseComponent { return } - if (this._isAnimated()) { - this._isTransitioning = true - } - const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, { relatedTarget }) - if (this._isShown || showEvent.defaultPrevented) { + if (showEvent.defaultPrevented) { return } this._isShown = true + if (this._isAnimated()) { + this._isTransitioning = true + } + scrollBarHide() document.body.classList.add(CLASS_NAME_OPEN) -- cgit v1.2.3 From 544d9ac3cf5b7a501524c1bab9570f4b46b8e7e4 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Tue, 25 May 2021 18:30:38 +0300 Subject: Change `element.parentNode.removeChild(element)` to `element.remove()` (#34071) --- js/src/alert.js | 4 +--- js/src/popover.js | 4 ++-- js/src/tooltip.js | 8 ++++---- js/src/util/backdrop.js | 6 +----- js/src/util/sanitizer.js | 2 +- 5 files changed, 9 insertions(+), 15 deletions(-) (limited to 'js/src') diff --git a/js/src/alert.js b/js/src/alert.js index 87cc7e731..679a90cdb 100644 --- a/js/src/alert.js +++ b/js/src/alert.js @@ -78,9 +78,7 @@ class Alert extends BaseComponent { } _destroyElement(element) { - if (element.parentNode) { - element.parentNode.removeChild(element) - } + element.remove() EventHandler.trigger(element, EVENT_CLOSED) } diff --git a/js/src/popover.js b/js/src/popover.js index e8614156c..929391392 100644 --- a/js/src/popover.js +++ b/js/src/popover.js @@ -98,11 +98,11 @@ class Popover extends Tooltip { this.tip = super.getTipElement() if (!this.getTitle()) { - this.tip.removeChild(SelectorEngine.findOne(SELECTOR_TITLE, this.tip)) + SelectorEngine.findOne(SELECTOR_TITLE, this.tip).remove() } if (!this._getContent()) { - this.tip.removeChild(SelectorEngine.findOne(SELECTOR_CONTENT, this.tip)) + SelectorEngine.findOne(SELECTOR_CONTENT, this.tip).remove() } return this.tip diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 84379c88f..2eb91965b 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -208,8 +208,8 @@ class Tooltip extends BaseComponent { EventHandler.off(this._element.closest(`.${CLASS_NAME_MODAL}`), 'hide.bs.modal', this._hideModalHandler) - if (this.tip && this.tip.parentNode) { - this.tip.parentNode.removeChild(this.tip) + if (this.tip) { + this.tip.remove() } if (this._popper) { @@ -314,8 +314,8 @@ class Tooltip extends BaseComponent { return } - if (this._hoverState !== HOVER_STATE_SHOW && tip.parentNode) { - tip.parentNode.removeChild(tip) + if (this._hoverState !== HOVER_STATE_SHOW) { + tip.remove() } this._cleanTipClass() diff --git a/js/src/util/backdrop.js b/js/src/util/backdrop.js index c05c221dd..f7990f701 100644 --- a/js/src/util/backdrop.js +++ b/js/src/util/backdrop.js @@ -116,11 +116,7 @@ class Backdrop { EventHandler.off(this._element, EVENT_MOUSEDOWN) - const { parentNode } = this._getElement() - if (parentNode) { - parentNode.removeChild(this._element) - } - + this._element.remove() this._isAppended = false } diff --git a/js/src/util/sanitizer.js b/js/src/util/sanitizer.js index 5fc94275c..d1e55a2b1 100644 --- a/js/src/util/sanitizer.js +++ b/js/src/util/sanitizer.js @@ -108,7 +108,7 @@ export function sanitizeHtml(unsafeHtml, allowList, sanitizeFn) { const elName = el.nodeName.toLowerCase() if (!allowlistKeys.includes(elName)) { - el.parentNode.removeChild(el) + el.remove() continue } -- cgit v1.2.3 From 0cb70e214ffd24070fa9e312746953faa0b8a305 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Mon, 31 May 2021 05:35:59 -0400 Subject: Changing Backdrop rootElement to default to a string (#34092) The current config can cause the "body" to become stale. Specifically, if the entire body element is swapped out for a new body element, then the backdrop will continue to append itself to the original body element, since it's stored in memory as a reference on this object. This also no longer allows an explicit null to be passed to Backdrop's rootElement This still accomplishes the laziness of "not finding the rootElement until the Backdrop is created" to avoid problems of the JavaScript being included inside (so, before body is available). --- js/src/util/backdrop.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'js/src') diff --git a/js/src/util/backdrop.js b/js/src/util/backdrop.js index f7990f701..07ad20fab 100644 --- a/js/src/util/backdrop.js +++ b/js/src/util/backdrop.js @@ -6,19 +6,19 @@ */ import EventHandler from '../dom/event-handler' -import { emulateTransitionEnd, execute, getTransitionDurationFromElement, reflow, typeCheckConfig } from './index' +import { emulateTransitionEnd, execute, getElement, getTransitionDurationFromElement, reflow, typeCheckConfig } from './index' const Default = { isVisible: true, // if false, we use the backdrop helper without adding any element to the dom isAnimated: false, - rootElement: document.body, // give the choice to place backdrop under different elements + rootElement: 'body', // give the choice to place backdrop under different elements clickCallback: null } const DefaultType = { isVisible: 'boolean', isAnimated: 'boolean', - rootElement: 'element', + rootElement: '(element|string)', clickCallback: '(function|null)' } const NAME = 'backdrop' @@ -90,7 +90,8 @@ class Backdrop { ...(typeof config === 'object' ? config : {}) } - config.rootElement = config.rootElement || document.body + // use getElement() with the default "body" to get a fresh Element on each instantiation + config.rootElement = getElement(config.rootElement) typeCheckConfig(NAME, config, DefaultType) return config } -- cgit v1.2.3 From 4a5029ea29ac75243dfec68153051292fc70f5cf Mon Sep 17 00:00:00 2001 From: alpadev <2838324+alpadev@users.noreply.github.com> Date: Thu, 3 Jun 2021 13:44:16 +0200 Subject: Fix handling of transitionend events dispatched by nested elements(#33845) Fix handling of transitionend events dispatched by nested elements Properly handle events from nested elements Change `emulateTransitionEnd` to `executeAfterTransition` && --- js/src/base-component.js | 16 +++------------ js/src/modal.js | 31 +++++++++++++++-------------- js/src/util/backdrop.js | 11 ++--------- js/src/util/index.js | 51 +++++++++++++++++++++++++++++------------------- 4 files changed, 52 insertions(+), 57 deletions(-) (limited to 'js/src') diff --git a/js/src/base-component.js b/js/src/base-component.js index a5f1b36a0..368cc99c8 100644 --- a/js/src/base-component.js +++ b/js/src/base-component.js @@ -7,10 +7,8 @@ import Data from './dom/data' import { - emulateTransitionEnd, - execute, - getElement, - getTransitionDurationFromElement + executeAfterTransition, + getElement } from './util/index' import EventHandler from './dom/event-handler' @@ -44,15 +42,7 @@ class BaseComponent { } _queueCallback(callback, element, isAnimated = true) { - if (!isAnimated) { - execute(callback) - return - } - - const transitionDuration = getTransitionDurationFromElement(element) - EventHandler.one(element, 'transitionend', () => execute(callback)) - - emulateTransitionEnd(element, transitionDuration) + executeAfterTransition(callback, element, isAnimated) } /** Static */ diff --git a/js/src/modal.js b/js/src/modal.js index b05fe8de7..e8eee3b4d 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -7,9 +7,7 @@ import { defineJQueryPlugin, - emulateTransitionEnd, getElementFromSelector, - getTransitionDurationFromElement, isRTL, isVisible, reflow, @@ -339,25 +337,28 @@ class Modal extends BaseComponent { return } - const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight + const { classList, scrollHeight, style } = this._element + const isModalOverflowing = scrollHeight > document.documentElement.clientHeight + + // return if the following background transition hasn't yet completed + if ((!isModalOverflowing && style.overflowY === 'hidden') || classList.contains(CLASS_NAME_STATIC)) { + return + } if (!isModalOverflowing) { - this._element.style.overflowY = 'hidden' + style.overflowY = 'hidden' } - this._element.classList.add(CLASS_NAME_STATIC) - const modalTransitionDuration = getTransitionDurationFromElement(this._dialog) - EventHandler.off(this._element, 'transitionend') - EventHandler.one(this._element, 'transitionend', () => { - this._element.classList.remove(CLASS_NAME_STATIC) + classList.add(CLASS_NAME_STATIC) + this._queueCallback(() => { + classList.remove(CLASS_NAME_STATIC) if (!isModalOverflowing) { - EventHandler.one(this._element, 'transitionend', () => { - this._element.style.overflowY = '' - }) - emulateTransitionEnd(this._element, modalTransitionDuration) + this._queueCallback(() => { + style.overflowY = '' + }, this._dialog) } - }) - emulateTransitionEnd(this._element, modalTransitionDuration) + }, this._dialog) + this._element.focus() } diff --git a/js/src/util/backdrop.js b/js/src/util/backdrop.js index 07ad20fab..028325d11 100644 --- a/js/src/util/backdrop.js +++ b/js/src/util/backdrop.js @@ -6,7 +6,7 @@ */ import EventHandler from '../dom/event-handler' -import { emulateTransitionEnd, execute, getElement, getTransitionDurationFromElement, reflow, typeCheckConfig } from './index' +import { execute, executeAfterTransition, getElement, reflow, typeCheckConfig } from './index' const Default = { isVisible: true, // if false, we use the backdrop helper without adding any element to the dom @@ -122,14 +122,7 @@ class Backdrop { } _emulateAnimation(callback) { - if (!this._config.isAnimated) { - execute(callback) - return - } - - const backdropTransitionDuration = getTransitionDurationFromElement(this._getElement()) - EventHandler.one(this._getElement(), 'transitionend', () => execute(callback)) - emulateTransitionEnd(this._getElement(), backdropTransitionDuration) + executeAfterTransition(callback, this._getElement(), this._config.isAnimated) } } diff --git a/js/src/util/index.js b/js/src/util/index.js index 4d077b21f..6edfaa580 100644 --- a/js/src/util/index.js +++ b/js/src/util/index.js @@ -126,24 +126,6 @@ const getElement = obj => { return null } -const emulateTransitionEnd = (element, duration) => { - let called = false - const durationPadding = 5 - const emulatedDuration = duration + durationPadding - - function listener() { - called = true - element.removeEventListener(TRANSITION_END, listener) - } - - element.addEventListener(TRANSITION_END, listener) - setTimeout(() => { - if (!called) { - triggerTransitionEnd(element) - } - }, emulatedDuration) -} - const typeCheckConfig = (componentName, config, configTypes) => { Object.keys(configTypes).forEach(property => { const expectedTypes = configTypes[property] @@ -252,6 +234,35 @@ const execute = callback => { } } +const executeAfterTransition = (callback, transitionElement, waitForTransition = true) => { + if (!waitForTransition) { + execute(callback) + return + } + + const durationPadding = 5 + const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding + + let called = false + + const handler = ({ target }) => { + if (target !== transitionElement) { + return + } + + called = true + transitionElement.removeEventListener(TRANSITION_END, handler) + execute(callback) + } + + transitionElement.addEventListener(TRANSITION_END, handler) + setTimeout(() => { + if (!called) { + triggerTransitionEnd(transitionElement) + } + }, emulatedDuration) +} + /** * Return the previous/next element of a list. * @@ -288,7 +299,6 @@ export { getTransitionDurationFromElement, triggerTransitionEnd, isElement, - emulateTransitionEnd, typeCheckConfig, isVisible, isDisabled, @@ -300,5 +310,6 @@ export { onDOMContentLoaded, isRTL, defineJQueryPlugin, - execute + execute, + executeAfterTransition } -- cgit v1.2.3 From c98657b8303150bfda3bdea750055b83a29b27a3 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 3 Jun 2021 18:53:27 +0300 Subject: Add `getOrCreateInstance` method in base-component (#33276) Co-authored-by: Rohit Sharma Co-authored-by: XhmikosR --- js/src/alert.js | 7 +------ js/src/base-component.js | 4 ++++ js/src/button.js | 13 ++----------- js/src/carousel.js | 26 ++++++-------------------- js/src/collapse.js | 6 +++--- js/src/dropdown.js | 10 ++-------- js/src/modal.js | 6 +++--- js/src/offcanvas.js | 12 +++++------- js/src/popover.js | 9 +-------- js/src/scrollspy.js | 2 +- js/src/tab.js | 5 ++--- js/src/toast.js | 8 +------- js/src/tooltip.js | 7 +------ 13 files changed, 32 insertions(+), 83 deletions(-) (limited to 'js/src') diff --git a/js/src/alert.js b/js/src/alert.js index 679a90cdb..e5e5e2a5d 100644 --- a/js/src/alert.js +++ b/js/src/alert.js @@ -9,7 +9,6 @@ import { defineJQueryPlugin, getElementFromSelector } from './util/index' -import Data from './dom/data' import EventHandler from './dom/event-handler' import BaseComponent from './base-component' @@ -87,11 +86,7 @@ class Alert extends BaseComponent { static jQueryInterface(config) { return this.each(function () { - let data = Data.get(this, DATA_KEY) - - if (!data) { - data = new Alert(this) - } + const data = Alert.getOrCreateInstance(this) if (config === 'close') { data[config](this) diff --git a/js/src/base-component.js b/js/src/base-component.js index 368cc99c8..cadd53d26 100644 --- a/js/src/base-component.js +++ b/js/src/base-component.js @@ -51,6 +51,10 @@ class BaseComponent { return Data.get(element, this.DATA_KEY) } + static getOrCreateInstance(element, config = {}) { + return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null) + } + static get VERSION() { return VERSION } diff --git a/js/src/button.js b/js/src/button.js index 6ef753136..c0e6b5d2b 100644 --- a/js/src/button.js +++ b/js/src/button.js @@ -6,7 +6,6 @@ */ import { defineJQueryPlugin } from './util/index' -import Data from './dom/data' import EventHandler from './dom/event-handler' import BaseComponent from './base-component' @@ -51,11 +50,7 @@ class Button extends BaseComponent { static jQueryInterface(config) { return this.each(function () { - let data = Data.get(this, DATA_KEY) - - if (!data) { - data = new Button(this) - } + const data = Button.getOrCreateInstance(this) if (config === 'toggle') { data[config]() @@ -74,11 +69,7 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, event => { event.preventDefault() const button = event.target.closest(SELECTOR_DATA_TOGGLE) - - let data = Data.get(button, DATA_KEY) - if (!data) { - data = new Button(button) - } + const data = Button.getOrCreateInstance(button) data.toggle() }) diff --git a/js/src/carousel.js b/js/src/carousel.js index 7d197ab1e..a956ebc8b 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -15,7 +15,6 @@ import { triggerTransitionEnd, typeCheckConfig } from './util/index' -import Data from './dom/data' import EventHandler from './dom/event-handler' import Manipulator from './dom/manipulator' import SelectorEngine from './dom/selector-engine' @@ -219,7 +218,8 @@ class Carousel extends BaseComponent { _getConfig(config) { config = { ...Default, - ...config + ...Manipulator.getDataAttributes(this._element), + ...(typeof config === 'object' ? config : {}) } typeCheckConfig(NAME, config, DefaultType) return config @@ -496,25 +496,11 @@ class Carousel extends BaseComponent { // Static static carouselInterface(element, config) { - let data = Data.get(element, DATA_KEY) - let _config = { - ...Default, - ...Manipulator.getDataAttributes(element) - } - - if (typeof config === 'object') { - _config = { - ..._config, - ...config - } - } + const data = Carousel.getOrCreateInstance(element, config) + const { _config } = data const action = typeof config === 'string' ? config : _config.slide - if (!data) { - data = new Carousel(element, _config) - } - if (typeof config === 'number') { data.to(config) } else if (typeof action === 'string') { @@ -555,7 +541,7 @@ class Carousel extends BaseComponent { Carousel.carouselInterface(target, config) if (slideIndex) { - Data.get(target, DATA_KEY).to(slideIndex) + Carousel.getInstance(target).to(slideIndex) } event.preventDefault() @@ -574,7 +560,7 @@ EventHandler.on(window, EVENT_LOAD_DATA_API, () => { const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE) for (let i = 0, len = carousels.length; i < len; i++) { - Carousel.carouselInterface(carousels[i], Data.get(carousels[i], DATA_KEY)) + Carousel.carouselInterface(carousels[i], Carousel.getInstance(carousels[i])) } }) diff --git a/js/src/collapse.js b/js/src/collapse.js index fd85fbde2..2d12ef57f 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -145,7 +145,7 @@ class Collapse extends BaseComponent { const container = SelectorEngine.findOne(this._selector) if (actives) { const tempActiveData = actives.find(elem => container !== elem) - activesData = tempActiveData ? Data.get(tempActiveData, DATA_KEY) : null + activesData = tempActiveData ? Collapse.getInstance(tempActiveData) : null if (activesData && activesData._isTransitioning) { return @@ -310,7 +310,7 @@ class Collapse extends BaseComponent { // Static static collapseInterface(element, config) { - let data = Data.get(element, DATA_KEY) + let data = Collapse.getInstance(element) const _config = { ...Default, ...Manipulator.getDataAttributes(element), @@ -358,7 +358,7 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( const selectorElements = SelectorEngine.find(selector) selectorElements.forEach(element => { - const data = Data.get(element, DATA_KEY) + const data = Collapse.getInstance(element) let config if (data) { // update parent attribute diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 34beb6512..e79ac4591 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -19,7 +19,6 @@ import { getNextActiveElement, typeCheckConfig } from './util/index' -import Data from './dom/data' import EventHandler from './dom/event-handler' import Manipulator from './dom/manipulator' import SelectorEngine from './dom/selector-engine' @@ -369,12 +368,7 @@ class Dropdown extends BaseComponent { // Static static dropdownInterface(element, config) { - let data = Data.get(element, DATA_KEY) - const _config = typeof config === 'object' ? config : null - - if (!data) { - data = new Dropdown(element, _config) - } + const data = Dropdown.getOrCreateInstance(element, config) if (typeof config === 'string') { if (typeof data[config] === 'undefined') { @@ -399,7 +393,7 @@ class Dropdown extends BaseComponent { const toggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE) for (let i = 0, len = toggles.length; i < len; i++) { - const context = Data.get(toggles[i], DATA_KEY) + const context = Dropdown.getInstance(toggles[i]) if (!context || context._config.autoClose === false) { continue } diff --git a/js/src/modal.js b/js/src/modal.js index e8eee3b4d..74b608303 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -209,7 +209,7 @@ class Modal extends BaseComponent { config = { ...Default, ...Manipulator.getDataAttributes(this._element), - ...config + ...(typeof config === 'object' ? config : {}) } typeCheckConfig(NAME, config, DefaultType) return config @@ -389,7 +389,7 @@ class Modal extends BaseComponent { static jQueryInterface(config, relatedTarget) { return this.each(function () { - const data = Modal.getInstance(this) || new Modal(this, typeof config === 'object' ? config : {}) + const data = Modal.getOrCreateInstance(this, config) if (typeof config !== 'string') { return @@ -430,7 +430,7 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( }) }) - const data = Modal.getInstance(target) || new Modal(target) + const data = Modal.getOrCreateInstance(target) data.toggle(this) }) diff --git a/js/src/offcanvas.js b/js/src/offcanvas.js index fed892f4c..f990ff199 100644 --- a/js/src/offcanvas.js +++ b/js/src/offcanvas.js @@ -13,7 +13,6 @@ import { typeCheckConfig } from './util/index' import { hide as scrollBarHide, reset as scrollBarReset } from './util/scrollbar' -import Data from './dom/data' import EventHandler from './dom/event-handler' import BaseComponent from './base-component' import SelectorEngine from './dom/selector-engine' @@ -211,7 +210,7 @@ class Offcanvas extends BaseComponent { static jQueryInterface(config) { return this.each(function () { - const data = Data.get(this, DATA_KEY) || new Offcanvas(this, typeof config === 'object' ? config : {}) + const data = Offcanvas.getOrCreateInstance(this, config) if (typeof config !== 'string') { return @@ -256,14 +255,13 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( Offcanvas.getInstance(allReadyOpen).hide() } - const data = Data.get(target, DATA_KEY) || new Offcanvas(target) - + const data = Offcanvas.getOrCreateInstance(target) data.toggle(this) }) -EventHandler.on(window, EVENT_LOAD_DATA_API, () => { - SelectorEngine.find(OPEN_SELECTOR).forEach(el => (Data.get(el, DATA_KEY) || new Offcanvas(el)).show()) -}) +EventHandler.on(window, EVENT_LOAD_DATA_API, () => + SelectorEngine.find(OPEN_SELECTOR).forEach(el => Offcanvas.getOrCreateInstance(el).show()) +) /** * ------------------------------------------------------------------------ diff --git a/js/src/popover.js b/js/src/popover.js index 929391392..457760c14 100644 --- a/js/src/popover.js +++ b/js/src/popover.js @@ -6,7 +6,6 @@ */ import { defineJQueryPlugin } from './util/index' -import Data from './dom/data' import SelectorEngine from './dom/selector-engine' import Tooltip from './tooltip' @@ -146,13 +145,7 @@ class Popover extends Tooltip { static jQueryInterface(config) { return this.each(function () { - let data = Data.get(this, DATA_KEY) - const _config = typeof config === 'object' ? config : null - - if (!data) { - data = new Popover(this, _config) - Data.set(this, DATA_KEY, data) - } + const data = Popover.getOrCreateInstance(this, config) if (typeof config === 'string') { if (typeof data[config] === 'undefined') { diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index 3297c45c2..b7ea2a4e5 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -270,7 +270,7 @@ class ScrollSpy extends BaseComponent { static jQueryInterface(config) { return this.each(function () { - const data = ScrollSpy.getInstance(this) || new ScrollSpy(this, typeof config === 'object' ? config : {}) + const data = ScrollSpy.getOrCreateInstance(this, config) if (typeof config !== 'string') { return diff --git a/js/src/tab.js b/js/src/tab.js index 51deb170f..6de48e4cd 100644 --- a/js/src/tab.js +++ b/js/src/tab.js @@ -11,7 +11,6 @@ import { isDisabled, reflow } from './util/index' -import Data from './dom/data' import EventHandler from './dom/event-handler' import SelectorEngine from './dom/selector-engine' import BaseComponent from './base-component' @@ -181,7 +180,7 @@ class Tab extends BaseComponent { static jQueryInterface(config) { return this.each(function () { - const data = Data.get(this, DATA_KEY) || new Tab(this) + const data = Tab.getOrCreateInstance(this) if (typeof config === 'string') { if (typeof data[config] === 'undefined') { @@ -209,7 +208,7 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( return } - const data = Data.get(this, DATA_KEY) || new Tab(this) + const data = Tab.getOrCreateInstance(this) data.show() }) diff --git a/js/src/toast.js b/js/src/toast.js index 40cd40d9a..8aeaa0148 100644 --- a/js/src/toast.js +++ b/js/src/toast.js @@ -10,7 +10,6 @@ import { reflow, typeCheckConfig } from './util/index' -import Data from './dom/data' import EventHandler from './dom/event-handler' import Manipulator from './dom/manipulator' import BaseComponent from './base-component' @@ -218,12 +217,7 @@ class Toast extends BaseComponent { static jQueryInterface(config) { return this.each(function () { - let data = Data.get(this, DATA_KEY) - const _config = typeof config === 'object' && config - - if (!data) { - data = new Toast(this, _config) - } + const data = Toast.getOrCreateInstance(this, config) if (typeof config === 'string') { if (typeof data[config] === 'undefined') { diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 2eb91965b..78b7c478b 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -722,12 +722,7 @@ class Tooltip extends BaseComponent { static jQueryInterface(config) { return this.each(function () { - let data = Data.get(this, DATA_KEY) - const _config = typeof config === 'object' && config - - if (!data) { - data = new Tooltip(this, _config) - } + const data = Tooltip.getOrCreateInstance(this, config) if (typeof config === 'string') { if (typeof data[config] === 'undefined') { -- cgit v1.2.3 From cb47b8c9640abcc19c17908475153849b9d4ad60 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Sun, 6 Jun 2021 09:26:36 +0300 Subject: Refactor scrollbar.js to be used as a Class (#33947) --- js/src/modal.js | 9 ++-- js/src/offcanvas.js | 6 +-- js/src/util/scrollbar.js | 121 ++++++++++++++++++++++++++--------------------- 3 files changed, 75 insertions(+), 61 deletions(-) (limited to 'js/src') diff --git a/js/src/modal.js b/js/src/modal.js index 74b608303..1d23b3d89 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -16,7 +16,7 @@ import { import EventHandler from './dom/event-handler' import Manipulator from './dom/manipulator' import SelectorEngine from './dom/selector-engine' -import { getWidth as getScrollBarWidth, hide as scrollBarHide, reset as scrollBarReset } from './util/scrollbar' +import ScrollBarHelper from './util/scrollbar' import BaseComponent from './base-component' import Backdrop from './util/backdrop' @@ -83,6 +83,7 @@ class Modal extends BaseComponent { this._isShown = false this._ignoreBackdropClick = false this._isTransitioning = false + this._scrollBar = new ScrollBarHelper() } // Getters @@ -120,7 +121,7 @@ class Modal extends BaseComponent { this._isTransitioning = true } - scrollBarHide() + this._scrollBar.hide() document.body.classList.add(CLASS_NAME_OPEN) @@ -301,7 +302,7 @@ class Modal extends BaseComponent { this._backdrop.hide(() => { document.body.classList.remove(CLASS_NAME_OPEN) this._resetAdjustments() - scrollBarReset() + this._scrollBar.reset() EventHandler.trigger(this._element, EVENT_HIDDEN) }) } @@ -368,7 +369,7 @@ class Modal extends BaseComponent { _adjustDialog() { const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight - const scrollbarWidth = getScrollBarWidth() + const scrollbarWidth = this._scrollBar.getWidth() const isBodyOverflowing = scrollbarWidth > 0 if ((!isBodyOverflowing && isModalOverflowing && !isRTL()) || (isBodyOverflowing && !isModalOverflowing && isRTL())) { diff --git a/js/src/offcanvas.js b/js/src/offcanvas.js index f990ff199..71e47668f 100644 --- a/js/src/offcanvas.js +++ b/js/src/offcanvas.js @@ -12,7 +12,7 @@ import { isVisible, typeCheckConfig } from './util/index' -import { hide as scrollBarHide, reset as scrollBarReset } from './util/scrollbar' +import ScrollBarHelper from './util/scrollbar' import EventHandler from './dom/event-handler' import BaseComponent from './base-component' import SelectorEngine from './dom/selector-engine' @@ -108,7 +108,7 @@ class Offcanvas extends BaseComponent { this._backdrop.show() if (!this._config.scroll) { - scrollBarHide() + new ScrollBarHelper().hide() this._enforceFocusOnElement(this._element) } @@ -148,7 +148,7 @@ class Offcanvas extends BaseComponent { this._element.style.visibility = 'hidden' if (!this._config.scroll) { - scrollBarReset() + new ScrollBarHelper().reset() } EventHandler.trigger(this._element, EVENT_HIDDEN) diff --git a/js/src/util/scrollbar.js b/js/src/util/scrollbar.js index 79a3b12c7..e23415f1d 100644 --- a/js/src/util/scrollbar.js +++ b/js/src/util/scrollbar.js @@ -7,78 +7,91 @@ import SelectorEngine from '../dom/selector-engine' import Manipulator from '../dom/manipulator' +import { isElement } from './index' const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top' const SELECTOR_STICKY_CONTENT = '.sticky-top' -const getWidth = () => { - // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes - const documentWidth = document.documentElement.clientWidth - return Math.abs(window.innerWidth - documentWidth) -} +class ScrollBarHelper { + constructor() { + this._element = document.body + } -const hide = (width = getWidth()) => { - _disableOverFlow() - // give padding to element to balances the hidden scrollbar width - _setElementAttributes('body', 'paddingRight', calculatedValue => calculatedValue + width) - // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements, to keep shown fullwidth - _setElementAttributes(SELECTOR_FIXED_CONTENT, 'paddingRight', calculatedValue => calculatedValue + width) - _setElementAttributes(SELECTOR_STICKY_CONTENT, 'marginRight', calculatedValue => calculatedValue - width) -} + getWidth() { + // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes + const documentWidth = document.documentElement.clientWidth + return Math.abs(window.innerWidth - documentWidth) + } -const _disableOverFlow = () => { - const actualValue = document.body.style.overflow - if (actualValue) { - Manipulator.setDataAttribute(document.body, 'overflow', actualValue) + hide() { + const width = this.getWidth() + this._disableOverFlow() + // give padding to element to balance the hidden scrollbar width + this._setElementAttributes(this._element, 'paddingRight', calculatedValue => calculatedValue + width) + // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth + this._setElementAttributes(SELECTOR_FIXED_CONTENT, 'paddingRight', calculatedValue => calculatedValue + width) + this._setElementAttributes(SELECTOR_STICKY_CONTENT, 'marginRight', calculatedValue => calculatedValue - width) } - document.body.style.overflow = 'hidden' -} + _disableOverFlow() { + this._saveInitialAttribute(this._element, 'overflow') + this._element.style.overflow = 'hidden' + } -const _setElementAttributes = (selector, styleProp, callback) => { - const scrollbarWidth = getWidth() - SelectorEngine.find(selector) - .forEach(element => { - if (element !== document.body && window.innerWidth > element.clientWidth + scrollbarWidth) { + _setElementAttributes(selector, styleProp, callback) { + const scrollbarWidth = this.getWidth() + const manipulationCallBack = element => { + if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) { return } - const actualValue = element.style[styleProp] - if (actualValue) { - Manipulator.setDataAttribute(element, styleProp, actualValue) - } - + this._saveInitialAttribute(element, styleProp) const calculatedValue = window.getComputedStyle(element)[styleProp] element.style[styleProp] = `${callback(Number.parseFloat(calculatedValue))}px` - }) -} + } -const reset = () => { - _resetElementAttributes('body', 'overflow') - _resetElementAttributes('body', 'paddingRight') - _resetElementAttributes(SELECTOR_FIXED_CONTENT, 'paddingRight') - _resetElementAttributes(SELECTOR_STICKY_CONTENT, 'marginRight') -} + this._applyManipulationCallback(selector, manipulationCallBack) + } + + reset() { + this._resetElementAttributes(this._element, 'overflow') + this._resetElementAttributes(this._element, 'paddingRight') + this._resetElementAttributes(SELECTOR_FIXED_CONTENT, 'paddingRight') + this._resetElementAttributes(SELECTOR_STICKY_CONTENT, 'marginRight') + } + + _saveInitialAttribute(element, styleProp) { + const actualValue = element.style[styleProp] + if (actualValue) { + Manipulator.setDataAttribute(element, styleProp, actualValue) + } + } + + _resetElementAttributes(selector, styleProp) { + const manipulationCallBack = element => { + const value = Manipulator.getDataAttribute(element, styleProp) + if (typeof value === 'undefined') { + element.style.removeProperty(styleProp) + } else { + Manipulator.removeDataAttribute(element, styleProp) + element.style[styleProp] = value + } + } -const _resetElementAttributes = (selector, styleProp) => { - SelectorEngine.find(selector).forEach(element => { - const value = Manipulator.getDataAttribute(element, styleProp) - if (typeof value === 'undefined') { - element.style.removeProperty(styleProp) + this._applyManipulationCallback(selector, manipulationCallBack) + } + + _applyManipulationCallback(selector, callBack) { + if (isElement(selector)) { + callBack(selector) } else { - Manipulator.removeDataAttribute(element, styleProp) - element.style[styleProp] = value + SelectorEngine.find(selector, this._element).forEach(callBack) } - }) -} + } -const isBodyOverflowing = () => { - return getWidth() > 0 + isOverflowing() { + return this.getWidth() > 0 + } } -export { - getWidth, - hide, - isBodyOverflowing, - reset -} +export default ScrollBarHelper -- cgit v1.2.3 From d62ba935ef2e1ee97a57b1b75090e50e86e0d140 Mon Sep 17 00:00:00 2001 From: alpadev <2838324+alpadev@users.noreply.github.com> Date: Wed, 16 Jun 2021 06:48:23 +0200 Subject: Fix carousel buttons (#34266) * test(carousel): add test to check if next/prev button work as intended * fix(carousel): merge passed config with instance config in carouselInterface --- js/src/carousel.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index a956ebc8b..fa401535a 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -498,7 +498,14 @@ class Carousel extends BaseComponent { static carouselInterface(element, config) { const data = Carousel.getOrCreateInstance(element, config) - const { _config } = data + let { _config } = data + if (typeof config === 'object') { + _config = { + ..._config, + ...config + } + } + const action = typeof config === 'string' ? config : _config.slide if (typeof config === 'number') { -- cgit v1.2.3 From 290b9ee2cde5a0182e1a53116ef626bd6c0c9cad Mon Sep 17 00:00:00 2001 From: alpadev <2838324+alpadev@users.noreply.github.com> Date: Tue, 22 Jun 2021 12:11:03 +0200 Subject: fix(carousel): arrow keys break animation if carousel sliding (#34307) --- js/src/carousel.js | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index fa401535a..3c64829db 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -59,6 +59,11 @@ const ORDER_PREV = 'prev' const DIRECTION_LEFT = 'left' const DIRECTION_RIGHT = 'right' +const KEY_TO_DIRECTION = { + [ARROW_LEFT_KEY]: DIRECTION_RIGHT, + [ARROW_RIGHT_KEY]: DIRECTION_LEFT +} + const EVENT_SLIDE = `slide${EVENT_KEY}` const EVENT_SLID = `slid${EVENT_KEY}` const EVENT_KEYDOWN = `keydown${EVENT_KEY}` @@ -134,9 +139,7 @@ class Carousel extends BaseComponent { // Public next() { - if (!this._isSliding) { - this._slide(ORDER_NEXT) - } + this._slide(ORDER_NEXT) } nextWhenVisible() { @@ -148,9 +151,7 @@ class Carousel extends BaseComponent { } prev() { - if (!this._isSliding) { - this._slide(ORDER_PREV) - } + this._slide(ORDER_PREV) } pause(event) { @@ -319,12 +320,10 @@ class Carousel extends BaseComponent { return } - if (event.key === ARROW_LEFT_KEY) { - event.preventDefault() - this._slide(DIRECTION_RIGHT) - } else if (event.key === ARROW_RIGHT_KEY) { + const direction = KEY_TO_DIRECTION[event.key] + if (direction) { event.preventDefault() - this._slide(DIRECTION_LEFT) + this._slide(direction) } } @@ -408,6 +407,10 @@ class Carousel extends BaseComponent { return } + if (this._isSliding) { + return + } + const slideEvent = this._triggerSlideEvent(nextElement, eventDirectionName) if (slideEvent.defaultPrevented) { return -- cgit v1.2.3 From 4927388197a3a2b1b041dce1be513c4cc5c39d22 Mon Sep 17 00:00:00 2001 From: alpadev <2838324+alpadev@users.noreply.github.com> Date: Tue, 22 Jun 2021 19:19:55 +0200 Subject: Register only one `DOMContentLoaded` event listener in `onDOMContentLoaded` (#34158) * refactor: reuse one DOMContentLoaded event listener in onDOMContentLoaded function Instead of adding an event listener everytime the utility function is called, cache the callbacks and execute them all at once. * refactor: drop iife for onDOMContentLoaded Co-authored-by: XhmikosR --- js/src/util/index.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'js/src') diff --git a/js/src/util/index.js b/js/src/util/index.js index 6edfaa580..064b4e943 100644 --- a/js/src/util/index.js +++ b/js/src/util/index.js @@ -201,9 +201,18 @@ const getjQuery = () => { return null } +const DOMContentLoadedCallbacks = [] + const onDOMContentLoaded = callback => { if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', callback) + // add listener on the first call when the document is in loading state + if (!DOMContentLoadedCallbacks.length) { + document.addEventListener('DOMContentLoaded', () => { + DOMContentLoadedCallbacks.forEach(callback => callback()) + }) + } + + DOMContentLoadedCallbacks.push(callback) } else { callback() } -- cgit v1.2.3 From 688bce4fa695cc360a0d084e34f029b0c192b223 Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Tue, 22 Jun 2021 21:29:16 +0300 Subject: Release v5.0.2 (#34276) * Bump version to v5.0.2. * Dist --- js/src/alert.js | 2 +- js/src/base-component.js | 4 ++-- js/src/button.js | 2 +- js/src/carousel.js | 2 +- js/src/collapse.js | 2 +- js/src/dom/data.js | 2 +- js/src/dom/event-handler.js | 2 +- js/src/dom/manipulator.js | 2 +- js/src/dom/selector-engine.js | 2 +- js/src/dropdown.js | 2 +- js/src/modal.js | 2 +- js/src/offcanvas.js | 2 +- js/src/popover.js | 2 +- js/src/scrollspy.js | 2 +- js/src/tab.js | 2 +- js/src/toast.js | 2 +- js/src/tooltip.js | 2 +- js/src/util/backdrop.js | 2 +- js/src/util/index.js | 2 +- js/src/util/sanitizer.js | 2 +- js/src/util/scrollbar.js | 2 +- 21 files changed, 22 insertions(+), 22 deletions(-) (limited to 'js/src') diff --git a/js/src/alert.js b/js/src/alert.js index e5e5e2a5d..75dbec71b 100644 --- a/js/src/alert.js +++ b/js/src/alert.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.1): alert.js + * Bootstrap (v5.0.2): alert.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/base-component.js b/js/src/base-component.js index cadd53d26..62aa4adf1 100644 --- a/js/src/base-component.js +++ b/js/src/base-component.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.1): base-component.js + * Bootstrap (v5.0.2): base-component.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ @@ -18,7 +18,7 @@ import EventHandler from './dom/event-handler' * ------------------------------------------------------------------------ */ -const VERSION = '5.0.1' +const VERSION = '5.0.2' class BaseComponent { constructor(element) { diff --git a/js/src/button.js b/js/src/button.js index c0e6b5d2b..528f6233c 100644 --- a/js/src/button.js +++ b/js/src/button.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.1): button.js + * Bootstrap (v5.0.2): button.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/carousel.js b/js/src/carousel.js index 3c64829db..fe43f53eb 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.1): carousel.js + * Bootstrap (v5.0.2): carousel.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/collapse.js b/js/src/collapse.js index 2d12ef57f..8831510df 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.1): collapse.js + * Bootstrap (v5.0.2): collapse.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/dom/data.js b/js/src/dom/data.js index 897998e43..cb88ef53d 100644 --- a/js/src/dom/data.js +++ b/js/src/dom/data.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.1): dom/data.js + * Bootstrap (v5.0.2): dom/data.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/dom/event-handler.js b/js/src/dom/event-handler.js index 6e808f402..c8303f7f2 100644 --- a/js/src/dom/event-handler.js +++ b/js/src/dom/event-handler.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.1): dom/event-handler.js + * Bootstrap (v5.0.2): dom/event-handler.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/dom/manipulator.js b/js/src/dom/manipulator.js index 2cd7098a5..113817bee 100644 --- a/js/src/dom/manipulator.js +++ b/js/src/dom/manipulator.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.1): dom/manipulator.js + * Bootstrap (v5.0.2): dom/manipulator.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/dom/selector-engine.js b/js/src/dom/selector-engine.js index 8476a4643..381e45fe8 100644 --- a/js/src/dom/selector-engine.js +++ b/js/src/dom/selector-engine.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.1): dom/selector-engine.js + * Bootstrap (v5.0.2): dom/selector-engine.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/dropdown.js b/js/src/dropdown.js index e79ac4591..681369b48 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.1): dropdown.js + * Bootstrap (v5.0.2): dropdown.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/modal.js b/js/src/modal.js index 1d23b3d89..8dac75265 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.1): modal.js + * Bootstrap (v5.0.2): modal.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/offcanvas.js b/js/src/offcanvas.js index 71e47668f..88eb8c997 100644 --- a/js/src/offcanvas.js +++ b/js/src/offcanvas.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.1): offcanvas.js + * Bootstrap (v5.0.2): offcanvas.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/popover.js b/js/src/popover.js index 457760c14..5a3b32631 100644 --- a/js/src/popover.js +++ b/js/src/popover.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.1): popover.js + * Bootstrap (v5.0.2): popover.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index b7ea2a4e5..e2c432ca3 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.1): scrollspy.js + * Bootstrap (v5.0.2): scrollspy.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/tab.js b/js/src/tab.js index 6de48e4cd..ff12efe2e 100644 --- a/js/src/tab.js +++ b/js/src/tab.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.1): tab.js + * Bootstrap (v5.0.2): tab.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/toast.js b/js/src/toast.js index 8aeaa0148..b6c9bdd79 100644 --- a/js/src/toast.js +++ b/js/src/toast.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.1): toast.js + * Bootstrap (v5.0.2): toast.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 78b7c478b..cd4a2878e 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.1): tooltip.js + * Bootstrap (v5.0.2): tooltip.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/util/backdrop.js b/js/src/util/backdrop.js index 028325d11..7ba7b4c43 100644 --- a/js/src/util/backdrop.js +++ b/js/src/util/backdrop.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.1): util/backdrop.js + * Bootstrap (v5.0.2): util/backdrop.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/util/index.js b/js/src/util/index.js index 064b4e943..7c317b016 100644 --- a/js/src/util/index.js +++ b/js/src/util/index.js @@ -2,7 +2,7 @@ import SelectorEngine from '../dom/selector-engine' /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.1): util/index.js + * Bootstrap (v5.0.2): util/index.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/util/sanitizer.js b/js/src/util/sanitizer.js index d1e55a2b1..49f66417d 100644 --- a/js/src/util/sanitizer.js +++ b/js/src/util/sanitizer.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.1): util/sanitizer.js + * Bootstrap (v5.0.2): util/sanitizer.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/util/scrollbar.js b/js/src/util/scrollbar.js index e23415f1d..fad9766ac 100644 --- a/js/src/util/scrollbar.js +++ b/js/src/util/scrollbar.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.1): util/scrollBar.js + * Bootstrap (v5.0.2): util/scrollBar.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -- cgit v1.2.3 From f94f497ad5c3108b7f95242af586592cf274b9bd Mon Sep 17 00:00:00 2001 From: GeoSot Date: Tue, 8 Jun 2021 10:38:27 +0300 Subject: ScrollSpy: Make Proper use of the SelectorEngine * avoid extra work, creating ids * simplify selectors and constrain search inside `config.target` --- js/src/scrollspy.js | 29 ++++++++--------------------- 1 file changed, 8 insertions(+), 21 deletions(-) (limited to 'js/src') diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index e2c432ca3..25fcd5ad2 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -7,9 +7,8 @@ import { defineJQueryPlugin, + getElement, getSelectorFromElement, - getUID, - isElement, typeCheckConfig } from './util/index' import EventHandler from './dom/event-handler' @@ -52,6 +51,7 @@ const SELECTOR_NAV_LIST_GROUP = '.nav, .list-group' const SELECTOR_NAV_LINKS = '.nav-link' const SELECTOR_NAV_ITEMS = '.nav-item' const SELECTOR_LIST_ITEMS = '.list-group-item' +const SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}, .${CLASS_NAME_DROPDOWN_ITEM}` const SELECTOR_DROPDOWN = '.dropdown' const SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle' @@ -69,7 +69,6 @@ class ScrollSpy extends BaseComponent { super(element) this._scrollElement = this._element.tagName === 'BODY' ? window : this._element this._config = this._getConfig(config) - this._selector = `${this._config.target} ${SELECTOR_NAV_LINKS}, ${this._config.target} ${SELECTOR_LIST_ITEMS}, ${this._config.target} .${CLASS_NAME_DROPDOWN_ITEM}` this._offsets = [] this._targets = [] this._activeTarget = null @@ -110,7 +109,7 @@ class ScrollSpy extends BaseComponent { this._targets = [] this._scrollHeight = this._getScrollHeight() - const targets = SelectorEngine.find(this._selector) + const targets = SelectorEngine.find(SELECTOR_LINK_ITEMS, this._config.target) targets.map(element => { const targetSelector = getSelectorFromElement(element) @@ -150,15 +149,7 @@ class ScrollSpy extends BaseComponent { ...(typeof config === 'object' && config ? config : {}) } - if (typeof config.target !== 'string' && isElement(config.target)) { - let { id } = config.target - if (!id) { - id = getUID(NAME) - config.target.id = id - } - - config.target = `#${id}` - } + config.target = getElement(config.target) || document.documentElement typeCheckConfig(NAME, config, DefaultType) @@ -225,20 +216,16 @@ class ScrollSpy extends BaseComponent { this._clear() - const queries = this._selector.split(',') + const queries = SELECTOR_LINK_ITEMS.split(',') .map(selector => `${selector}[data-bs-target="${target}"],${selector}[href="${target}"]`) - const link = SelectorEngine.findOne(queries.join(',')) + const link = SelectorEngine.findOne(queries.join(','), this._config.target) + link.classList.add(CLASS_NAME_ACTIVE) if (link.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) { SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE, link.closest(SELECTOR_DROPDOWN)) .classList.add(CLASS_NAME_ACTIVE) - - link.classList.add(CLASS_NAME_ACTIVE) } else { - // Set triggered link as active - link.classList.add(CLASS_NAME_ACTIVE) - SelectorEngine.parents(link, SELECTOR_NAV_LIST_GROUP) .forEach(listGroup => { // Set triggered links parents as active @@ -261,7 +248,7 @@ class ScrollSpy extends BaseComponent { } _clear() { - SelectorEngine.find(this._selector) + SelectorEngine.find(SELECTOR_LINK_ITEMS, this._config.target) .filter(node => node.classList.contains(CLASS_NAME_ACTIVE)) .forEach(node => node.classList.remove(CLASS_NAME_ACTIVE)) } -- cgit v1.2.3 From 45d26de72817b295c5f94c8426354fd5b7d0a1f9 Mon Sep 17 00:00:00 2001 From: Mark Otto Date: Fri, 25 Jun 2021 13:41:15 -0700 Subject: Variablize backdrop for modal and offcanvas --- js/src/offcanvas.js | 2 ++ js/src/util/backdrop.js | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'js/src') diff --git a/js/src/offcanvas.js b/js/src/offcanvas.js index 88eb8c997..016260437 100644 --- a/js/src/offcanvas.js +++ b/js/src/offcanvas.js @@ -45,6 +45,7 @@ const DefaultType = { } const CLASS_NAME_SHOW = 'show' +const CLASS_NAME_BACKDROP = 'offcanvas-backdrop' const OPEN_SELECTOR = '.offcanvas.show' const EVENT_SHOW = `show${EVENT_KEY}` @@ -177,6 +178,7 @@ class Offcanvas extends BaseComponent { _initializeBackDrop() { return new Backdrop({ + className: CLASS_NAME_BACKDROP, isVisible: this._config.backdrop, isAnimated: true, rootElement: this._element.parentNode, diff --git a/js/src/util/backdrop.js b/js/src/util/backdrop.js index 7ba7b4c43..fbe32445e 100644 --- a/js/src/util/backdrop.js +++ b/js/src/util/backdrop.js @@ -9,6 +9,7 @@ import EventHandler from '../dom/event-handler' import { execute, executeAfterTransition, getElement, reflow, typeCheckConfig } from './index' const Default = { + className: 'modal-backdrop', isVisible: true, // if false, we use the backdrop helper without adding any element to the dom isAnimated: false, rootElement: 'body', // give the choice to place backdrop under different elements @@ -16,13 +17,13 @@ const Default = { } const DefaultType = { + className: 'string', isVisible: 'boolean', isAnimated: 'boolean', rootElement: '(element|string)', clickCallback: '(function|null)' } const NAME = 'backdrop' -const CLASS_NAME_BACKDROP = 'modal-backdrop' const CLASS_NAME_FADE = 'fade' const CLASS_NAME_SHOW = 'show' @@ -73,7 +74,7 @@ class Backdrop { _getElement() { if (!this._element) { const backdrop = document.createElement('div') - backdrop.className = CLASS_NAME_BACKDROP + backdrop.className = this._config.className if (this._config.isAnimated) { backdrop.classList.add(CLASS_NAME_FADE) } -- cgit v1.2.3 From 70dd7f6ff51a0bd3b09b0de3640b0517b8b08f64 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Mon, 28 Jun 2021 16:34:47 +0300 Subject: Changes to Alert component to match the others (#33402) Alert.js: Refactor code to match the other components * Use this._element * Remove handleDismiss method and keep its functionality on event * Change JqueryInterface to be more generic * Correct docs to be aligned with code, and add undocumented functionality * Update alerts.md Co-authored-by: XhmikosR --- js/src/alert.js | 69 ++++++++++++++++++++++++++------------------------------- 1 file changed, 32 insertions(+), 37 deletions(-) (limited to 'js/src') diff --git a/js/src/alert.js b/js/src/alert.js index 75dbec71b..0bbe62af5 100644 --- a/js/src/alert.js +++ b/js/src/alert.js @@ -7,7 +7,8 @@ import { defineJQueryPlugin, - getElementFromSelector + getElementFromSelector, + isDisabled } from './util/index' import EventHandler from './dom/event-handler' import BaseComponent from './base-component' @@ -48,38 +49,24 @@ class Alert extends BaseComponent { // Public - close(element) { - const rootElement = element ? this._getRootElement(element) : this._element - const customEvent = this._triggerCloseEvent(rootElement) + close() { + const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE) - if (customEvent === null || customEvent.defaultPrevented) { + if (closeEvent.defaultPrevented) { return } - this._removeElement(rootElement) - } - - // Private - - _getRootElement(element) { - return getElementFromSelector(element) || element.closest(`.${CLASS_NAME_ALERT}`) - } + this._element.classList.remove(CLASS_NAME_SHOW) - _triggerCloseEvent(element) { - return EventHandler.trigger(element, EVENT_CLOSE) + const isAnimated = this._element.classList.contains(CLASS_NAME_FADE) + this._queueCallback(() => this._destroyElement(), this._element, isAnimated) } - _removeElement(element) { - element.classList.remove(CLASS_NAME_SHOW) - - const isAnimated = element.classList.contains(CLASS_NAME_FADE) - this._queueCallback(() => this._destroyElement(element), element, isAnimated) - } - - _destroyElement(element) { - element.remove() - - EventHandler.trigger(element, EVENT_CLOSED) + // Private + _destroyElement() { + this._element.remove() + EventHandler.trigger(this._element, EVENT_CLOSED) + this.dispose() } // Static @@ -88,20 +75,16 @@ class Alert extends BaseComponent { return this.each(function () { const data = Alert.getOrCreateInstance(this) - if (config === 'close') { - data[config](this) + if (typeof config !== 'string') { + return } - }) - } - static handleDismiss(alertInstance) { - return function (event) { - if (event) { - event.preventDefault() + if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { + throw new TypeError(`No method named "${config}"`) } - alertInstance.close(this) - } + data[config](this) + }) } } @@ -111,7 +94,19 @@ class Alert extends BaseComponent { * ------------------------------------------------------------------------ */ -EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DISMISS, Alert.handleDismiss(new Alert())) +EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DISMISS, function (event) { + if (['A', 'AREA'].includes(this.tagName)) { + event.preventDefault() + } + + if (isDisabled(this)) { + return + } + + const target = getElementFromSelector(this) || this.closest(`.${CLASS_NAME_ALERT}`) + const alert = Alert.getOrCreateInstance(target) + alert.close() +}) /** * ------------------------------------------------------------------------ -- cgit v1.2.3 From d314466a4dd00006ce72f46418a98cce3cfccdb2 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Tue, 29 Jun 2021 09:45:45 -0500 Subject: Accept argument of different types in the `getInstance` method (#34333) --- js/src/base-component.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'js/src') diff --git a/js/src/base-component.js b/js/src/base-component.js index 62aa4adf1..ea0ad6c24 100644 --- a/js/src/base-component.js +++ b/js/src/base-component.js @@ -48,7 +48,7 @@ class BaseComponent { /** Static */ static getInstance(element) { - return Data.get(element, this.DATA_KEY) + return Data.get(getElement(element), this.DATA_KEY) } static getOrCreateInstance(element, config = {}) { -- cgit v1.2.3 From 359ed099e5b2f82bd602f2a6c45f43af8f2c87e8 Mon Sep 17 00:00:00 2001 From: Mark Otto Date: Mon, 14 Jun 2021 23:25:11 +0300 Subject: Add horizontal collapse support --- js/src/collapse.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'js/src') diff --git a/js/src/collapse.js b/js/src/collapse.js index 8831510df..22bd31f9b 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -50,6 +50,7 @@ const CLASS_NAME_SHOW = 'show' const CLASS_NAME_COLLAPSE = 'collapse' const CLASS_NAME_COLLAPSING = 'collapsing' const CLASS_NAME_COLLAPSED = 'collapsed' +const CLASS_NAME_HORIZONTAL = 'collapse-horizontal' const WIDTH = 'width' const HEIGHT = 'height' @@ -266,7 +267,7 @@ class Collapse extends BaseComponent { } _getDimension() { - return this._element.classList.contains(WIDTH) ? WIDTH : HEIGHT + return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT } _getParent() { -- cgit v1.2.3 From e45b25e08ed13ae063a9d2f2382f6459bc564cff Mon Sep 17 00:00:00 2001 From: GeoSot Date: Wed, 14 Jul 2021 09:08:10 +0300 Subject: util.js: remove `Selector.findOne()` dependency (#34441) Co-authored-by: XhmikosR --- js/src/util/index.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'js/src') diff --git a/js/src/util/index.js b/js/src/util/index.js index 7c317b016..a1af87aa4 100644 --- a/js/src/util/index.js +++ b/js/src/util/index.js @@ -1,4 +1,3 @@ -import SelectorEngine from '../dom/selector-engine' /** * -------------------------------------------------------------------------- @@ -120,7 +119,7 @@ const getElement = obj => { } if (typeof obj === 'string' && obj.length > 0) { - return SelectorEngine.findOne(obj) + return document.querySelector(obj) } return null -- cgit v1.2.3 From dfafb9a60c5f15d341fc8992542aead014114058 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Mon, 19 Jul 2021 16:56:05 +0300 Subject: modal: change `data-dismiss` so that it can be outside of a modal using `bs-target` (#33403) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * change data-dismiss, so can be outside modal, using a bs-target * Update site/content/docs/5.0/components/modal.md Co-authored-by: Gaël Poupard --- js/src/modal.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'js/src') diff --git a/js/src/modal.js b/js/src/modal.js index 8dac75265..0e8346d6f 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -62,6 +62,7 @@ const CLASS_NAME_FADE = 'fade' const CLASS_NAME_SHOW = 'show' const CLASS_NAME_STATIC = 'modal-static' +const SELECTOR = '.modal' const SELECTOR_DIALOG = '.modal-dialog' const SELECTOR_MODAL_BODY = '.modal-body' const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="modal"]' @@ -130,8 +131,6 @@ class Modal extends BaseComponent { this._setEscapeEvent() this._setResizeEvent() - EventHandler.on(this._element, EVENT_CLICK_DISMISS, SELECTOR_DATA_DISMISS, event => this.hide(event)) - EventHandler.on(this._dialog, EVENT_MOUSEDOWN_DISMISS, () => { EventHandler.one(this._element, EVENT_MOUSEUP_DISMISS, event => { if (event.target === this._element) { @@ -436,6 +435,13 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( data.toggle(this) }) +EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_DISMISS, function (event) { + const target = getElementFromSelector(this) || this.closest(SELECTOR) + const modal = Modal.getOrCreateInstance(target) + + modal.hide(event) +}) + /** * ------------------------------------------------------------------------ * jQuery -- cgit v1.2.3 From 5541179b387ed8a1b5e457aeb47a35e6e7c62d4a Mon Sep 17 00:00:00 2001 From: GeoSot Date: Tue, 20 Jul 2021 17:20:43 +0300 Subject: Fix `Util.reflow` function and add documentation (#34543) * add documentation to reflow function * refactor to void as it should be Co-authored-by: XhmikosR --- js/src/util/index.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'js/src') diff --git a/js/src/util/index.js b/js/src/util/index.js index a1af87aa4..f81d64837 100644 --- a/js/src/util/index.js +++ b/js/src/util/index.js @@ -188,7 +188,18 @@ const findShadowRoot = element => { const noop = () => {} -const reflow = element => element.offsetHeight +/** + * Trick to restart an element's animation + * + * @param {HTMLElement} element + * @return void + * + * @see https://www.charistheo.io/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation + */ +const reflow = element => { + // eslint-disable-next-line no-unused-expressions + element.offsetHeight +} const getjQuery = () => { const { jQuery } = window -- cgit v1.2.3 From 119cfc3dfe07643a59ba58477106912fee638b02 Mon Sep 17 00:00:00 2001 From: Ryan Berliner <22206986+RyanBerliner@users.noreply.github.com> Date: Wed, 21 Jul 2021 00:49:55 -0400 Subject: Remove whitespace at beginning of util/index.js (#34545) --- js/src/util/index.js | 1 - 1 file changed, 1 deletion(-) (limited to 'js/src') diff --git a/js/src/util/index.js b/js/src/util/index.js index f81d64837..136b13cb5 100644 --- a/js/src/util/index.js +++ b/js/src/util/index.js @@ -1,4 +1,3 @@ - /** * -------------------------------------------------------------------------- * Bootstrap (v5.0.2): util/index.js -- cgit v1.2.3 From c4e189df40ffced65848d440e8e95892360445ba Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 10 Jun 2021 10:48:35 +0300 Subject: use a class private getter to decouple same methods usage --- js/src/popover.js | 14 ++------------ js/src/tooltip.js | 10 +++++++--- 2 files changed, 9 insertions(+), 15 deletions(-) (limited to 'js/src') diff --git a/js/src/popover.js b/js/src/popover.js index 5a3b32631..a08e4c4dd 100644 --- a/js/src/popover.js +++ b/js/src/popover.js @@ -19,7 +19,6 @@ const NAME = 'popover' const DATA_KEY = 'bs.popover' const EVENT_KEY = `.${DATA_KEY}` const CLASS_PREFIX = 'bs-popover' -const BSCLS_PREFIX_REGEX = new RegExp(`(^|\\s)${CLASS_PREFIX}\\S+`, 'g') const Default = { ...Tooltip.Default, @@ -124,21 +123,12 @@ class Popover extends Tooltip { // Private - _addAttachmentClass(attachment) { - this.getTipElement().classList.add(`${CLASS_PREFIX}-${this.updateAttachment(attachment)}`) - } - _getContent() { return this._element.getAttribute('data-bs-content') || this._config.content } - _cleanTipClass() { - const tip = this.getTipElement() - const tabClass = tip.getAttribute('class').match(BSCLS_PREFIX_REGEX) - if (tabClass !== null && tabClass.length > 0) { - tabClass.map(token => token.trim()) - .forEach(tClass => tip.classList.remove(tClass)) - } + _getBasicClassPrefix() { + return CLASS_PREFIX } // Static diff --git a/js/src/tooltip.js b/js/src/tooltip.js index cd4a2878e..cdc9b1e5d 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -37,7 +37,6 @@ const NAME = 'tooltip' 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 = new Set(['sanitize', 'allowList', 'sanitizeFn']) const DefaultType = { @@ -514,7 +513,7 @@ class Tooltip extends BaseComponent { } _addAttachmentClass(attachment) { - this.getTipElement().classList.add(`${CLASS_PREFIX}-${this.updateAttachment(attachment)}`) + this.getTipElement().classList.add(`${this._getBasicClassPrefix()}-${this.updateAttachment(attachment)}`) } _getAttachment(placement) { @@ -699,13 +698,18 @@ class Tooltip extends BaseComponent { _cleanTipClass() { const tip = this.getTipElement() - const tabClass = tip.getAttribute('class').match(BSCLS_PREFIX_REGEX) + const basicClassPrefixRegex = new RegExp(`(^|\\s)${this._getBasicClassPrefix()}\\S+`, 'g') + const tabClass = tip.getAttribute('class').match(basicClassPrefixRegex) if (tabClass !== null && tabClass.length > 0) { tabClass.map(token => token.trim()) .forEach(tClass => tip.classList.remove(tClass)) } } + _getBasicClassPrefix() { + return CLASS_PREFIX + } + _handlePopperPlacementChange(popperData) { const { state } = popperData -- cgit v1.2.3 From 92c7056619293a581626c37ef2c0095c6f1abceb Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 10 Jun 2021 10:52:35 +0300 Subject: `_getDelegateConfig()`: add a comment and remove an unneeded config check --- js/src/tooltip.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'js/src') diff --git a/js/src/tooltip.js b/js/src/tooltip.js index cdc9b1e5d..d69a80e27 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -685,14 +685,15 @@ class Tooltip extends BaseComponent { _getDelegateConfig() { const config = {} - if (this._config) { - for (const key in this._config) { - if (this.constructor.Default[key] !== this._config[key]) { - config[key] = this._config[key] - } + for (const key in this._config) { + if (this.constructor.Default[key] !== this._config[key]) { + config[key] = this._config[key] } } + // In the future can be replaced with: + // const keysWithDifferentValues = Object.entries(this._config).filter(entry => this.constructor.Default[entry[0]] !== this._config[entry[0]]) + // `Object.fromEntries(keysWithDifferentValues)` return config } -- cgit v1.2.3 From 3716603dbc3dc1b612c4ef6c83860195312f2532 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 10 Jun 2021 10:53:59 +0300 Subject: Use `getOrCreateInstance` on `_initializeOnDelegatedTarget` --- js/src/tooltip.js | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) (limited to 'js/src') diff --git a/js/src/tooltip.js b/js/src/tooltip.js index d69a80e27..6dc7a0350 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -439,15 +439,7 @@ class Tooltip extends BaseComponent { // Private _initializeOnDelegatedTarget(event, context) { - const dataKey = this.constructor.DATA_KEY - context = context || Data.get(event.delegateTarget, dataKey) - - if (!context) { - context = new this.constructor(event.delegateTarget, this._getDelegateConfig()) - Data.set(event.delegateTarget, dataKey, context) - } - - return context + return context || this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig()) } _getOffset() { -- cgit v1.2.3 From a97fd1cd2420a4c07589689593385484bd38fb50 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 10 Jun 2021 10:55:34 +0300 Subject: use one private method to resolve string or function --- js/src/popover.js | 2 +- js/src/tooltip.js | 21 ++++++++------------- 2 files changed, 9 insertions(+), 14 deletions(-) (limited to 'js/src') diff --git a/js/src/popover.js b/js/src/popover.js index a08e4c4dd..5ef127b8a 100644 --- a/js/src/popover.js +++ b/js/src/popover.js @@ -124,7 +124,7 @@ class Popover extends Tooltip { // Private _getContent() { - return this._element.getAttribute('data-bs-content') || this._config.content + return this._resolvePossibleFunction(this._config.content) } _getBasicClassPrefix() { diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 6dc7a0350..5746ec6ba 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -17,10 +17,7 @@ import { noop, typeCheckConfig } from './util/index' -import { - DefaultAllowlist, - sanitizeHtml -} from './util/sanitizer' +import { DefaultAllowlist, sanitizeHtml } from './util/sanitizer' import Data from './dom/data' import EventHandler from './dom/event-handler' import Manipulator from './dom/manipulator' @@ -272,7 +269,7 @@ class Tooltip extends BaseComponent { tip.classList.add(CLASS_NAME_SHOW) - const customClass = typeof this._config.customClass === 'function' ? this._config.customClass() : this._config.customClass + const customClass = this._resolvePossibleFunction(this._config.customClass) if (customClass) { tip.classList.add(...customClass.split(' ')) } @@ -413,15 +410,9 @@ class Tooltip extends BaseComponent { } getTitle() { - let title = this._element.getAttribute('data-bs-original-title') - - if (!title) { - title = typeof this._config.title === 'function' ? - this._config.title.call(this._element) : - this._config.title - } + const title = this._element.getAttribute('data-bs-original-title') || this._config.title - return title + return this._resolvePossibleFunction(title) } updateAttachment(attachment) { @@ -456,6 +447,10 @@ class Tooltip extends BaseComponent { return offset } + _resolvePossibleFunction(content) { + return typeof content === 'function' ? content.call(this._element) : content + } + _getPopperConfig(attachment) { const defaultBsPopperConfig = { placement: attachment, -- cgit v1.2.3 From 9c3ceaa25b0fcb9b5bd9ff688235e4f3b741026a Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 10 Jun 2021 10:58:41 +0300 Subject: popover: Move common code in tooltip's `getTipElement()` --- js/src/popover.js | 5 ----- js/src/tooltip.js | 5 ++++- 2 files changed, 4 insertions(+), 6 deletions(-) (limited to 'js/src') diff --git a/js/src/popover.js b/js/src/popover.js index 5ef127b8a..87df36086 100644 --- a/js/src/popover.js +++ b/js/src/popover.js @@ -51,9 +51,6 @@ const Event = { MOUSELEAVE: `mouseleave${EVENT_KEY}` } -const CLASS_NAME_FADE = 'fade' -const CLASS_NAME_SHOW = 'show' - const SELECTOR_TITLE = '.popover-header' const SELECTOR_CONTENT = '.popover-body' @@ -117,8 +114,6 @@ class Popover extends Tooltip { } this.setElementContent(SelectorEngine.findOne(SELECTOR_CONTENT, tip), content) - - tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW) } // Private diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 5746ec6ba..fa364a1e6 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -367,7 +367,10 @@ class Tooltip extends BaseComponent { const element = document.createElement('div') element.innerHTML = this._config.template - this.tip = element.children[0] + const tip = element.children[0] + tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW) + + this.tip = tip return this.tip } -- cgit v1.2.3 From da2db218edfc0746c169b567e6a13c9f8e945dda Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 10 Jun 2021 11:00:05 +0300 Subject: Use on private method to set content & cleanup template --- js/src/popover.js | 29 ++--------------------------- js/src/tooltip.js | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 29 deletions(-) (limited to 'js/src') diff --git a/js/src/popover.js b/js/src/popover.js index 87df36086..15deaafe2 100644 --- a/js/src/popover.js +++ b/js/src/popover.js @@ -6,7 +6,6 @@ */ import { defineJQueryPlugin } from './util/index' -import SelectorEngine from './dom/selector-engine' import Tooltip from './tooltip' /** @@ -85,35 +84,11 @@ class Popover extends Tooltip { return this.getTitle() || this._getContent() } - getTipElement() { - if (this.tip) { - return this.tip - } - - this.tip = super.getTipElement() - - if (!this.getTitle()) { - SelectorEngine.findOne(SELECTOR_TITLE, this.tip).remove() - } - - if (!this._getContent()) { - SelectorEngine.findOne(SELECTOR_CONTENT, this.tip).remove() - } - - return this.tip - } - setContent() { const tip = this.getTipElement() - // we use append for html objects to maintain js events - this.setElementContent(SelectorEngine.findOne(SELECTOR_TITLE, tip), this.getTitle()) - let content = this._getContent() - if (typeof content === 'function') { - content = content.call(this._element) - } - - this.setElementContent(SelectorEngine.findOne(SELECTOR_CONTENT, tip), content) + this._sanitizeAndSetContent(tip, this.getTitle(), SELECTOR_TITLE) + this._sanitizeAndSetContent(tip, this._getContent(), SELECTOR_CONTENT) } // Private diff --git a/js/src/tooltip.js b/js/src/tooltip.js index fa364a1e6..e09a53b5c 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -376,8 +376,18 @@ class Tooltip extends BaseComponent { setContent() { const tip = this.getTipElement() - this.setElementContent(SelectorEngine.findOne(SELECTOR_TOOLTIP_INNER, tip), this.getTitle()) - tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW) + this._sanitizeAndSetContent(tip, this.getTitle(), SELECTOR_TOOLTIP_INNER) + } + + _sanitizeAndSetContent(template, content, selector) { + const templateElement = SelectorEngine.findOne(selector, template) + if (!content) { + templateElement.remove() + return + } + + // we use append for html objects to maintain js events + this.setElementContent(templateElement, content) } setElementContent(element, content) { -- cgit v1.2.3 From d3c6f25fd0cc811a681f3a6b88708c04b2c9b797 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 10 Jun 2021 17:51:01 +0300 Subject: Add `isShown` method and reuse it --- js/src/dropdown.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 681369b48..d9bd903c1 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -11,12 +11,12 @@ import { defineJQueryPlugin, getElement, getElementFromSelector, + getNextActiveElement, isDisabled, isElement, - isVisible, isRTL, + isVisible, noop, - getNextActiveElement, typeCheckConfig } from './util/index' import EventHandler from './dom/event-handler' @@ -128,7 +128,7 @@ class Dropdown extends BaseComponent { return } - const isActive = this._element.classList.contains(CLASS_NAME_SHOW) + const isActive = this._isShown() if (isActive) { this.hide() @@ -139,7 +139,7 @@ class Dropdown extends BaseComponent { } show() { - if (isDisabled(this._element) || this._menu.classList.contains(CLASS_NAME_SHOW)) { + if (isDisabled(this._element) || this._isShown(this._menu)) { return } @@ -201,7 +201,7 @@ class Dropdown extends BaseComponent { } hide() { - if (isDisabled(this._element) || !this._menu.classList.contains(CLASS_NAME_SHOW)) { + if (isDisabled(this._element) || !this._isShown(this._menu)) { return } @@ -279,6 +279,10 @@ class Dropdown extends BaseComponent { return config } + _isShown(element = this._element) { + return element.classList.contains(CLASS_NAME_SHOW) + } + _getMenuElement() { return SelectorEngine.next(this._element, SELECTOR_MENU)[0] } @@ -398,7 +402,7 @@ class Dropdown extends BaseComponent { continue } - if (!context._element.classList.contains(CLASS_NAME_SHOW)) { + if (!context._isShown()) { continue } -- cgit v1.2.3 From 3533e2d637d694b5b11a65c7911c3dc45d131e42 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 10 Jun 2021 17:56:43 +0300 Subject: Merge `dropdownInterface` to `jQueryInterface` --- js/src/dropdown.js | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index d9bd903c1..09414c97b 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -371,21 +371,19 @@ class Dropdown extends BaseComponent { // Static - static dropdownInterface(element, config) { - const data = Dropdown.getOrCreateInstance(element, config) + static jQueryInterface(config) { + return this.each(function () { + const data = Dropdown.getOrCreateInstance(this, config) + + if (typeof config !== 'string') { + return + } - if (typeof config === 'string') { if (typeof data[config] === 'undefined') { throw new TypeError(`No method named "${config}"`) } data[config]() - } - } - - static jQueryInterface(config) { - return this.each(function () { - Dropdown.dropdownInterface(this, config) }) } @@ -503,7 +501,7 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, Dropdown.clearMenus) EventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus) EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) { event.preventDefault() - Dropdown.dropdownInterface(this) + Dropdown.getOrCreateInstance(this) }) /** -- cgit v1.2.3 From d130b00cad9552feb313cbd04486bd179b7295df Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 8 Jul 2021 17:42:09 +0300 Subject: simplify toggle --- js/src/dropdown.js | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 09414c97b..be561b128 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -124,18 +124,7 @@ class Dropdown extends BaseComponent { // Public toggle() { - if (isDisabled(this._element)) { - return - } - - const isActive = this._isShown() - - if (isActive) { - this.hide() - return - } - - this.show() + return this._isShown() ? this.hide() : this.show() } show() { -- cgit v1.2.3 From d01a08547def495cb1c814ffaecb9d36cad14acd Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 8 Jul 2021 17:54:15 +0300 Subject: use classList `add` instead of `toggle` on show --- js/src/dropdown.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index be561b128..1e0029a60 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -184,8 +184,8 @@ class Dropdown extends BaseComponent { this._element.focus() this._element.setAttribute('aria-expanded', true) - this._menu.classList.toggle(CLASS_NAME_SHOW) - this._element.classList.toggle(CLASS_NAME_SHOW) + this._menu.classList.add(CLASS_NAME_SHOW) + this._element.classList.add(CLASS_NAME_SHOW) EventHandler.trigger(this._element, EVENT_SHOWN, relatedTarget) } -- cgit v1.2.3 From b1dad0943f4fc8c6eb6bf58f5e38a5615dd78e5b Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 8 Jul 2021 17:59:18 +0300 Subject: handle click event in one place, remove undocumented click listener on element in case of not having the proper markup --- js/src/dropdown.js | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 1e0029a60..95752d998 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -48,7 +48,6 @@ const EVENT_HIDE = `hide${EVENT_KEY}` const EVENT_HIDDEN = `hidden${EVENT_KEY}` const EVENT_SHOW = `show${EVENT_KEY}` const EVENT_SHOWN = `shown${EVENT_KEY}` -const EVENT_CLICK = `click${EVENT_KEY}` const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` const EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY}${DATA_API_KEY}` const EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}` @@ -103,8 +102,6 @@ class Dropdown extends BaseComponent { this._config = this._getConfig(config) this._menu = this._getMenuElement() this._inNavbar = this._detectNavbar() - - this._addEventListeners() } // Getters @@ -218,13 +215,6 @@ class Dropdown extends BaseComponent { // Private - _addEventListeners() { - EventHandler.on(this._element, EVENT_CLICK, event => { - event.preventDefault() - this.toggle() - }) - } - _completeHide(relatedTarget) { const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE, relatedTarget) if (hideEvent.defaultPrevented) { @@ -490,7 +480,7 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, Dropdown.clearMenus) EventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus) EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) { event.preventDefault() - Dropdown.getOrCreateInstance(this) + Dropdown.getOrCreateInstance(this).toggle() }) /** -- cgit v1.2.3 From 99161913115fa4637cb7ab723bbae3fd552d0d09 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 8 Jul 2021 18:23:54 +0300 Subject: extract createPopper method --- js/src/dropdown.js | 50 +++++++++++++++++++++++++++----------------------- 1 file changed, 27 insertions(+), 23 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 95752d998..60a27dbb0 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -129,7 +129,6 @@ class Dropdown extends BaseComponent { return } - const parent = Dropdown.getParentFromElement(this._element) const relatedTarget = { relatedTarget: this._element } @@ -140,32 +139,12 @@ class Dropdown extends BaseComponent { return } + const parent = Dropdown.getParentFromElement(this._element) // Totally disable Popper for Dropdowns in Navbar if (this._inNavbar) { Manipulator.setDataAttribute(this._menu, 'popper', 'none') } else { - if (typeof Popper === 'undefined') { - throw new TypeError('Bootstrap\'s dropdowns require Popper (https://popper.js.org)') - } - - let referenceElement = this._element - - if (this._config.reference === 'parent') { - referenceElement = parent - } else if (isElement(this._config.reference)) { - referenceElement = getElement(this._config.reference) - } else if (typeof this._config.reference === 'object') { - referenceElement = this._config.reference - } - - const popperConfig = this._getPopperConfig() - const isDisplayStatic = popperConfig.modifiers.find(modifier => modifier.name === 'applyStyles' && modifier.enabled === false) - - this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig) - - if (isDisplayStatic) { - Manipulator.setDataAttribute(this._menu, 'popper', 'static') - } + this._createPopper(parent) } // If this is a touch-enabled device we add extra @@ -258,6 +237,31 @@ class Dropdown extends BaseComponent { return config } + _createPopper(parent) { + if (typeof Popper === 'undefined') { + throw new TypeError('Bootstrap\'s dropdowns require Popper (https://popper.js.org)') + } + + let referenceElement = this._element + + if (this._config.reference === 'parent') { + referenceElement = parent + } else if (isElement(this._config.reference)) { + referenceElement = getElement(this._config.reference) + } else if (typeof this._config.reference === 'object') { + referenceElement = this._config.reference + } + + const popperConfig = this._getPopperConfig() + const isDisplayStatic = popperConfig.modifiers.find(modifier => modifier.name === 'applyStyles' && modifier.enabled === false) + + this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig) + + if (isDisplayStatic) { + Manipulator.setDataAttribute(this._menu, 'popper', 'static') + } + } + _isShown(element = this._element) { return element.classList.contains(CLASS_NAME_SHOW) } -- cgit v1.2.3 From e85a6ed77c92ee43cf23b5c6ce479dbd0100be66 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 8 Jul 2021 18:44:58 +0300 Subject: make `dataApiKeydownHandler` to handle specific instance, avoiding extra manipulations --- js/src/dropdown.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 60a27dbb0..52c5339fa 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -449,20 +449,20 @@ class Dropdown extends BaseComponent { return } - const getToggleButton = () => this.matches(SELECTOR_DATA_TOGGLE) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] + const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] + const instance = Dropdown.getOrCreateInstance(getToggleButton) if (event.key === ESCAPE_KEY) { - getToggleButton().focus() - Dropdown.clearMenus() + instance.hide() return } if (event.key === ARROW_UP_KEY || event.key === ARROW_DOWN_KEY) { if (!isActive) { - getToggleButton().click() + instance.show() } - Dropdown.getInstance(getToggleButton())._selectMenuItem(event) + instance._selectMenuItem(event) return } -- cgit v1.2.3 From 41292a52570610dc312a775ae0491dc46f93a4fe Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 22 Jul 2021 18:13:13 +0300 Subject: Toasts: Change showing timings and classes to keep toast `display:none` by default (#33610) --- js/src/toast.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'js/src') diff --git a/js/src/toast.js b/js/src/toast.js index b6c9bdd79..9b3c0f7c8 100644 --- a/js/src/toast.js +++ b/js/src/toast.js @@ -35,7 +35,7 @@ const EVENT_SHOW = `show${EVENT_KEY}` const EVENT_SHOWN = `shown${EVENT_KEY}` const CLASS_NAME_FADE = 'fade' -const CLASS_NAME_HIDE = 'hide' +const CLASS_NAME_HIDE = 'hide' // @deprecated - kept here only for backwards compatibility const CLASS_NAME_SHOW = 'show' const CLASS_NAME_SHOWING = 'showing' @@ -101,15 +101,14 @@ class Toast extends BaseComponent { const complete = () => { this._element.classList.remove(CLASS_NAME_SHOWING) - this._element.classList.add(CLASS_NAME_SHOW) - EventHandler.trigger(this._element, EVENT_SHOWN) this._maybeScheduleHide() } - this._element.classList.remove(CLASS_NAME_HIDE) + this._element.classList.remove(CLASS_NAME_HIDE) // @deprecated reflow(this._element) + this._element.classList.add(CLASS_NAME_SHOW) this._element.classList.add(CLASS_NAME_SHOWING) this._queueCallback(complete, this._element, this._config.animation) @@ -127,11 +126,13 @@ class Toast extends BaseComponent { } const complete = () => { - this._element.classList.add(CLASS_NAME_HIDE) + this._element.classList.add(CLASS_NAME_HIDE) // @deprecated + this._element.classList.remove(CLASS_NAME_SHOWING) + this._element.classList.remove(CLASS_NAME_SHOW) EventHandler.trigger(this._element, EVENT_HIDDEN) } - this._element.classList.remove(CLASS_NAME_SHOW) + this._element.classList.add(CLASS_NAME_SHOWING) this._queueCallback(complete, this._element, this._config.animation) } -- cgit v1.2.3 From 7646f6bd33a03132e446fb060880bbf051a1639f Mon Sep 17 00:00:00 2001 From: Ryan Berliner <22206986+RyanBerliner@users.noreply.github.com> Date: Tue, 27 Jul 2021 01:01:04 -0400 Subject: Add shift-tab keyboard support for dialogs (modal & Offcanvas components) (#33865) * consolidate dialog focus trap logic * add shift-tab support to focustrap * remove redundant null check of trap element Co-authored-by: GeoSot * remove area support forom focusableChildren * fix no expectations warning in focustrap tests Co-authored-by: GeoSot Co-authored-by: XhmikosR --- js/src/dom/selector-engine.js | 17 +++++++ js/src/modal.js | 36 +++++--------- js/src/offcanvas.js | 24 +++++----- js/src/util/focustrap.js | 109 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 148 insertions(+), 38 deletions(-) create mode 100644 js/src/util/focustrap.js (limited to 'js/src') diff --git a/js/src/dom/selector-engine.js b/js/src/dom/selector-engine.js index 381e45fe8..88f924076 100644 --- a/js/src/dom/selector-engine.js +++ b/js/src/dom/selector-engine.js @@ -11,6 +11,8 @@ * ------------------------------------------------------------------------ */ +import { isDisabled, isVisible } from '../util/index' + const NODE_TEXT = 3 const SelectorEngine = { @@ -69,6 +71,21 @@ const SelectorEngine = { } return [] + }, + + focusableChildren(element) { + const focusables = [ + 'a', + 'button', + 'input', + 'textarea', + 'select', + 'details', + '[tabindex]', + '[contenteditable="true"]' + ].map(selector => `${selector}:not([tabindex^="-"])`).join(', ') + + return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el)) } } diff --git a/js/src/modal.js b/js/src/modal.js index 0e8346d6f..53a3ccfd1 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -19,6 +19,7 @@ import SelectorEngine from './dom/selector-engine' import ScrollBarHelper from './util/scrollbar' import BaseComponent from './base-component' import Backdrop from './util/backdrop' +import FocusTrap from './util/focustrap' /** * ------------------------------------------------------------------------ @@ -49,7 +50,6 @@ const EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}` const EVENT_HIDDEN = `hidden${EVENT_KEY}` const EVENT_SHOW = `show${EVENT_KEY}` const EVENT_SHOWN = `shown${EVENT_KEY}` -const EVENT_FOCUSIN = `focusin${EVENT_KEY}` const EVENT_RESIZE = `resize${EVENT_KEY}` const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}` const EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}` @@ -81,6 +81,7 @@ class Modal extends BaseComponent { this._config = this._getConfig(config) this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element) this._backdrop = this._initializeBackDrop() + this._focustrap = this._initializeFocusTrap() this._isShown = false this._ignoreBackdropClick = false this._isTransitioning = false @@ -167,7 +168,7 @@ class Modal extends BaseComponent { this._setEscapeEvent() this._setResizeEvent() - EventHandler.off(document, EVENT_FOCUSIN) + this._focustrap.deactivate() this._element.classList.remove(CLASS_NAME_SHOW) @@ -182,14 +183,8 @@ class Modal extends BaseComponent { .forEach(htmlElement => EventHandler.off(htmlElement, EVENT_KEY)) this._backdrop.dispose() + this._focustrap.deactivate() super.dispose() - - /** - * `document` has 2 events `EVENT_FOCUSIN` and `EVENT_CLICK_DATA_API` - * Do not move `document` in `htmlElements` array - * It will remove `EVENT_CLICK_DATA_API` event that should remain - */ - EventHandler.off(document, EVENT_FOCUSIN) } handleUpdate() { @@ -205,6 +200,12 @@ class Modal extends BaseComponent { }) } + _initializeFocusTrap() { + return new FocusTrap({ + trapElement: this._element + }) + } + _getConfig(config) { config = { ...Default, @@ -240,13 +241,9 @@ class Modal extends BaseComponent { this._element.classList.add(CLASS_NAME_SHOW) - if (this._config.focus) { - this._enforceFocus() - } - const transitionComplete = () => { if (this._config.focus) { - this._element.focus() + this._focustrap.activate() } this._isTransitioning = false @@ -258,17 +255,6 @@ class Modal extends BaseComponent { this._queueCallback(transitionComplete, this._dialog, isAnimated) } - _enforceFocus() { - EventHandler.off(document, EVENT_FOCUSIN) // guard against infinite focus loop - EventHandler.on(document, EVENT_FOCUSIN, event => { - if (document !== event.target && - this._element !== event.target && - !this._element.contains(event.target)) { - this._element.focus() - } - }) - } - _setEscapeEvent() { if (this._isShown) { EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => { diff --git a/js/src/offcanvas.js b/js/src/offcanvas.js index 016260437..6c563cb4f 100644 --- a/js/src/offcanvas.js +++ b/js/src/offcanvas.js @@ -18,6 +18,7 @@ import BaseComponent from './base-component' import SelectorEngine from './dom/selector-engine' import Manipulator from './dom/manipulator' import Backdrop from './util/backdrop' +import FocusTrap from './util/focustrap' /** * ------------------------------------------------------------------------ @@ -52,7 +53,6 @@ const EVENT_SHOW = `show${EVENT_KEY}` const EVENT_SHOWN = `shown${EVENT_KEY}` const EVENT_HIDE = `hide${EVENT_KEY}` const EVENT_HIDDEN = `hidden${EVENT_KEY}` -const EVENT_FOCUSIN = `focusin${EVENT_KEY}` const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}` const EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}` @@ -73,6 +73,7 @@ class Offcanvas extends BaseComponent { this._config = this._getConfig(config) this._isShown = false this._backdrop = this._initializeBackDrop() + this._focustrap = this._initializeFocusTrap() this._addEventListeners() } @@ -110,7 +111,6 @@ class Offcanvas extends BaseComponent { if (!this._config.scroll) { new ScrollBarHelper().hide() - this._enforceFocusOnElement(this._element) } this._element.removeAttribute('aria-hidden') @@ -119,6 +119,10 @@ class Offcanvas extends BaseComponent { this._element.classList.add(CLASS_NAME_SHOW) const completeCallBack = () => { + if (!this._config.scroll) { + this._focustrap.activate() + } + EventHandler.trigger(this._element, EVENT_SHOWN, { relatedTarget }) } @@ -136,7 +140,7 @@ class Offcanvas extends BaseComponent { return } - EventHandler.off(document, EVENT_FOCUSIN) + this._focustrap.deactivate() this._element.blur() this._isShown = false this._element.classList.remove(CLASS_NAME_SHOW) @@ -160,8 +164,8 @@ class Offcanvas extends BaseComponent { dispose() { this._backdrop.dispose() + this._focustrap.deactivate() super.dispose() - EventHandler.off(document, EVENT_FOCUSIN) } // Private @@ -186,16 +190,10 @@ class Offcanvas extends BaseComponent { }) } - _enforceFocusOnElement(element) { - EventHandler.off(document, EVENT_FOCUSIN) // guard against infinite focus loop - EventHandler.on(document, EVENT_FOCUSIN, event => { - if (document !== event.target && - element !== event.target && - !element.contains(event.target)) { - element.focus() - } + _initializeFocusTrap() { + return new FocusTrap({ + trapElement: this._element }) - element.focus() } _addEventListeners() { diff --git a/js/src/util/focustrap.js b/js/src/util/focustrap.js new file mode 100644 index 000000000..ab8462e23 --- /dev/null +++ b/js/src/util/focustrap.js @@ -0,0 +1,109 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v5.0.2): util/focustrap.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + +import EventHandler from '../dom/event-handler' +import SelectorEngine from '../dom/selector-engine' +import { typeCheckConfig } from './index' + +const Default = { + trapElement: null, // The element to trap focus inside of + autofocus: true +} + +const DefaultType = { + trapElement: 'element', + autofocus: 'boolean' +} + +const NAME = 'focustrap' +const DATA_KEY = 'bs.focustrap' +const EVENT_KEY = `.${DATA_KEY}` +const EVENT_FOCUSIN = `focusin${EVENT_KEY}` +const EVENT_KEYDOWN_TAB = `keydown.tab${EVENT_KEY}` + +const TAB_KEY = 'Tab' +const TAB_NAV_FORWARD = 'forward' +const TAB_NAV_BACKWARD = 'backward' + +class FocusTrap { + constructor(config) { + this._config = this._getConfig(config) + this._isActive = false + this._lastTabNavDirection = null + } + + activate() { + const { trapElement, autofocus } = this._config + + if (this._isActive) { + return + } + + if (autofocus) { + trapElement.focus() + } + + EventHandler.off(document, EVENT_KEY) // guard against infinite focus loop + EventHandler.on(document, EVENT_FOCUSIN, event => this._handleFocusin(event)) + EventHandler.on(document, EVENT_KEYDOWN_TAB, event => this._handleKeydown(event)) + + this._isActive = true + } + + deactivate() { + if (!this._isActive) { + return + } + + this._isActive = false + EventHandler.off(document, EVENT_KEY) + } + + // Private + + _handleFocusin(event) { + const { target } = event + const { trapElement } = this._config + + if ( + target === document || + target === trapElement || + trapElement.contains(target) + ) { + return + } + + const elements = SelectorEngine.focusableChildren(trapElement) + + if (elements.length === 0) { + trapElement.focus() + } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) { + elements[elements.length - 1].focus() + } else { + elements[0].focus() + } + } + + _handleKeydown(event) { + if (event.key !== TAB_KEY) { + return + } + + this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD + } + + _getConfig(config) { + config = { + ...Default, + ...(typeof config === 'object' ? config : {}) + } + typeCheckConfig(NAME, config, DefaultType) + return config + } +} + +export default FocusTrap -- cgit v1.2.3 From 047145e8086793e7c39747e70f5d74a8860c2e50 Mon Sep 17 00:00:00 2001 From: alpadev <2838324+alpadev@users.noreply.github.com> Date: Wed, 28 Jul 2021 16:23:32 +0200 Subject: Fix `Manipulator.offset()` (#33603) * test: add more test cases for Manipulator.offset() * fix: Manipulator.offset() is using obsolete properties to get scroll position Co-authored-by: XhmikosR Co-authored-by: GeoSot --- js/src/dom/manipulator.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'js/src') diff --git a/js/src/dom/manipulator.js b/js/src/dom/manipulator.js index 113817bee..a866993f0 100644 --- a/js/src/dom/manipulator.js +++ b/js/src/dom/manipulator.js @@ -64,8 +64,8 @@ const Manipulator = { const rect = element.getBoundingClientRect() return { - top: rect.top + document.body.scrollTop, - left: rect.left + document.body.scrollLeft + top: rect.top + window.pageYOffset, + left: rect.left + window.pageXOffset } }, -- cgit v1.2.3 From 4bfd8a2cbcb10610b4078cefa45756b4a96301a0 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Wed, 28 Jul 2021 17:39:32 +0300 Subject: Use a streamlined way to trigger component dismiss (#34170) * use a streamlined way to trigger component dismiss * add documentation Co-authored-by: XhmikosR --- js/src/alert.js | 28 +++------------------------- js/src/modal.js | 16 +++------------- js/src/offcanvas.js | 6 ++---- js/src/toast.js | 7 +++---- js/src/util/component-functions.js | 34 ++++++++++++++++++++++++++++++++++ 5 files changed, 45 insertions(+), 46 deletions(-) create mode 100644 js/src/util/component-functions.js (limited to 'js/src') diff --git a/js/src/alert.js b/js/src/alert.js index 0bbe62af5..66c0bee0f 100644 --- a/js/src/alert.js +++ b/js/src/alert.js @@ -5,13 +5,10 @@ * -------------------------------------------------------------------------- */ -import { - defineJQueryPlugin, - getElementFromSelector, - isDisabled -} from './util/index' +import { defineJQueryPlugin } from './util/index' import EventHandler from './dom/event-handler' import BaseComponent from './base-component' +import { enableDismissTrigger } from './util/component-functions' /** * ------------------------------------------------------------------------ @@ -22,15 +19,9 @@ import BaseComponent from './base-component' const NAME = 'alert' const DATA_KEY = 'bs.alert' const EVENT_KEY = `.${DATA_KEY}` -const DATA_API_KEY = '.data-api' - -const SELECTOR_DISMISS = '[data-bs-dismiss="alert"]' const EVENT_CLOSE = `close${EVENT_KEY}` const EVENT_CLOSED = `closed${EVENT_KEY}` -const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` - -const CLASS_NAME_ALERT = 'alert' const CLASS_NAME_FADE = 'fade' const CLASS_NAME_SHOW = 'show' @@ -94,20 +85,7 @@ class Alert extends BaseComponent { * ------------------------------------------------------------------------ */ -EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DISMISS, function (event) { - if (['A', 'AREA'].includes(this.tagName)) { - event.preventDefault() - } - - if (isDisabled(this)) { - return - } - - const target = getElementFromSelector(this) || this.closest(`.${CLASS_NAME_ALERT}`) - const alert = Alert.getOrCreateInstance(target) - alert.close() -}) - +enableDismissTrigger(Alert, 'close') /** * ------------------------------------------------------------------------ * jQuery diff --git a/js/src/modal.js b/js/src/modal.js index 53a3ccfd1..bb8d97e48 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -20,6 +20,7 @@ import ScrollBarHelper from './util/scrollbar' import BaseComponent from './base-component' import Backdrop from './util/backdrop' import FocusTrap from './util/focustrap' +import { enableDismissTrigger } from './util/component-functions' /** * ------------------------------------------------------------------------ @@ -62,11 +63,9 @@ const CLASS_NAME_FADE = 'fade' const CLASS_NAME_SHOW = 'show' const CLASS_NAME_STATIC = 'modal-static' -const SELECTOR = '.modal' const SELECTOR_DIALOG = '.modal-dialog' const SELECTOR_MODAL_BODY = '.modal-body' const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="modal"]' -const SELECTOR_DATA_DISMISS = '[data-bs-dismiss="modal"]' /** * ------------------------------------------------------------------------ @@ -143,11 +142,7 @@ class Modal extends BaseComponent { this._showBackdrop(() => this._showElement(relatedTarget)) } - hide(event) { - if (event && ['A', 'AREA'].includes(event.target.tagName)) { - event.preventDefault() - } - + hide() { if (!this._isShown || this._isTransitioning) { return } @@ -421,12 +416,7 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( data.toggle(this) }) -EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_DISMISS, function (event) { - const target = getElementFromSelector(this) || this.closest(SELECTOR) - const modal = Modal.getOrCreateInstance(target) - - modal.hide(event) -}) +enableDismissTrigger(Modal) /** * ------------------------------------------------------------------------ diff --git a/js/src/offcanvas.js b/js/src/offcanvas.js index 6c563cb4f..7725b0188 100644 --- a/js/src/offcanvas.js +++ b/js/src/offcanvas.js @@ -19,6 +19,7 @@ import SelectorEngine from './dom/selector-engine' import Manipulator from './dom/manipulator' import Backdrop from './util/backdrop' import FocusTrap from './util/focustrap' +import { enableDismissTrigger } from './util/component-functions' /** * ------------------------------------------------------------------------ @@ -54,10 +55,8 @@ const EVENT_SHOWN = `shown${EVENT_KEY}` const EVENT_HIDE = `hide${EVENT_KEY}` const EVENT_HIDDEN = `hidden${EVENT_KEY}` const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` -const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}` const EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}` -const SELECTOR_DATA_DISMISS = '[data-bs-dismiss="offcanvas"]' const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="offcanvas"]' /** @@ -197,8 +196,6 @@ class Offcanvas extends BaseComponent { } _addEventListeners() { - EventHandler.on(this._element, EVENT_CLICK_DISMISS, SELECTOR_DATA_DISMISS, () => this.hide()) - EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => { if (this._config.keyboard && event.key === ESCAPE_KEY) { this.hide() @@ -263,6 +260,7 @@ EventHandler.on(window, EVENT_LOAD_DATA_API, () => SelectorEngine.find(OPEN_SELECTOR).forEach(el => Offcanvas.getOrCreateInstance(el).show()) ) +enableDismissTrigger(Offcanvas) /** * ------------------------------------------------------------------------ * jQuery diff --git a/js/src/toast.js b/js/src/toast.js index 9b3c0f7c8..bb5f768e6 100644 --- a/js/src/toast.js +++ b/js/src/toast.js @@ -13,6 +13,7 @@ import { import EventHandler from './dom/event-handler' import Manipulator from './dom/manipulator' import BaseComponent from './base-component' +import { enableDismissTrigger } from './util/component-functions' /** * ------------------------------------------------------------------------ @@ -24,7 +25,6 @@ const NAME = 'toast' const DATA_KEY = 'bs.toast' const EVENT_KEY = `.${DATA_KEY}` -const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}` const EVENT_MOUSEOVER = `mouseover${EVENT_KEY}` const EVENT_MOUSEOUT = `mouseout${EVENT_KEY}` const EVENT_FOCUSIN = `focusin${EVENT_KEY}` @@ -51,8 +51,6 @@ const Default = { delay: 5000 } -const SELECTOR_DATA_DISMISS = '[data-bs-dismiss="toast"]' - /** * ------------------------------------------------------------------------ * Class Definition @@ -202,7 +200,6 @@ class Toast extends BaseComponent { } _setListeners() { - EventHandler.on(this._element, EVENT_CLICK_DISMISS, SELECTOR_DATA_DISMISS, () => this.hide()) EventHandler.on(this._element, EVENT_MOUSEOVER, event => this._onInteraction(event, true)) EventHandler.on(this._element, EVENT_MOUSEOUT, event => this._onInteraction(event, false)) EventHandler.on(this._element, EVENT_FOCUSIN, event => this._onInteraction(event, true)) @@ -231,6 +228,8 @@ class Toast extends BaseComponent { } } +enableDismissTrigger(Toast) + /** * ------------------------------------------------------------------------ * jQuery diff --git a/js/src/util/component-functions.js b/js/src/util/component-functions.js new file mode 100644 index 000000000..b7d180e0d --- /dev/null +++ b/js/src/util/component-functions.js @@ -0,0 +1,34 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v5.0.2): util/component-functions.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + +import EventHandler from '../dom/event-handler' +import { getElementFromSelector, isDisabled } from './index' + +const enableDismissTrigger = (component, method = 'hide') => { + const clickEvent = `click.dismiss${component.EVENT_KEY}` + const name = component.NAME + + EventHandler.on(document, clickEvent, `[data-bs-dismiss="${name}"]`, function (event) { + if (['A', 'AREA'].includes(this.tagName)) { + event.preventDefault() + } + + if (isDisabled(this)) { + return + } + + const target = getElementFromSelector(this) || this.closest(`.${name}`) + const instance = component.getOrCreateInstance(target) + + // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method + instance[method]() + }) +} + +export { + enableDismissTrigger +} -- cgit v1.2.3 From 2ad0a4a9fdbc792502c6800f6ae68c41acf4b556 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 10 Jun 2021 01:08:07 +0300 Subject: streamline `_getConfig` & interface --- js/src/collapse.js | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) (limited to 'js/src') diff --git a/js/src/collapse.js b/js/src/collapse.js index 22bd31f9b..a8651fc0a 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -259,6 +259,7 @@ class Collapse extends BaseComponent { _getConfig(config) { config = { ...Default, + ...Manipulator.getDataAttributes(this._element), ...config } config.toggle = Boolean(config.toggle) // Coerce string values @@ -311,20 +312,12 @@ class Collapse extends BaseComponent { // Static static collapseInterface(element, config) { - let data = Collapse.getInstance(element) - const _config = { - ...Default, - ...Manipulator.getDataAttributes(element), - ...(typeof config === 'object' && config ? config : {}) - } - - if (!data && _config.toggle && typeof config === 'string' && /show|hide/.test(config)) { + const _config = {} + if (typeof config === 'string' && /show|hide/.test(config)) { _config.toggle = false } - if (!data) { - data = new Collapse(element, _config) - } + const data = Collapse.getOrCreateInstance(element, _config) if (typeof config === 'string') { if (typeof data[config] === 'undefined') { -- cgit v1.2.3 From 4961ad0c63849d360edf7c23a15001ff56d58639 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 10 Jun 2021 01:10:14 +0300 Subject: Remove redundant check on `data-toggle` click. Previously, it was assumed that the trigger element would have its own separate config than the collapse element itself. --- js/src/collapse.js | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) (limited to 'js/src') diff --git a/js/src/collapse.js b/js/src/collapse.js index a8651fc0a..fcb8be6d7 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -347,26 +347,16 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( event.preventDefault() } - const triggerData = Manipulator.getDataAttributes(this) const selector = getSelectorFromElement(this) const selectorElements = SelectorEngine.find(selector) selectorElements.forEach(element => { const data = Collapse.getInstance(element) - let config if (data) { - // update parent attribute - if (data._parent === null && typeof triggerData.parent === 'string') { - data._config.parent = triggerData.parent - data._parent = data._getParent() - } - - config = 'toggle' + data.toggle() } else { - config = triggerData + Collapse.getOrCreateInstance(element) } - - Collapse.collapseInterface(element, config) }) }) -- cgit v1.2.3 From 5882d5dbe85edc5e6d9ca1cdbf1fc94a0698ae82 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 10 Jun 2021 01:16:54 +0300 Subject: Add a helper function to check for showing Also, remove the `isTransitioning()` helper. --- js/src/collapse.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'js/src') diff --git a/js/src/collapse.js b/js/src/collapse.js index fcb8be6d7..c15bffa84 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -113,7 +113,7 @@ class Collapse extends BaseComponent { // Public toggle() { - if (this._element.classList.contains(CLASS_NAME_SHOW)) { + if (this._isShown()) { this.hide() } else { this.show() @@ -121,7 +121,7 @@ class Collapse extends BaseComponent { } show() { - if (this._isTransitioning || this._element.classList.contains(CLASS_NAME_SHOW)) { + if (this._isTransitioning || this._isShown()) { return } @@ -184,16 +184,16 @@ class Collapse extends BaseComponent { }) } - this.setTransitioning(true) + this._isTransitioning = true const complete = () => { + this._isTransitioning = false + this._element.classList.remove(CLASS_NAME_COLLAPSING) this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW) this._element.style[dimension] = '' - this.setTransitioning(false) - EventHandler.trigger(this._element, EVENT_SHOWN) } @@ -205,7 +205,7 @@ class Collapse extends BaseComponent { } hide() { - if (this._isTransitioning || !this._element.classList.contains(CLASS_NAME_SHOW)) { + if (this._isTransitioning || !this._isShown()) { return } @@ -229,17 +229,17 @@ class Collapse extends BaseComponent { const trigger = this._triggerArray[i] const elem = getElementFromSelector(trigger) - if (elem && !elem.classList.contains(CLASS_NAME_SHOW)) { + if (elem && !this._isShown(elem)) { trigger.classList.add(CLASS_NAME_COLLAPSED) trigger.setAttribute('aria-expanded', false) } } } - this.setTransitioning(true) + this._isTransitioning = true const complete = () => { - this.setTransitioning(false) + this._isTransitioning = false this._element.classList.remove(CLASS_NAME_COLLAPSING) this._element.classList.add(CLASS_NAME_COLLAPSE) EventHandler.trigger(this._element, EVENT_HIDDEN) @@ -250,8 +250,8 @@ class Collapse extends BaseComponent { this._queueCallback(complete, this._element, true) } - setTransitioning(isTransitioning) { - this._isTransitioning = isTransitioning + _isShown(element = this._element) { + return element.classList.contains(CLASS_NAME_SHOW) } // Private @@ -296,7 +296,7 @@ class Collapse extends BaseComponent { return } - const isOpen = element.classList.contains(CLASS_NAME_SHOW) + const isOpen = this._isShown(element) triggerArray.forEach(elem => { if (isOpen) { -- cgit v1.2.3 From 1947ca033e0ad29ea65d069db7662632d5c66477 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 10 Jun 2021 01:42:46 +0300 Subject: Refactor internal function to use it in more cases. Also, remove a few redundant checks since we already check for it in `_addAriaAndCollapsedClass()`. --- js/src/collapse.js | 38 +++++++++++++------------------------- 1 file changed, 13 insertions(+), 25 deletions(-) (limited to 'js/src') diff --git a/js/src/collapse.js b/js/src/collapse.js index c15bffa84..3618c9ee3 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -92,7 +92,7 @@ class Collapse extends BaseComponent { this._parent = this._config.parent ? this._getParent() : null if (!this._config.parent) { - this._addAriaAndCollapsedClass(this._element, this._triggerArray) + this._addAriaAndCollapsedClass(this._triggerArray, this._isShown()) } if (this._config.toggle) { @@ -177,13 +177,7 @@ class Collapse extends BaseComponent { this._element.style[dimension] = 0 - if (this._triggerArray.length) { - this._triggerArray.forEach(element => { - element.classList.remove(CLASS_NAME_COLLAPSED) - element.setAttribute('aria-expanded', true) - }) - } - + this._addAriaAndCollapsedClass(this._triggerArray, true) this._isTransitioning = true const complete = () => { @@ -224,15 +218,12 @@ class Collapse extends BaseComponent { this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW) const triggerArrayLength = this._triggerArray.length - if (triggerArrayLength > 0) { - for (let i = 0; i < triggerArrayLength; i++) { - const trigger = this._triggerArray[i] - const elem = getElementFromSelector(trigger) - - if (elem && !this._isShown(elem)) { - trigger.classList.add(CLASS_NAME_COLLAPSED) - trigger.setAttribute('aria-expanded', false) - } + for (let i = 0; i < triggerArrayLength; i++) { + const trigger = this._triggerArray[i] + const elem = getElementFromSelector(trigger) + + if (elem && !this._isShown(elem)) { + this._addAriaAndCollapsedClass([trigger], false) } } @@ -282,22 +273,19 @@ class Collapse extends BaseComponent { .forEach(element => { const selected = getElementFromSelector(element) - this._addAriaAndCollapsedClass( - selected, - [element] - ) + if (selected) { + this._addAriaAndCollapsedClass([element], this._isShown(selected)) + } }) return parent } - _addAriaAndCollapsedClass(element, triggerArray) { - if (!element || !triggerArray.length) { + _addAriaAndCollapsedClass(triggerArray, isOpen) { + if (!triggerArray.length) { return } - const isOpen = this._isShown(element) - triggerArray.forEach(elem => { if (isOpen) { elem.classList.remove(CLASS_NAME_COLLAPSED) -- cgit v1.2.3 From 8fc4f3aaf3598426c9077530949bac4fc3653f87 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 10 Jun 2021 02:35:59 +0300 Subject: initialize variable properly --- js/src/collapse.js | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) (limited to 'js/src') diff --git a/js/src/collapse.js b/js/src/collapse.js index 3618c9ee3..58de34c1c 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -125,7 +125,7 @@ class Collapse extends BaseComponent { return } - let actives + let actives = [] let activesData if (this._parent) { @@ -137,14 +137,10 @@ class Collapse extends BaseComponent { return elem.classList.contains(CLASS_NAME_COLLAPSE) }) - - if (actives.length === 0) { - actives = null - } } const container = SelectorEngine.findOne(this._selector) - if (actives) { + if (actives.length) { const tempActiveData = actives.find(elem => container !== elem) activesData = tempActiveData ? Collapse.getInstance(tempActiveData) : null @@ -158,17 +154,15 @@ class Collapse extends BaseComponent { return } - if (actives) { - actives.forEach(elemActive => { - if (container !== elemActive) { - Collapse.collapseInterface(elemActive, 'hide') - } + actives.forEach(elemActive => { + if (container !== elemActive) { + Collapse.collapseInterface(elemActive, 'hide') + } - if (!activesData) { - Data.set(elemActive, DATA_KEY, null) - } - }) - } + if (!activesData) { + Data.set(elemActive, DATA_KEY, null) + } + }) const dimension = this._getDimension() -- cgit v1.2.3 From 7a71703fd345879d2c60bf32bd934e605bde8c4c Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 10 Jun 2021 02:36:35 +0300 Subject: Simplify check for children --- js/src/collapse.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) (limited to 'js/src') diff --git a/js/src/collapse.js b/js/src/collapse.js index 58de34c1c..21ee5ccee 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -129,14 +129,8 @@ class Collapse extends BaseComponent { let activesData if (this._parent) { - actives = SelectorEngine.find(SELECTOR_ACTIVES, this._parent) - .filter(elem => { - if (typeof this._config.parent === 'string') { - return elem.getAttribute('data-bs-parent') === this._config.parent - } - - return elem.classList.contains(CLASS_NAME_COLLAPSE) - }) + const children = SelectorEngine.find(`.${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}`, this._parent) + actives = SelectorEngine.find(SELECTOR_ACTIVES, this._parent).filter(elem => !children.includes(elem)) // remove children if greater depth } const container = SelectorEngine.findOne(this._selector) -- cgit v1.2.3 From 4c1f7bb0513287e94d550bd19747244425781f90 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 10 Jun 2021 02:41:30 +0300 Subject: remove duplicated `Selector.find` --- js/src/collapse.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'js/src') diff --git a/js/src/collapse.js b/js/src/collapse.js index 21ee5ccee..550eeeaec 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -70,10 +70,7 @@ class Collapse extends BaseComponent { this._isTransitioning = false this._config = this._getConfig(config) - this._triggerArray = SelectorEngine.find( - `${SELECTOR_DATA_TOGGLE}[href="#${this._element.id}"],` + - `${SELECTOR_DATA_TOGGLE}[data-bs-target="#${this._element.id}"]` - ) + this._triggerArray = [] const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE) -- cgit v1.2.3 From 6f17e634cecfba1478913fb91dcb22df49ef3cba Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 10 Jun 2021 02:51:51 +0300 Subject: keep parent only as element --- js/src/collapse.js | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) (limited to 'js/src') diff --git a/js/src/collapse.js b/js/src/collapse.js index 550eeeaec..74c2f4da5 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -32,12 +32,12 @@ const DATA_API_KEY = '.data-api' const Default = { toggle: true, - parent: '' + parent: null } const DefaultType = { toggle: 'boolean', - parent: '(string|element)' + parent: '(null|element)' } const EVENT_SHOW = `show${EVENT_KEY}` @@ -86,7 +86,7 @@ class Collapse extends BaseComponent { } } - this._parent = this._config.parent ? this._getParent() : null + this._initializeChildren() if (!this._config.parent) { this._addAriaAndCollapsedClass(this._triggerArray, this._isShown()) @@ -125,9 +125,9 @@ class Collapse extends BaseComponent { let actives = [] let activesData - if (this._parent) { - const children = SelectorEngine.find(`.${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}`, this._parent) - actives = SelectorEngine.find(SELECTOR_ACTIVES, this._parent).filter(elem => !children.includes(elem)) // remove children if greater depth + if (this._config.parent) { + const children = SelectorEngine.find(`.${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}`, this._config.parent) + actives = SelectorEngine.find(SELECTOR_ACTIVES, this._config.parent).filter(elem => !children.includes(elem)) // remove children if greater depth } const container = SelectorEngine.findOne(this._selector) @@ -239,6 +239,7 @@ class Collapse extends BaseComponent { ...config } config.toggle = Boolean(config.toggle) // Coerce string values + config.parent = getElement(config.parent) typeCheckConfig(NAME, config, DefaultType) return config } @@ -247,14 +248,13 @@ class Collapse extends BaseComponent { return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT } - _getParent() { - let { parent } = this._config - - parent = getElement(parent) - - const selector = `${SELECTOR_DATA_TOGGLE}[data-bs-parent="${parent}"]` + _initializeChildren() { + if (!this._config.parent) { + return + } - SelectorEngine.find(selector, parent) + const children = SelectorEngine.find(`.${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}`, this._config.parent) + SelectorEngine.find(SELECTOR_DATA_TOGGLE, this._config.parent).filter(elem => !children.includes(elem)) .forEach(element => { const selected = getElementFromSelector(element) @@ -262,8 +262,6 @@ class Collapse extends BaseComponent { this._addAriaAndCollapsedClass([element], this._isShown(selected)) } }) - - return parent } _addAriaAndCollapsedClass(triggerArray, isOpen) { -- cgit v1.2.3 From e5b606d2422334a760a7e1d574ba4ee6b4b85b0d Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 10 Jun 2021 02:54:22 +0300 Subject: simplify initialization on `data-toggle` click --- js/src/collapse.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'js/src') diff --git a/js/src/collapse.js b/js/src/collapse.js index 74c2f4da5..b238738d9 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -322,12 +322,7 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( const selectorElements = SelectorEngine.find(selector) selectorElements.forEach(element => { - const data = Collapse.getInstance(element) - if (data) { - data.toggle() - } else { - Collapse.getOrCreateInstance(element) - } + Collapse.getOrCreateInstance(element, { toggle: false }).toggle() }) }) -- cgit v1.2.3 From 2bf32ad18017f85380eb6daed3a314b88e1e851a Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 10 Jun 2021 03:00:40 +0300 Subject: transfer `interface` inside `jQueryInterface` --- js/src/collapse.js | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) (limited to 'js/src') diff --git a/js/src/collapse.js b/js/src/collapse.js index b238738d9..7c66b2eca 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -147,7 +147,7 @@ class Collapse extends BaseComponent { actives.forEach(elemActive => { if (container !== elemActive) { - Collapse.collapseInterface(elemActive, 'hide') + Collapse.getOrCreateInstance(elemActive, { toggle: false }).hide() } if (!activesData) { @@ -282,26 +282,22 @@ class Collapse extends BaseComponent { // Static - static collapseInterface(element, config) { - const _config = {} - if (typeof config === 'string' && /show|hide/.test(config)) { - _config.toggle = false - } - - const data = Collapse.getOrCreateInstance(element, _config) - - if (typeof config === 'string') { - if (typeof data[config] === 'undefined') { - throw new TypeError(`No method named "${config}"`) + static jQueryInterface(config) { + return this.each(function () { + const _config = {} + if (typeof config === 'string' && /show|hide/.test(config)) { + _config.toggle = false } - data[config]() - } - } + const data = Collapse.getOrCreateInstance(this, _config) - static jQueryInterface(config) { - return this.each(function () { - Collapse.collapseInterface(this, config) + if (typeof config === 'string') { + if (typeof data[config] === 'undefined') { + throw new TypeError(`No method named "${config}"`) + } + + data[config]() + } }) } } -- cgit v1.2.3 From 6d707f4801750f1454351d6afe93a80ce4516d1a Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Fri, 30 Jul 2021 01:23:00 +0300 Subject: Enable a few eslint-config-xo rules (#34620) * unicorn/prefer-dom-node-append * unicorn/prefer-dom-node-remove --- js/src/modal.js | 2 +- js/src/tooltip.js | 4 ++-- js/src/util/backdrop.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) (limited to 'js/src') diff --git a/js/src/modal.js b/js/src/modal.js index bb8d97e48..097ecd656 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -217,7 +217,7 @@ class Modal extends BaseComponent { if (!this._element.parentNode || this._element.parentNode.nodeType !== Node.ELEMENT_NODE) { // Don't move modal's DOM position - document.body.appendChild(this._element) + document.body.append(this._element) } this._element.style.display = 'block' diff --git a/js/src/tooltip.js b/js/src/tooltip.js index e09a53b5c..0adde623f 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -257,7 +257,7 @@ class Tooltip extends BaseComponent { Data.set(tip, this.constructor.DATA_KEY, this) if (!this._element.ownerDocument.documentElement.contains(this.tip)) { - container.appendChild(tip) + container.append(tip) EventHandler.trigger(this._element, this.constructor.Event.INSERTED) } @@ -402,7 +402,7 @@ class Tooltip extends BaseComponent { if (this._config.html) { if (content.parentNode !== element) { element.innerHTML = '' - element.appendChild(content) + element.append(content) } } else { element.textContent = content.textContent diff --git a/js/src/util/backdrop.js b/js/src/util/backdrop.js index fbe32445e..30a3686f8 100644 --- a/js/src/util/backdrop.js +++ b/js/src/util/backdrop.js @@ -102,7 +102,7 @@ class Backdrop { return } - this._config.rootElement.appendChild(this._getElement()) + this._config.rootElement.append(this._getElement()) EventHandler.on(this._getElement(), EVENT_MOUSEDOWN, () => { execute(this._config.clickCallback) -- cgit v1.2.3 From 742269a73494300c149ca916328f3762cae616cc Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Fri, 30 Jul 2021 01:32:07 +0300 Subject: tooltip: move repeated strings to constants (#34619) --- js/src/tooltip.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'js/src') diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 0adde623f..f932a9ff9 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -108,6 +108,9 @@ 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' @@ -202,7 +205,7 @@ class Tooltip extends BaseComponent { dispose() { clearTimeout(this._timeout) - EventHandler.off(this._element.closest(`.${CLASS_NAME_MODAL}`), 'hide.bs.modal', this._hideModalHandler) + EventHandler.off(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler) if (this.tip) { this.tip.remove() @@ -545,7 +548,7 @@ class Tooltip extends BaseComponent { } } - EventHandler.on(this._element.closest(`.${CLASS_NAME_MODAL}`), 'hide.bs.modal', this._hideModalHandler) + EventHandler.on(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler) if (this._config.selector) { this._config = { -- cgit v1.2.3 From a6a2d1e2df4f97486d1715a709f40f85193c4ef6 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Tue, 3 Aug 2021 11:59:33 +0300 Subject: Regression on tooltip template creation process. (#34628) * Regression on tooltip template creation process. * check if template content does not exist, or given argument is empty * call `setContent()` once. --- js/src/popover.js | 4 +--- js/src/tooltip.js | 9 ++++----- 2 files changed, 5 insertions(+), 8 deletions(-) (limited to 'js/src') diff --git a/js/src/popover.js b/js/src/popover.js index 15deaafe2..dbd67558f 100644 --- a/js/src/popover.js +++ b/js/src/popover.js @@ -84,9 +84,7 @@ class Popover extends Tooltip { return this.getTitle() || this._getContent() } - setContent() { - const tip = this.getTipElement() - + setContent(tip) { this._sanitizeAndSetContent(tip, this.getTitle(), SELECTOR_TITLE) this._sanitizeAndSetContent(tip, this._getContent(), SELECTOR_CONTENT) } diff --git a/js/src/tooltip.js b/js/src/tooltip.js index f932a9ff9..2632e46e0 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -243,8 +243,6 @@ class Tooltip extends BaseComponent { tip.setAttribute('id', tipId) this._element.setAttribute('aria-describedby', tipId) - this.setContent() - if (this._config.animation) { tip.classList.add(CLASS_NAME_FADE) } @@ -371,20 +369,21 @@ class Tooltip extends BaseComponent { element.innerHTML = this._config.template const tip = element.children[0] + this.setContent(tip) tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW) this.tip = tip return this.tip } - setContent() { - const tip = this.getTipElement() + setContent(tip) { this._sanitizeAndSetContent(tip, this.getTitle(), SELECTOR_TOOLTIP_INNER) } _sanitizeAndSetContent(template, content, selector) { const templateElement = SelectorEngine.findOne(selector, template) - if (!content) { + + if (!content && templateElement) { templateElement.remove() return } -- cgit v1.2.3 From f20fece3a8cdd0e76a42c2737524b7652bf54d26 Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Wed, 4 Aug 2021 18:41:51 +0300 Subject: Prepare v5.1.0. (#34674) --- js/src/alert.js | 2 +- js/src/base-component.js | 4 ++-- js/src/button.js | 2 +- js/src/carousel.js | 2 +- js/src/collapse.js | 2 +- js/src/dom/data.js | 2 +- js/src/dom/event-handler.js | 2 +- js/src/dom/manipulator.js | 2 +- js/src/dom/selector-engine.js | 2 +- js/src/dropdown.js | 2 +- js/src/modal.js | 2 +- js/src/offcanvas.js | 2 +- js/src/popover.js | 2 +- js/src/scrollspy.js | 2 +- js/src/tab.js | 2 +- js/src/toast.js | 2 +- js/src/tooltip.js | 2 +- js/src/util/backdrop.js | 2 +- js/src/util/component-functions.js | 2 +- js/src/util/focustrap.js | 2 +- js/src/util/index.js | 2 +- js/src/util/sanitizer.js | 2 +- js/src/util/scrollbar.js | 2 +- 23 files changed, 24 insertions(+), 24 deletions(-) (limited to 'js/src') diff --git a/js/src/alert.js b/js/src/alert.js index 66c0bee0f..601078fc6 100644 --- a/js/src/alert.js +++ b/js/src/alert.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.2): alert.js + * Bootstrap (v5.1.0): alert.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/base-component.js b/js/src/base-component.js index ea0ad6c24..e7b4112a9 100644 --- a/js/src/base-component.js +++ b/js/src/base-component.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.2): base-component.js + * Bootstrap (v5.1.0): base-component.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ @@ -18,7 +18,7 @@ import EventHandler from './dom/event-handler' * ------------------------------------------------------------------------ */ -const VERSION = '5.0.2' +const VERSION = '5.1.0' class BaseComponent { constructor(element) { diff --git a/js/src/button.js b/js/src/button.js index 528f6233c..a145fd845 100644 --- a/js/src/button.js +++ b/js/src/button.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.2): button.js + * Bootstrap (v5.1.0): button.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/carousel.js b/js/src/carousel.js index fe43f53eb..75f34e422 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.2): carousel.js + * Bootstrap (v5.1.0): carousel.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/collapse.js b/js/src/collapse.js index 7c66b2eca..f39c55b92 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.2): collapse.js + * Bootstrap (v5.1.0): collapse.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/dom/data.js b/js/src/dom/data.js index cb88ef53d..69488b510 100644 --- a/js/src/dom/data.js +++ b/js/src/dom/data.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.2): dom/data.js + * Bootstrap (v5.1.0): dom/data.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/dom/event-handler.js b/js/src/dom/event-handler.js index c8303f7f2..087afde07 100644 --- a/js/src/dom/event-handler.js +++ b/js/src/dom/event-handler.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.2): dom/event-handler.js + * Bootstrap (v5.1.0): dom/event-handler.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/dom/manipulator.js b/js/src/dom/manipulator.js index a866993f0..fdeb69ed8 100644 --- a/js/src/dom/manipulator.js +++ b/js/src/dom/manipulator.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.2): dom/manipulator.js + * Bootstrap (v5.1.0): dom/manipulator.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/dom/selector-engine.js b/js/src/dom/selector-engine.js index 88f924076..e3988a986 100644 --- a/js/src/dom/selector-engine.js +++ b/js/src/dom/selector-engine.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.2): dom/selector-engine.js + * Bootstrap (v5.1.0): dom/selector-engine.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 52c5339fa..d1f573fc8 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.2): dropdown.js + * Bootstrap (v5.1.0): dropdown.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/modal.js b/js/src/modal.js index 097ecd656..8453dbc9d 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.2): modal.js + * Bootstrap (v5.1.0): modal.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/offcanvas.js b/js/src/offcanvas.js index 7725b0188..60ce8a6c9 100644 --- a/js/src/offcanvas.js +++ b/js/src/offcanvas.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.2): offcanvas.js + * Bootstrap (v5.1.0): offcanvas.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/popover.js b/js/src/popover.js index dbd67558f..a01e2d294 100644 --- a/js/src/popover.js +++ b/js/src/popover.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.2): popover.js + * Bootstrap (v5.1.0): popover.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index 25fcd5ad2..ad170fcbd 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.2): scrollspy.js + * Bootstrap (v5.1.0): scrollspy.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/tab.js b/js/src/tab.js index ff12efe2e..e50f41456 100644 --- a/js/src/tab.js +++ b/js/src/tab.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.2): tab.js + * Bootstrap (v5.1.0): tab.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/toast.js b/js/src/toast.js index bb5f768e6..442200738 100644 --- a/js/src/toast.js +++ b/js/src/toast.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.2): toast.js + * Bootstrap (v5.1.0): toast.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 2632e46e0..288146472 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.2): tooltip.js + * Bootstrap (v5.1.0): tooltip.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/util/backdrop.js b/js/src/util/backdrop.js index 30a3686f8..0f515b3d3 100644 --- a/js/src/util/backdrop.js +++ b/js/src/util/backdrop.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.2): util/backdrop.js + * Bootstrap (v5.1.0): util/backdrop.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/util/component-functions.js b/js/src/util/component-functions.js index b7d180e0d..0737b6374 100644 --- a/js/src/util/component-functions.js +++ b/js/src/util/component-functions.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.2): util/component-functions.js + * Bootstrap (v5.1.0): util/component-functions.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/util/focustrap.js b/js/src/util/focustrap.js index ab8462e23..e35bbe6ab 100644 --- a/js/src/util/focustrap.js +++ b/js/src/util/focustrap.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.2): util/focustrap.js + * Bootstrap (v5.1.0): util/focustrap.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/util/index.js b/js/src/util/index.js index 136b13cb5..bed2534e5 100644 --- a/js/src/util/index.js +++ b/js/src/util/index.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.2): util/index.js + * Bootstrap (v5.1.0): util/index.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/util/sanitizer.js b/js/src/util/sanitizer.js index 49f66417d..d40655918 100644 --- a/js/src/util/sanitizer.js +++ b/js/src/util/sanitizer.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.2): util/sanitizer.js + * Bootstrap (v5.1.0): util/sanitizer.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/util/scrollbar.js b/js/src/util/scrollbar.js index fad9766ac..c90d82907 100644 --- a/js/src/util/scrollbar.js +++ b/js/src/util/scrollbar.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.2): util/scrollBar.js + * Bootstrap (v5.1.0): util/scrollBar.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -- cgit v1.2.3 From 418fe8113ec78e3b7251322560cd2d4a5bc6b71e Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Tue, 10 Aug 2021 17:50:32 +0300 Subject: carousel: move common checks to a function (#34621) --- js/src/carousel.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index 75f34e422..b0aed3872 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -260,8 +260,13 @@ class Carousel extends BaseComponent { } _addTouchEventListeners() { + const hasPointerPenTouch = event => { + return this._pointerEvent && + (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH) + } + const start = event => { - if (this._pointerEvent && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH)) { + if (hasPointerPenTouch(event)) { this.touchStartX = event.clientX } else if (!this._pointerEvent) { this.touchStartX = event.touches[0].clientX @@ -276,7 +281,7 @@ class Carousel extends BaseComponent { } const end = event => { - if (this._pointerEvent && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH)) { + if (hasPointerPenTouch(event)) { this.touchDeltaX = event.clientX - this.touchStartX } -- cgit v1.2.3 From 1e5e65567049cc5a7521530fbc1149f8c35b96f9 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Tue, 10 Aug 2021 17:55:34 +0300 Subject: Fix modal when is triggered by `bs-toggle`, to hide other open instances (#34701) --- js/src/modal.js | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'js/src') diff --git a/js/src/modal.js b/js/src/modal.js index 8453dbc9d..7d44c31e8 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -63,6 +63,7 @@ const CLASS_NAME_FADE = 'fade' const CLASS_NAME_SHOW = 'show' const CLASS_NAME_STATIC = 'modal-static' +const OPEN_SELECTOR = '.modal.show' const SELECTOR_DIALOG = '.modal-dialog' const SELECTOR_MODAL_BODY = '.modal-body' const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="modal"]' @@ -411,6 +412,12 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( }) }) + // avoid conflict when clicking moddal toggler while another one is open + const allReadyOpen = SelectorEngine.findOne(OPEN_SELECTOR) + if (allReadyOpen) { + Modal.getInstance(allReadyOpen).hide() + } + const data = Modal.getOrCreateInstance(target) data.toggle(this) -- cgit v1.2.3