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