UNPKG

bootstrap-vue

Version:

With more than 85 components, over 45 available plugins, several directives, and 1000+ icons, BootstrapVue provides one of the most comprehensive implementations of the Bootstrap v4 component and grid system available for Vue.js v2.6, complete with extens

225 lines (196 loc) 8.21 kB
/** * Private ModalManager helper * Handles controlling modal stacking zIndexes and body adjustments/classes */ import { extend } from '../../../vue'; import { IS_BROWSER } from '../../../constants/env'; import { addClass, getAttr, getBCR, getCS, getStyle, hasAttr, removeAttr, removeClass, requestAF, selectAll, setAttr, setStyle } from '../../../utils/dom'; 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'; var SELECTOR_STICKY_CONTENT = '.sticky-top'; var SELECTOR_NAVBAR_TOGGLER = '.navbar-toggler'; // --- Main component --- // @vue/component var ModalManager = /*#__PURE__*/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 (IS_BROWSER) { 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(newValue) { var _this = this; this.checkScrollbar(); requestAF(function () { _this.updateModals(newValue || []); }); } }, methods: { // Public methods registerModal: function registerModal(modal) { // Register the modal if not already registered if (modal && this.modals.indexOf(modal) === -1) { this.modals.push(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 (IS_BROWSER && isNull(this.baseZIndex)) { // Create a temporary `div.modal-backdrop` to get computed z-index var div = document.createElement('div'); addClass(div, 'modal-backdrop'); addClass(div, 'd-none'); setStyle(div, '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 (IS_BROWSER && isNull(this.scrollbarWidth)) { // Create a temporary `div.measure-scrollbar` to get computed z-index var div = document.createElement('div'); addClass(div, '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 _this2 = 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 === _this2.modals.length - 1; modal.isBodyOverflowing = _this2.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 = getStyle(el, 'paddingRight') || ''; setAttr(el, 'data-padding-right', actualPadding); setStyle(el, 'paddingRight', "".concat(toFloat(getCS(el).paddingRight, 0) + 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 = getStyle(el, 'marginRight') || ''; setAttr(el, 'data-margin-right', actualMargin); setStyle(el, 'marginRight', "".concat(toFloat(getCS(el).marginRight, 0) - 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 = getStyle(el, 'marginRight') || ''; setAttr(el, 'data-margin-right', actualMargin); setStyle(el, 'marginRight', "".concat(toFloat(getCS(el).marginRight, 0) + scrollbarWidth, "px")); body._marginChangedForModal.push(el); }); // Adjust body padding var actualPadding = getStyle(body, 'paddingRight') || ''; setAttr(body, 'data-padding-right', actualPadding); setStyle(body, 'paddingRight', "".concat(toFloat(getCS(body).paddingRight, 0) + 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')) { setStyle(el, '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')) { setStyle(el, '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')) { setStyle(body, 'paddingRight', getAttr(body, 'data-padding-right') || ''); removeAttr(body, 'data-padding-right'); } } } }); // Create and export our modal manager instance export var modalManager = new ModalManager();