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/util') 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 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/util/scrollbar.js | 70 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 js/src/util/scrollbar.js (limited to 'js/src/util') 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 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/util/index.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'js/src/util') 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/util') 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 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/util/index.js | 2 +- js/src/util/sanitizer.js | 2 +- js/src/util/scrollbar.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'js/src/util') 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 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/util/index.js | 6 ++---- js/src/util/scrollbar.js | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) (limited to 'js/src/util') 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 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/util/scrollbar.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'js/src/util') 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/util/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'js/src/util') 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 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/util/backdrop.js | 123 ++++++++++++++++++++++++++++++++++++++++++++++++ js/src/util/index.js | 9 +++- 2 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 js/src/util/backdrop.js (limited to 'js/src/util') 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 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/util/backdrop.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'js/src/util') 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 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/util') 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/util/backdrop.js | 2 +- js/src/util/index.js | 2 +- js/src/util/sanitizer.js | 2 +- js/src/util/scrollbar.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) (limited to 'js/src/util') 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/util') 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 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/util/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'js/src/util') 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 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/util/index.js | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) (limited to 'js/src/util') 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/util/backdrop.js | 2 +- js/src/util/index.js | 2 +- js/src/util/sanitizer.js | 2 +- js/src/util/scrollbar.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) (limited to 'js/src/util') 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 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/util/index.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) (limited to 'js/src/util') 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 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/util') 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/util') 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/util') 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/util/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'js/src/util') 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 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/util/backdrop.js | 6 +----- js/src/util/sanitizer.js | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) (limited to 'js/src/util') 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/util') 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/util/backdrop.js | 11 ++--------- js/src/util/index.js | 51 ++++++++++++++++++++++++++++++------------------- 2 files changed, 33 insertions(+), 29 deletions(-) (limited to 'js/src/util') 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 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/util/scrollbar.js | 121 ++++++++++++++++++++++++++--------------------- 1 file changed, 67 insertions(+), 54 deletions(-) (limited to 'js/src/util') 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 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/util') 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/util/backdrop.js | 2 +- js/src/util/index.js | 2 +- js/src/util/sanitizer.js | 2 +- js/src/util/scrollbar.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) (limited to 'js/src/util') 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 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/util/backdrop.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'js/src/util') 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 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/util') 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 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/util') 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/util') 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 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/util/focustrap.js | 109 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 js/src/util/focustrap.js (limited to 'js/src/util') 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 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/util/component-functions.js | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 js/src/util/component-functions.js (limited to 'js/src/util') 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 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/util/backdrop.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'js/src/util') 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 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/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 +- 6 files changed, 6 insertions(+), 6 deletions(-) (limited to 'js/src/util') 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