import _classCallCheck from "@babel/runtime/helpers/esm/classCallCheck"; import _createClass from "@babel/runtime/helpers/esm/createClass"; import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray"; import { unstable_ownerWindow as ownerWindow, unstable_ownerDocument as ownerDocument, unstable_getScrollbarSize as getScrollbarSize } from '@mui/utils'; // Is a vertical scrollbar displayed? function isOverflowing(container) { var doc = ownerDocument(container); if (doc.body === container) { return ownerWindow(container).innerWidth > doc.documentElement.clientWidth; } return container.scrollHeight > container.clientHeight; } export function ariaHidden(element, show) { if (show) { element.setAttribute('aria-hidden', 'true'); } else { element.removeAttribute('aria-hidden'); } } function getPaddingRight(element) { return parseInt(ownerWindow(element).getComputedStyle(element).paddingRight, 10) || 0; } function isAriaHiddenForbiddenOnElement(element) { // The forbidden HTML tags are the ones from ARIA specification that // can be children of body and can't have aria-hidden attribute. // cf. https://www.w3.org/TR/html-aria/#docconformance var forbiddenTagNames = ['TEMPLATE', 'SCRIPT', 'STYLE', 'LINK', 'MAP', 'META', 'NOSCRIPT', 'PICTURE', 'COL', 'COLGROUP', 'PARAM', 'SLOT', 'SOURCE', 'TRACK']; var isForbiddenTagName = forbiddenTagNames.indexOf(element.tagName) !== -1; var isInputHidden = element.tagName === 'INPUT' && element.getAttribute('type') === 'hidden'; return isForbiddenTagName || isInputHidden; } function ariaHiddenSiblings(container, mountElement, currentElement, elementsToExclude, show) { var blacklist = [mountElement, currentElement].concat(_toConsumableArray(elementsToExclude)); [].forEach.call(container.children, function (element) { var isNotExcludedElement = blacklist.indexOf(element) === -1; var isNotForbiddenElement = !isAriaHiddenForbiddenOnElement(element); if (isNotExcludedElement && isNotForbiddenElement) { ariaHidden(element, show); } }); } function findIndexOf(items, callback) { var idx = -1; items.some(function (item, index) { if (callback(item)) { idx = index; return true; } return false; }); return idx; } function handleContainer(containerInfo, props) { var restoreStyle = []; var container = containerInfo.container; if (!props.disableScrollLock) { if (isOverflowing(container)) { // Compute the size before applying overflow hidden to avoid any scroll jumps. var scrollbarSize = getScrollbarSize(ownerDocument(container)); restoreStyle.push({ value: container.style.paddingRight, property: 'padding-right', el: container }); // Use computed style, here to get the real padding to add our scrollbar width. container.style.paddingRight = "".concat(getPaddingRight(container) + scrollbarSize, "px"); // .mui-fixed is a global helper. var fixedElements = ownerDocument(container).querySelectorAll('.mui-fixed'); [].forEach.call(fixedElements, function (element) { restoreStyle.push({ value: element.style.paddingRight, property: 'padding-right', el: element }); element.style.paddingRight = "".concat(getPaddingRight(element) + scrollbarSize, "px"); }); } var scrollContainer; if (container.parentNode instanceof DocumentFragment) { scrollContainer = ownerDocument(container).body; } else { // Support html overflow-y: auto for scroll stability between pages // https://css-tricks.com/snippets/css/force-vertical-scrollbar/ var parent = container.parentElement; var containerWindow = ownerWindow(container); scrollContainer = (parent == null ? void 0 : parent.nodeName) === 'HTML' && containerWindow.getComputedStyle(parent).overflowY === 'scroll' ? parent : container; } // Block the scroll even if no scrollbar is visible to account for mobile keyboard // screensize shrink. restoreStyle.push({ value: scrollContainer.style.overflow, property: 'overflow', el: scrollContainer }, { value: scrollContainer.style.overflowX, property: 'overflow-x', el: scrollContainer }, { value: scrollContainer.style.overflowY, property: 'overflow-y', el: scrollContainer }); scrollContainer.style.overflow = 'hidden'; } var restore = function restore() { restoreStyle.forEach(function (_ref) { var value = _ref.value, el = _ref.el, property = _ref.property; if (value) { el.style.setProperty(property, value); } else { el.style.removeProperty(property); } }); }; return restore; } function getHiddenSiblings(container) { var hiddenSiblings = []; [].forEach.call(container.children, function (element) { if (element.getAttribute('aria-hidden') === 'true') { hiddenSiblings.push(element); } }); return hiddenSiblings; } /** * @ignore - do not document. * * Proper state management for containers and the modals in those containers. * Simplified, but inspired by react-overlay's ModalManager class. * Used by the Modal to ensure proper styling of containers. */ export var ModalManager = /*#__PURE__*/function () { function ModalManager() { _classCallCheck(this, ModalManager); this.containers = void 0; this.modals = void 0; this.modals = []; this.containers = []; } _createClass(ModalManager, [{ key: "add", value: function add(modal, container) { var modalIndex = this.modals.indexOf(modal); if (modalIndex !== -1) { return modalIndex; } modalIndex = this.modals.length; this.modals.push(modal); // If the modal we are adding is already in the DOM. if (modal.modalRef) { ariaHidden(modal.modalRef, false); } var hiddenSiblings = getHiddenSiblings(container); ariaHiddenSiblings(container, modal.mount, modal.modalRef, hiddenSiblings, true); var containerIndex = findIndexOf(this.containers, function (item) { return item.container === container; }); if (containerIndex !== -1) { this.containers[containerIndex].modals.push(modal); return modalIndex; } this.containers.push({ modals: [modal], container: container, restore: null, hiddenSiblings: hiddenSiblings }); return modalIndex; } }, { key: "mount", value: function mount(modal, props) { var containerIndex = findIndexOf(this.containers, function (item) { return item.modals.indexOf(modal) !== -1; }); var containerInfo = this.containers[containerIndex]; if (!containerInfo.restore) { containerInfo.restore = handleContainer(containerInfo, props); } } }, { key: "remove", value: function remove(modal) { var ariaHiddenState = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; var modalIndex = this.modals.indexOf(modal); if (modalIndex === -1) { return modalIndex; } var containerIndex = findIndexOf(this.containers, function (item) { return item.modals.indexOf(modal) !== -1; }); var containerInfo = this.containers[containerIndex]; containerInfo.modals.splice(containerInfo.modals.indexOf(modal), 1); this.modals.splice(modalIndex, 1); // If that was the last modal in a container, clean up the container. if (containerInfo.modals.length === 0) { // The modal might be closed before it had the chance to be mounted in the DOM. if (containerInfo.restore) { containerInfo.restore(); } if (modal.modalRef) { // In case the modal wasn't in the DOM yet. ariaHidden(modal.modalRef, ariaHiddenState); } ariaHiddenSiblings(containerInfo.container, modal.mount, modal.modalRef, containerInfo.hiddenSiblings, false); this.containers.splice(containerIndex, 1); } else { // Otherwise make sure the next top modal is visible to a screen reader. var nextTop = containerInfo.modals[containerInfo.modals.length - 1]; // as soon as a modal is adding its modalRef is undefined. it can't set // aria-hidden because the dom element doesn't exist either // when modal was unmounted before modalRef gets null if (nextTop.modalRef) { ariaHidden(nextTop.modalRef, false); } } return modalIndex; } }, { key: "isTopModal", value: function isTopModal(modal) { return this.modals.length > 0 && this.modals[this.modals.length - 1] === modal; } }]); return ModalManager; }();