bootstrap-vue
Version:
BootstrapVue, with over 40 plugins and more than 80 custom components, custom directives, and over 300 icons, provides one of the most comprehensive implementations of Bootstrap v4 components and grid system for Vue.js. With extensive and automated WAI-AR
237 lines (207 loc) • 8.41 kB
JavaScript
/**
* Private ModalManager helper
* Handles controlling modal stacking zIndexes and body adjustments/classes
*/
import Vue from '../../../utils/vue';
import { getAttr, hasAttr, removeAttr, setAttr, addClass, removeClass, getBCR, getCS, selectAll, requestAF } from '../../../utils/dom';
import { isBrowser } from '../../../utils/env';
import { isNull } from '../../../utils/inspect';
import { toFloat, toInteger } from '../../../utils/number'; // --- Constants ---
// Default modal backdrop z-index
var DEFAULT_ZINDEX = 1040; // Selectors for padding/margin adjustments
var Selector = {
FIXED_CONTENT: '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top',
STICKY_CONTENT: '.sticky-top',
NAVBAR_TOGGLER: '.navbar-toggler'
}; // @vue/component
var ModalManager =
/*#__PURE__*/
Vue.extend({
data: function data() {
return {
modals: [],
baseZIndex: null,
scrollbarWidth: null,
isBodyOverflowing: false
};
},
computed: {
modalCount: function modalCount() {
return this.modals.length;
},
modalsAreOpen: function modalsAreOpen() {
return this.modalCount > 0;
}
},
watch: {
modalCount: function modalCount(newCount, oldCount) {
if (isBrowser) {
this.getScrollbarWidth();
if (newCount > 0 && oldCount === 0) {
// Transitioning to modal(s) open
this.checkScrollbar();
this.setScrollbar();
addClass(document.body, 'modal-open');
} else if (newCount === 0 && oldCount > 0) {
// Transitioning to modal(s) closed
this.resetScrollbar();
removeClass(document.body, 'modal-open');
}
setAttr(document.body, 'data-modal-open-count', String(newCount));
}
},
modals: function modals(newVal, oldVal) {
var _this = this;
this.checkScrollbar();
requestAF(function () {
_this.updateModals(newVal || []);
});
}
},
methods: {
// Public methods
registerModal: function registerModal(modal) {
var _this2 = this;
// Register the modal if not already registered
if (modal && this.modals.indexOf(modal) === -1) {
// Add modal to modals array
this.modals.push(modal);
modal.$once('hook:beforeDestroy', function () {
_this2.unregisterModal(modal);
});
}
},
unregisterModal: function unregisterModal(modal) {
var index = this.modals.indexOf(modal);
if (index > -1) {
// Remove modal from modals array
this.modals.splice(index, 1); // Reset the modal's data
if (!(modal._isBeingDestroyed || modal._isDestroyed)) {
this.resetModal(modal);
}
}
},
getBaseZIndex: function getBaseZIndex() {
if (isNull(this.baseZIndex) && isBrowser) {
// Create a temporary `div.modal-backdrop` to get computed z-index
var div = document.createElement('div');
div.className = 'modal-backdrop d-none';
div.style.display = 'none';
document.body.appendChild(div);
this.baseZIndex = toInteger(getCS(div).zIndex || DEFAULT_ZINDEX);
document.body.removeChild(div);
}
return this.baseZIndex || DEFAULT_ZINDEX;
},
getScrollbarWidth: function getScrollbarWidth() {
if (isNull(this.scrollbarWidth) && isBrowser) {
// Create a temporary `div.measure-scrollbar` to get computed z-index
var div = document.createElement('div');
div.className = 'modal-scrollbar-measure';
document.body.appendChild(div);
this.scrollbarWidth = getBCR(div).width - div.clientWidth;
document.body.removeChild(div);
}
return this.scrollbarWidth || 0;
},
// Private methods
updateModals: function updateModals(modals) {
var _this3 = this;
var baseZIndex = this.getBaseZIndex();
var scrollbarWidth = this.getScrollbarWidth();
modals.forEach(function (modal, index) {
// We update data values on each modal
modal.zIndex = baseZIndex + index;
modal.scrollbarWidth = scrollbarWidth;
modal.isTop = index === _this3.modals.length - 1;
modal.isBodyOverflowing = _this3.isBodyOverflowing;
});
},
resetModal: function resetModal(modal) {
if (modal) {
modal.zIndex = this.getBaseZIndex();
modal.isTop = true;
modal.isBodyOverflowing = false;
}
},
checkScrollbar: function checkScrollbar() {
// Determine if the body element is overflowing
var _getBCR = getBCR(document.body),
left = _getBCR.left,
right = _getBCR.right;
this.isBodyOverflowing = left + right < window.innerWidth;
},
setScrollbar: function setScrollbar() {
var body = document.body; // Storage place to cache changes to margins and padding
// Note: This assumes the following element types are not added to the
// document after the modal has opened.
body._paddingChangedForModal = body._paddingChangedForModal || [];
body._marginChangedForModal = body._marginChangedForModal || [];
if (this.isBodyOverflowing) {
var scrollbarWidth = this.scrollbarWidth; // Adjust fixed content padding
/* istanbul ignore next: difficult to test in JSDOM */
selectAll(Selector.FIXED_CONTENT).forEach(function (el) {
var actualPadding = el.style.paddingRight;
var calculatedPadding = getCS(el).paddingRight || 0;
setAttr(el, 'data-padding-right', actualPadding);
el.style.paddingRight = "".concat(toFloat(calculatedPadding) + scrollbarWidth, "px");
body._paddingChangedForModal.push(el);
}); // Adjust sticky content margin
/* istanbul ignore next: difficult to test in JSDOM */
selectAll(Selector.STICKY_CONTENT).forEach(function (el)
/* istanbul ignore next */
{
var actualMargin = el.style.marginRight;
var calculatedMargin = getCS(el).marginRight || 0;
setAttr(el, 'data-margin-right', actualMargin);
el.style.marginRight = "".concat(toFloat(calculatedMargin) - scrollbarWidth, "px");
body._marginChangedForModal.push(el);
}); // Adjust <b-navbar-toggler> margin
/* istanbul ignore next: difficult to test in JSDOM */
selectAll(Selector.NAVBAR_TOGGLER).forEach(function (el)
/* istanbul ignore next */
{
var actualMargin = el.style.marginRight;
var calculatedMargin = getCS(el).marginRight || 0;
setAttr(el, 'data-margin-right', actualMargin);
el.style.marginRight = "".concat(toFloat(calculatedMargin) + scrollbarWidth, "px");
body._marginChangedForModal.push(el);
}); // Adjust body padding
var actualPadding = body.style.paddingRight;
var calculatedPadding = getCS(body).paddingRight;
setAttr(body, 'data-padding-right', actualPadding);
body.style.paddingRight = "".concat(toFloat(calculatedPadding) + scrollbarWidth, "px");
}
},
resetScrollbar: function resetScrollbar() {
var body = document.body;
if (body._paddingChangedForModal) {
// Restore fixed content padding
body._paddingChangedForModal.forEach(function (el) {
/* istanbul ignore next: difficult to test in JSDOM */
if (hasAttr(el, 'data-padding-right')) {
el.style.paddingRight = getAttr(el, 'data-padding-right') || '';
removeAttr(el, 'data-padding-right');
}
});
}
if (body._marginChangedForModal) {
// Restore sticky content and navbar-toggler margin
body._marginChangedForModal.forEach(function (el) {
/* istanbul ignore next: difficult to test in JSDOM */
if (hasAttr(el, 'data-margin-right')) {
el.style.marginRight = getAttr(el, 'data-margin-right') || '';
removeAttr(el, 'data-margin-right');
}
});
}
body._paddingChangedForModal = null;
body._marginChangedForModal = null; // Restore body padding
if (hasAttr(body, 'data-padding-right')) {
body.style.paddingRight = getAttr(body, 'data-padding-right') || '';
removeAttr(body, 'data-padding-right');
}
}
}
}); // Create and export our modal manager instance
export var modalManager = new ModalManager();