UNPKG

8.82 kBJavaScriptView Raw
1import _classCallCheck from "@babel/runtime/helpers/esm/classCallCheck";
2import _createClass from "@babel/runtime/helpers/esm/createClass";
3import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
4import { unstable_ownerWindow as ownerWindow, unstable_ownerDocument as ownerDocument, unstable_getScrollbarSize as getScrollbarSize } from '@mui/utils';
5// Is a vertical scrollbar displayed?
6function isOverflowing(container) {
7 var doc = ownerDocument(container);
8 if (doc.body === container) {
9 return ownerWindow(container).innerWidth > doc.documentElement.clientWidth;
10 }
11 return container.scrollHeight > container.clientHeight;
12}
13export function ariaHidden(element, show) {
14 if (show) {
15 element.setAttribute('aria-hidden', 'true');
16 } else {
17 element.removeAttribute('aria-hidden');
18 }
19}
20function getPaddingRight(element) {
21 return parseInt(ownerWindow(element).getComputedStyle(element).paddingRight, 10) || 0;
22}
23function isAriaHiddenForbiddenOnElement(element) {
24 // The forbidden HTML tags are the ones from ARIA specification that
25 // can be children of body and can't have aria-hidden attribute.
26 // cf. https://www.w3.org/TR/html-aria/#docconformance
27 var forbiddenTagNames = ['TEMPLATE', 'SCRIPT', 'STYLE', 'LINK', 'MAP', 'META', 'NOSCRIPT', 'PICTURE', 'COL', 'COLGROUP', 'PARAM', 'SLOT', 'SOURCE', 'TRACK'];
28 var isForbiddenTagName = forbiddenTagNames.indexOf(element.tagName) !== -1;
29 var isInputHidden = element.tagName === 'INPUT' && element.getAttribute('type') === 'hidden';
30 return isForbiddenTagName || isInputHidden;
31}
32function ariaHiddenSiblings(container, mountElement, currentElement, elementsToExclude, show) {
33 var blacklist = [mountElement, currentElement].concat(_toConsumableArray(elementsToExclude));
34 [].forEach.call(container.children, function (element) {
35 var isNotExcludedElement = blacklist.indexOf(element) === -1;
36 var isNotForbiddenElement = !isAriaHiddenForbiddenOnElement(element);
37 if (isNotExcludedElement && isNotForbiddenElement) {
38 ariaHidden(element, show);
39 }
40 });
41}
42function findIndexOf(items, callback) {
43 var idx = -1;
44 items.some(function (item, index) {
45 if (callback(item)) {
46 idx = index;
47 return true;
48 }
49 return false;
50 });
51 return idx;
52}
53function handleContainer(containerInfo, props) {
54 var restoreStyle = [];
55 var container = containerInfo.container;
56 if (!props.disableScrollLock) {
57 if (isOverflowing(container)) {
58 // Compute the size before applying overflow hidden to avoid any scroll jumps.
59 var scrollbarSize = getScrollbarSize(ownerDocument(container));
60 restoreStyle.push({
61 value: container.style.paddingRight,
62 property: 'padding-right',
63 el: container
64 });
65 // Use computed style, here to get the real padding to add our scrollbar width.
66 container.style.paddingRight = "".concat(getPaddingRight(container) + scrollbarSize, "px");
67
68 // .mui-fixed is a global helper.
69 var fixedElements = ownerDocument(container).querySelectorAll('.mui-fixed');
70 [].forEach.call(fixedElements, function (element) {
71 restoreStyle.push({
72 value: element.style.paddingRight,
73 property: 'padding-right',
74 el: element
75 });
76 element.style.paddingRight = "".concat(getPaddingRight(element) + scrollbarSize, "px");
77 });
78 }
79 var scrollContainer;
80 if (container.parentNode instanceof DocumentFragment) {
81 scrollContainer = ownerDocument(container).body;
82 } else {
83 // Improve Gatsby support
84 // https://css-tricks.com/snippets/css/force-vertical-scrollbar/
85 var parent = container.parentElement;
86 var containerWindow = ownerWindow(container);
87 scrollContainer = (parent == null ? void 0 : parent.nodeName) === 'HTML' && containerWindow.getComputedStyle(parent).overflowY === 'scroll' ? parent : container;
88 }
89
90 // Block the scroll even if no scrollbar is visible to account for mobile keyboard
91 // screensize shrink.
92 restoreStyle.push({
93 value: scrollContainer.style.overflow,
94 property: 'overflow',
95 el: scrollContainer
96 }, {
97 value: scrollContainer.style.overflowX,
98 property: 'overflow-x',
99 el: scrollContainer
100 }, {
101 value: scrollContainer.style.overflowY,
102 property: 'overflow-y',
103 el: scrollContainer
104 });
105 scrollContainer.style.overflow = 'hidden';
106 }
107 var restore = function restore() {
108 restoreStyle.forEach(function (_ref) {
109 var value = _ref.value,
110 el = _ref.el,
111 property = _ref.property;
112 if (value) {
113 el.style.setProperty(property, value);
114 } else {
115 el.style.removeProperty(property);
116 }
117 });
118 };
119 return restore;
120}
121function getHiddenSiblings(container) {
122 var hiddenSiblings = [];
123 [].forEach.call(container.children, function (element) {
124 if (element.getAttribute('aria-hidden') === 'true') {
125 hiddenSiblings.push(element);
126 }
127 });
128 return hiddenSiblings;
129}
130/**
131 * @ignore - do not document.
132 *
133 * Proper state management for containers and the modals in those containers.
134 * Simplified, but inspired by react-overlay's ModalManager class.
135 * Used by the Modal to ensure proper styling of containers.
136 */
137var ModalManager = /*#__PURE__*/function () {
138 function ModalManager() {
139 _classCallCheck(this, ModalManager);
140 this.containers = void 0;
141 this.modals = void 0;
142 this.modals = [];
143 this.containers = [];
144 }
145 _createClass(ModalManager, [{
146 key: "add",
147 value: function add(modal, container) {
148 var modalIndex = this.modals.indexOf(modal);
149 if (modalIndex !== -1) {
150 return modalIndex;
151 }
152 modalIndex = this.modals.length;
153 this.modals.push(modal);
154
155 // If the modal we are adding is already in the DOM.
156 if (modal.modalRef) {
157 ariaHidden(modal.modalRef, false);
158 }
159 var hiddenSiblings = getHiddenSiblings(container);
160 ariaHiddenSiblings(container, modal.mount, modal.modalRef, hiddenSiblings, true);
161 var containerIndex = findIndexOf(this.containers, function (item) {
162 return item.container === container;
163 });
164 if (containerIndex !== -1) {
165 this.containers[containerIndex].modals.push(modal);
166 return modalIndex;
167 }
168 this.containers.push({
169 modals: [modal],
170 container: container,
171 restore: null,
172 hiddenSiblings: hiddenSiblings
173 });
174 return modalIndex;
175 }
176 }, {
177 key: "mount",
178 value: function mount(modal, props) {
179 var containerIndex = findIndexOf(this.containers, function (item) {
180 return item.modals.indexOf(modal) !== -1;
181 });
182 var containerInfo = this.containers[containerIndex];
183 if (!containerInfo.restore) {
184 containerInfo.restore = handleContainer(containerInfo, props);
185 }
186 }
187 }, {
188 key: "remove",
189 value: function remove(modal) {
190 var ariaHiddenState = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
191 var modalIndex = this.modals.indexOf(modal);
192 if (modalIndex === -1) {
193 return modalIndex;
194 }
195 var containerIndex = findIndexOf(this.containers, function (item) {
196 return item.modals.indexOf(modal) !== -1;
197 });
198 var containerInfo = this.containers[containerIndex];
199 containerInfo.modals.splice(containerInfo.modals.indexOf(modal), 1);
200 this.modals.splice(modalIndex, 1);
201
202 // If that was the last modal in a container, clean up the container.
203 if (containerInfo.modals.length === 0) {
204 // The modal might be closed before it had the chance to be mounted in the DOM.
205 if (containerInfo.restore) {
206 containerInfo.restore();
207 }
208 if (modal.modalRef) {
209 // In case the modal wasn't in the DOM yet.
210 ariaHidden(modal.modalRef, ariaHiddenState);
211 }
212 ariaHiddenSiblings(containerInfo.container, modal.mount, modal.modalRef, containerInfo.hiddenSiblings, false);
213 this.containers.splice(containerIndex, 1);
214 } else {
215 // Otherwise make sure the next top modal is visible to a screen reader.
216 var nextTop = containerInfo.modals[containerInfo.modals.length - 1];
217 // as soon as a modal is adding its modalRef is undefined. it can't set
218 // aria-hidden because the dom element doesn't exist either
219 // when modal was unmounted before modalRef gets null
220 if (nextTop.modalRef) {
221 ariaHidden(nextTop.modalRef, false);
222 }
223 }
224 return modalIndex;
225 }
226 }, {
227 key: "isTopModal",
228 value: function isTopModal(modal) {
229 return this.modals.length > 0 && this.modals[this.modals.length - 1] === modal;
230 }
231 }]);
232 return ModalManager;
233}();
234export { ModalManager as default };
\No newline at end of file