diff options
| author | GeoSot <[email protected]> | 2021-04-11 09:37:59 +0300 |
|---|---|---|
| committer | GitHub <[email protected]> | 2021-04-11 09:37:59 +0300 |
| commit | 7b7f4a5ced176ae3d7d9d16583795245cb9c7df3 (patch) | |
| tree | 820815a7921dc0c4787f19187b4ca278c7bd7fbe /js/src | |
| parent | 2d2f5b3dfd901bca22133ae25fdcce7afb4042c7 (diff) | |
| download | bootstrap-7b7f4a5ced176ae3d7d9d16583795245cb9c7df3.tar.xz bootstrap-7b7f4a5ced176ae3d7d9d16583795245cb9c7df3.zip | |
Decouple Modal's scrollbar functionality (#33245)
Diffstat (limited to 'js/src')
| -rw-r--r-- | js/src/modal.js | 82 | ||||
| -rw-r--r-- | js/src/util/scrollbar.js | 5 |
2 files changed, 14 insertions, 73 deletions
diff --git a/js/src/modal.js b/js/src/modal.js index b2a2e80eb..dea90ec0a 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -18,6 +18,7 @@ import { import EventHandler from './dom/event-handler' import Manipulator from './dom/manipulator' import SelectorEngine from './dom/selector-engine' +import { getWidth as getScrollBarWidth, hide as scrollBarHide, reset as scrollBarReset } from './util/scrollbar' import BaseComponent from './base-component' /** @@ -57,7 +58,6 @@ const EVENT_MOUSEUP_DISMISS = `mouseup.dismiss${EVENT_KEY}` const EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY}` const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` -const CLASS_NAME_SCROLLBAR_MEASURER = 'modal-scrollbar-measure' const CLASS_NAME_BACKDROP = 'modal-backdrop' const CLASS_NAME_OPEN = 'modal-open' const CLASS_NAME_FADE = 'fade' @@ -68,8 +68,6 @@ const SELECTOR_DIALOG = '.modal-dialog' const SELECTOR_MODAL_BODY = '.modal-body' const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="modal"]' const SELECTOR_DATA_DISMISS = '[data-bs-dismiss="modal"]' -const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top' -const SELECTOR_STICKY_CONTENT = '.sticky-top' /** * ------------------------------------------------------------------------ @@ -85,10 +83,8 @@ class Modal extends BaseComponent { this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element) this._backdrop = null this._isShown = false - this._isBodyOverflowing = false this._ignoreBackdropClick = false this._isTransitioning = false - this._scrollbarWidth = 0 } // Getters @@ -126,8 +122,9 @@ class Modal extends BaseComponent { this._isShown = true - this._checkScrollbar() - this._setScrollbar() + scrollBarHide() + + document.body.classList.add(CLASS_NAME_OPEN) this._adjustDialog() @@ -206,10 +203,8 @@ class Modal extends BaseComponent { this._dialog = null this._backdrop = null this._isShown = null - this._isBodyOverflowing = null this._ignoreBackdropClick = null this._isTransitioning = null - this._scrollbarWidth = null } handleUpdate() { @@ -321,7 +316,7 @@ class Modal extends BaseComponent { this._showBackdrop(() => { document.body.classList.remove(CLASS_NAME_OPEN) this._resetAdjustments() - this._resetScrollbar() + scrollBarReset() EventHandler.trigger(this._element, EVENT_HIDDEN) }) } @@ -433,13 +428,15 @@ class Modal extends BaseComponent { _adjustDialog() { const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight + const scrollbarWidth = getScrollBarWidth() + const isBodyOverflowing = scrollbarWidth > 0 - if ((!this._isBodyOverflowing && isModalOverflowing && !isRTL()) || (this._isBodyOverflowing && !isModalOverflowing && isRTL())) { - this._element.style.paddingLeft = `${this._scrollbarWidth}px` + if ((!isBodyOverflowing && isModalOverflowing && !isRTL()) || (isBodyOverflowing && !isModalOverflowing && isRTL())) { + this._element.style.paddingLeft = `${scrollbarWidth}px` } - if ((this._isBodyOverflowing && !isModalOverflowing && !isRTL()) || (!this._isBodyOverflowing && isModalOverflowing && isRTL())) { - this._element.style.paddingRight = `${this._scrollbarWidth}px` + if ((isBodyOverflowing && !isModalOverflowing && !isRTL()) || (!isBodyOverflowing && isModalOverflowing && isRTL())) { + this._element.style.paddingRight = `${scrollbarWidth}px` } } @@ -448,63 +445,6 @@ class Modal extends BaseComponent { this._element.style.paddingRight = '' } - _checkScrollbar() { - const rect = document.body.getBoundingClientRect() - this._isBodyOverflowing = Math.round(rect.left + rect.right) < window.innerWidth - this._scrollbarWidth = this._getScrollbarWidth() - } - - _setScrollbar() { - if (this._isBodyOverflowing) { - this._setElementAttributes(SELECTOR_FIXED_CONTENT, 'paddingRight', calculatedValue => calculatedValue + this._scrollbarWidth) - this._setElementAttributes(SELECTOR_STICKY_CONTENT, 'marginRight', calculatedValue => calculatedValue - this._scrollbarWidth) - this._setElementAttributes('body', 'paddingRight', calculatedValue => calculatedValue + this._scrollbarWidth) - } - - document.body.classList.add(CLASS_NAME_OPEN) - } - - _setElementAttributes(selector, styleProp, callback) { - SelectorEngine.find(selector) - .forEach(element => { - if (element !== document.body && window.innerWidth > element.clientWidth + this._scrollbarWidth) { - return - } - - const actualValue = element.style[styleProp] - const calculatedValue = window.getComputedStyle(element)[styleProp] - Manipulator.setDataAttribute(element, styleProp, actualValue) - element.style[styleProp] = `${callback(Number.parseFloat(calculatedValue))}px` - }) - } - - _resetScrollbar() { - this._resetElementAttributes(SELECTOR_FIXED_CONTENT, 'paddingRight') - this._resetElementAttributes(SELECTOR_STICKY_CONTENT, 'marginRight') - this._resetElementAttributes('body', 'paddingRight') - } - - _resetElementAttributes(selector, styleProp) { - SelectorEngine.find(selector).forEach(element => { - const value = Manipulator.getDataAttribute(element, styleProp) - if (typeof value === 'undefined' && element === document.body) { - element.style[styleProp] = '' - } else { - Manipulator.removeDataAttribute(element, styleProp) - element.style[styleProp] = value - } - }) - } - - _getScrollbarWidth() { // thx d.walsh - const scrollDiv = document.createElement('div') - scrollDiv.className = CLASS_NAME_SCROLLBAR_MEASURER - document.body.appendChild(scrollDiv) - const scrollbarWidth = scrollDiv.getBoundingClientRect().width - scrollDiv.clientWidth - document.body.removeChild(scrollDiv) - return scrollbarWidth - } - // Static static jQueryInterface(config, relatedTarget) { diff --git a/js/src/util/scrollbar.js b/js/src/util/scrollbar.js index e63a66bf2..31b614375 100644 --- a/js/src/util/scrollbar.js +++ b/js/src/util/scrollbar.js @@ -8,7 +8,7 @@ import SelectorEngine from '../dom/selector-engine' import Manipulator from '../dom/manipulator' -const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed' +const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top' const SELECTOR_STICKY_CONTENT = '.sticky-top' const getWidth = () => { @@ -19,6 +19,7 @@ const getWidth = () => { const hide = (width = getWidth()) => { document.body.style.overflow = 'hidden' + // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements, to keep shown fullwidth _setElementAttributes(SELECTOR_FIXED_CONTENT, 'paddingRight', calculatedValue => calculatedValue + width) _setElementAttributes(SELECTOR_STICKY_CONTENT, 'marginRight', calculatedValue => calculatedValue - width) _setElementAttributes('body', 'paddingRight', calculatedValue => calculatedValue + width) @@ -49,7 +50,7 @@ const reset = () => { const _resetElementAttributes = (selector, styleProp) => { SelectorEngine.find(selector).forEach(element => { const value = Manipulator.getDataAttribute(element, styleProp) - if (typeof value === 'undefined' && element === document.body) { + if (typeof value === 'undefined') { element.style.removeProperty(styleProp) } else { Manipulator.removeDataAttribute(element, styleProp) |
