aboutsummaryrefslogtreecommitdiff
path: root/js/src/modal.js
diff options
context:
space:
mode:
authorPatrick H. Lauke <[email protected]>2021-05-04 12:46:06 +0100
committerGitHub <[email protected]>2021-05-04 12:46:06 +0100
commit8865a8ab1c7157ab81bf49afa62b75f36daee46d (patch)
tree97ef78f2ea8e07aab50014176d061fe3c1d49134 /js/src/modal.js
parent018ee6a3b50b958ddb49657086cd9168abf5a485 (diff)
parent7ea6578773cb1b7f5cfb8fb41321b3fa10349daf (diff)
downloadbootstrap-jo-docs-thanks-page.tar.xz
bootstrap-jo-docs-thanks-page.zip
Merge branch 'main' into jo-docs-thanks-pagejo-docs-thanks-page
Diffstat (limited to 'js/src/modal.js')
-rw-r--r--js/src/modal.js286
1 files changed, 67 insertions, 219 deletions
diff --git a/js/src/modal.js b/js/src/modal.js
index 94bf95f8a..fabb151cb 100644
--- a/js/src/modal.js
+++ b/js/src/modal.js
@@ -1,27 +1,26 @@
/**
* --------------------------------------------------------------------------
- * Bootstrap (v5.0.0-alpha3): modal.js
+ * Bootstrap (v5.0.0-beta3): modal.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
import {
- getjQuery,
- onDOMContentLoaded,
- TRANSITION_END,
+ defineJQueryPlugin,
emulateTransitionEnd,
getElementFromSelector,
getTransitionDurationFromElement,
- isVisible,
isRTL,
+ isVisible,
reflow,
typeCheckConfig
} from './util/index'
-import Data from './dom/data'
import EventHandler from './dom/event-handler'
import Manipulator from './dom/manipulator'
import SelectorEngine from './dom/selector-engine'
+import { getWidth as getScrollBarWidth, hide as scrollBarHide, reset as scrollBarReset } from './util/scrollbar'
import BaseComponent from './base-component'
+import Backdrop from './util/backdrop'
/**
* ------------------------------------------------------------------------
@@ -60,8 +59,6 @@ const EVENT_MOUSEUP_DISMISS = `mouseup.dismiss${EVENT_KEY}`
const EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY}`
const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`
-const CLASS_NAME_SCROLLBAR_MEASURER = 'modal-scrollbar-measure'
-const CLASS_NAME_BACKDROP = 'modal-backdrop'
const CLASS_NAME_OPEN = 'modal-open'
const CLASS_NAME_FADE = 'fade'
const CLASS_NAME_SHOW = 'show'
@@ -71,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,13 +80,11 @@ class Modal extends BaseComponent {
super(element)
this._config = this._getConfig(config)
- this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, element)
- this._backdrop = null
+ this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element)
+ this._backdrop = this._initializeBackDrop()
this._isShown = false
- this._isBodyOverflowing = false
this._ignoreBackdropClick = false
this._isTransitioning = false
- this._scrollbarWidth = 0
}
// Getters
@@ -115,7 +108,7 @@ class Modal extends BaseComponent {
return
}
- if (this._element.classList.contains(CLASS_NAME_FADE)) {
+ if (this._isAnimated()) {
this._isTransitioning = true
}
@@ -129,8 +122,9 @@ class Modal extends BaseComponent {
this._isShown = true
- this._checkScrollbar()
- this._setScrollbar()
+ scrollBarHide()
+
+ document.body.classList.add(CLASS_NAME_OPEN)
this._adjustDialog()
@@ -166,9 +160,9 @@ class Modal extends BaseComponent {
}
this._isShown = false
- const transition = this._element.classList.contains(CLASS_NAME_FADE)
+ const isAnimated = this._isAnimated()
- if (transition) {
+ if (isAnimated) {
this._isTransitioning = true
}
@@ -182,10 +176,10 @@ class Modal extends BaseComponent {
EventHandler.off(this._element, EVENT_CLICK_DISMISS)
EventHandler.off(this._dialog, EVENT_MOUSEDOWN_DISMISS)
- if (transition) {
+ if (isAnimated) {
const transitionDuration = getTransitionDurationFromElement(this._element)
- EventHandler.one(this._element, TRANSITION_END, event => this._hideModal(event))
+ EventHandler.one(this._element, 'transitionend', event => this._hideModal(event))
emulateTransitionEnd(this._element, transitionDuration)
} else {
this._hideModal()
@@ -193,7 +187,7 @@ class Modal extends BaseComponent {
}
dispose() {
- [window, this._element, this._dialog]
+ [window, this._dialog]
.forEach(htmlElement => EventHandler.off(htmlElement, EVENT_KEY))
super.dispose()
@@ -207,12 +201,11 @@ class Modal extends BaseComponent {
this._config = null
this._dialog = null
+ this._backdrop.dispose()
this._backdrop = null
this._isShown = null
- this._isBodyOverflowing = null
this._ignoreBackdropClick = null
this._isTransitioning = null
- this._scrollbarWidth = null
}
handleUpdate() {
@@ -221,9 +214,17 @@ class Modal extends BaseComponent {
// Private
+ _initializeBackDrop() {
+ return new Backdrop({
+ isVisible: Boolean(this._config.backdrop), // 'static' option will be translated to true, and booleans will keep their value
+ isAnimated: this._isAnimated()
+ })
+ }
+
_getConfig(config) {
config = {
...Default,
+ ...Manipulator.getDataAttributes(this._element),
...config
}
typeCheckConfig(NAME, config, DefaultType)
@@ -231,7 +232,7 @@ class Modal extends BaseComponent {
}
_showElement(relatedTarget) {
- const transition = this._element.classList.contains(CLASS_NAME_FADE)
+ const isAnimated = this._isAnimated()
const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog)
if (!this._element.parentNode || this._element.parentNode.nodeType !== Node.ELEMENT_NODE) {
@@ -249,7 +250,7 @@ class Modal extends BaseComponent {
modalBody.scrollTop = 0
}
- if (transition) {
+ if (isAnimated) {
reflow(this._element)
}
@@ -270,10 +271,10 @@ class Modal extends BaseComponent {
})
}
- if (transition) {
+ if (isAnimated) {
const transitionDuration = getTransitionDurationFromElement(this._dialog)
- EventHandler.one(this._dialog, TRANSITION_END, transitionComplete)
+ EventHandler.one(this._dialog, 'transitionend', transitionComplete)
emulateTransitionEnd(this._dialog, transitionDuration)
} else {
transitionComplete()
@@ -320,84 +321,37 @@ class Modal extends BaseComponent {
this._element.removeAttribute('aria-modal')
this._element.removeAttribute('role')
this._isTransitioning = false
- this._showBackdrop(() => {
+ this._backdrop.hide(() => {
document.body.classList.remove(CLASS_NAME_OPEN)
this._resetAdjustments()
- this._resetScrollbar()
+ scrollBarReset()
EventHandler.trigger(this._element, EVENT_HIDDEN)
})
}
- _removeBackdrop() {
- this._backdrop.parentNode.removeChild(this._backdrop)
- this._backdrop = null
- }
-
_showBackdrop(callback) {
- const animate = this._element.classList.contains(CLASS_NAME_FADE) ?
- CLASS_NAME_FADE :
- ''
-
- if (this._isShown && this._config.backdrop) {
- this._backdrop = document.createElement('div')
- this._backdrop.className = CLASS_NAME_BACKDROP
-
- if (animate) {
- this._backdrop.classList.add(animate)
- }
-
- document.body.appendChild(this._backdrop)
-
- EventHandler.on(this._element, EVENT_CLICK_DISMISS, event => {
- if (this._ignoreBackdropClick) {
- this._ignoreBackdropClick = false
- return
- }
-
- if (event.target !== event.currentTarget) {
- return
- }
-
- if (this._config.backdrop === 'static') {
- this._triggerBackdropTransition()
- } else {
- this.hide()
- }
- })
-
- if (animate) {
- reflow(this._backdrop)
+ EventHandler.on(this._element, EVENT_CLICK_DISMISS, event => {
+ if (this._ignoreBackdropClick) {
+ this._ignoreBackdropClick = false
+ return
}
- this._backdrop.classList.add(CLASS_NAME_SHOW)
-
- if (!animate) {
- callback()
+ if (event.target !== event.currentTarget) {
return
}
- const backdropTransitionDuration = getTransitionDurationFromElement(this._backdrop)
-
- EventHandler.one(this._backdrop, TRANSITION_END, callback)
- emulateTransitionEnd(this._backdrop, backdropTransitionDuration)
- } else if (!this._isShown && this._backdrop) {
- this._backdrop.classList.remove(CLASS_NAME_SHOW)
-
- const callbackRemove = () => {
- this._removeBackdrop()
- callback()
+ if (this._config.backdrop === true) {
+ this.hide()
+ } else if (this._config.backdrop === 'static') {
+ this._triggerBackdropTransition()
}
+ })
- if (this._element.classList.contains(CLASS_NAME_FADE)) {
- const backdropTransitionDuration = getTransitionDurationFromElement(this._backdrop)
- EventHandler.one(this._backdrop, TRANSITION_END, callbackRemove)
- emulateTransitionEnd(this._backdrop, backdropTransitionDuration)
- } else {
- callbackRemove()
- }
- } else {
- callback()
- }
+ this._backdrop.show(callback)
+ }
+
+ _isAnimated() {
+ return this._element.classList.contains(CLASS_NAME_FADE)
}
_triggerBackdropTransition() {
@@ -414,11 +368,11 @@ class Modal extends BaseComponent {
this._element.classList.add(CLASS_NAME_STATIC)
const modalTransitionDuration = getTransitionDurationFromElement(this._dialog)
- EventHandler.off(this._element, TRANSITION_END)
- EventHandler.one(this._element, TRANSITION_END, () => {
+ EventHandler.off(this._element, 'transitionend')
+ EventHandler.one(this._element, 'transitionend', () => {
this._element.classList.remove(CLASS_NAME_STATIC)
if (!isModalOverflowing) {
- EventHandler.one(this._element, TRANSITION_END, () => {
+ EventHandler.one(this._element, 'transitionend', () => {
this._element.style.overflowY = ''
})
emulateTransitionEnd(this._element, modalTransitionDuration)
@@ -433,15 +387,16 @@ class Modal extends BaseComponent {
// ----------------------------------------------------------------------
_adjustDialog() {
- const isModalOverflowing =
- this._element.scrollHeight > document.documentElement.clientHeight
+ 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`
}
}
@@ -450,108 +405,21 @@ 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) {
- // Note: DOMNode.style.paddingRight returns the actual value or '' if not set
- // while $(DOMNode).css('padding-right') returns the calculated value or 0 if not set
-
- // Adjust fixed content padding
- SelectorEngine.find(SELECTOR_FIXED_CONTENT)
- .forEach(element => {
- const actualPadding = element.style.paddingRight
- const calculatedPadding = window.getComputedStyle(element)['padding-right']
- Manipulator.setDataAttribute(element, 'padding-right', actualPadding)
- element.style.paddingRight = `${Number.parseFloat(calculatedPadding) + this._scrollbarWidth}px`
- })
-
- // Adjust sticky content margin
- SelectorEngine.find(SELECTOR_STICKY_CONTENT)
- .forEach(element => {
- const actualMargin = element.style.marginRight
- const calculatedMargin = window.getComputedStyle(element)['margin-right']
- Manipulator.setDataAttribute(element, 'margin-right', actualMargin)
- element.style.marginRight = `${Number.parseFloat(calculatedMargin) - this._scrollbarWidth}px`
- })
-
- // Adjust body padding
- const actualPadding = document.body.style.paddingRight
- const calculatedPadding = window.getComputedStyle(document.body)['padding-right']
-
- Manipulator.setDataAttribute(document.body, 'padding-right', actualPadding)
- document.body.style.paddingRight = `${Number.parseFloat(calculatedPadding) + this._scrollbarWidth}px`
- }
-
- document.body.classList.add(CLASS_NAME_OPEN)
- }
-
- _resetScrollbar() {
- // Restore fixed content padding
- SelectorEngine.find(SELECTOR_FIXED_CONTENT)
- .forEach(element => {
- const padding = Manipulator.getDataAttribute(element, 'padding-right')
- if (typeof padding !== 'undefined') {
- Manipulator.removeDataAttribute(element, 'padding-right')
- element.style.paddingRight = padding
- }
- })
-
- // Restore sticky content and navbar-toggler margin
- SelectorEngine.find(`${SELECTOR_STICKY_CONTENT}`)
- .forEach(element => {
- const margin = Manipulator.getDataAttribute(element, 'margin-right')
- if (typeof margin !== 'undefined') {
- Manipulator.removeDataAttribute(element, 'margin-right')
- element.style.marginRight = margin
- }
- })
-
- // Restore body padding
- const padding = Manipulator.getDataAttribute(document.body, 'padding-right')
- if (typeof padding === 'undefined') {
- document.body.style.paddingRight = ''
- } else {
- Manipulator.removeDataAttribute(document.body, 'padding-right')
- document.body.style.paddingRight = padding
- }
- }
-
- _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) {
return this.each(function () {
- let data = Data.getData(this, DATA_KEY)
- const _config = {
- ...Default,
- ...Manipulator.getDataAttributes(this),
- ...(typeof config === 'object' && config ? config : {})
- }
+ const data = Modal.getInstance(this) || new Modal(this, typeof config === 'object' ? config : {})
- if (!data) {
- data = new Modal(this, _config)
+ if (typeof config !== 'string') {
+ return
}
- if (typeof config === 'string') {
- if (typeof data[config] === 'undefined') {
- throw new TypeError(`No method named "${config}"`)
- }
-
- data[config](relatedTarget)
+ if (typeof data[config] === 'undefined') {
+ throw new TypeError(`No method named "${config}"`)
}
+
+ data[config](relatedTarget)
})
}
}
@@ -565,7 +433,7 @@ class Modal extends BaseComponent {
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
const target = getElementFromSelector(this)
- if (this.tagName === 'A' || this.tagName === 'AREA') {
+ if (['A', 'AREA'].includes(this.tagName)) {
event.preventDefault()
}
@@ -582,17 +450,9 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (
})
})
- let data = Data.getData(target, DATA_KEY)
- if (!data) {
- const config = {
- ...Manipulator.getDataAttributes(target),
- ...Manipulator.getDataAttributes(this)
- }
-
- data = new Modal(target, config)
- }
+ const data = Modal.getInstance(target) || new Modal(target)
- data.show(this)
+ data.toggle(this)
})
/**
@@ -602,18 +462,6 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (
* add .Modal to jQuery only if jQuery is present
*/
-onDOMContentLoaded(() => {
- const $ = getjQuery()
- /* istanbul ignore if */
- if ($) {
- const JQUERY_NO_CONFLICT = $.fn[NAME]
- $.fn[NAME] = Modal.jQueryInterface
- $.fn[NAME].Constructor = Modal
- $.fn[NAME].noConflict = () => {
- $.fn[NAME] = JQUERY_NO_CONFLICT
- return Modal.jQueryInterface
- }
- }
-})
+defineJQueryPlugin(NAME, Modal)
export default Modal