UNPKG

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