-
Notifications
You must be signed in to change notification settings - Fork 222
/
ModalManager.js
151 lines (119 loc) · 3.72 KB
/
ModalManager.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import classes from 'dom-helpers/class';
import css from 'dom-helpers/style';
import getScrollbarSize from 'dom-helpers/util/scrollbarSize';
import isOverflowing from './utils/isOverflowing';
import { ariaHidden, hideSiblings, showSiblings }
from './utils/manageAriaHidden';
function findIndexOf(arr, cb){
let idx = -1;
arr.some((d, i)=> {
if (cb(d, i)) {
idx = i;
return true;
}
});
return idx;
}
function findContainer(data, modal) {
return findIndexOf(data,
d => d.modals.indexOf(modal) !== -1);
}
function setContainerStyle(state, container) {
let style = { overflow: 'hidden' };
// we are only interested in the actual `style` here
// becasue we will override it
state.style = {
overflow: container.style.overflow,
paddingRight: container.style.paddingRight
}
if (state.overflowing) {
// use computed style, here to get the real padding
// to add our scrollbar width
style.paddingRight =
parseInt(css(container, 'paddingRight') || 0, 10) + getScrollbarSize() + 'px';
}
css(container, style);
}
function removeContainerStyle({ style }, container) {
Object.keys(style).forEach(
key => container.style[key] = style[key]);
}
/**
* Proper state managment for containers and the modals in those containers.
*
* @internal Used by the Modal to ensure proper styling of containers.
*/
class ModalManager {
constructor({ hideSiblingNodes = true, handleContainerOverflow = true } = {}) {
this.hideSiblingNodes = hideSiblingNodes;
this.handleContainerOverflow = handleContainerOverflow;
this.modals = [];
this.containers = [];
this.data = [];
}
add = (modal, container, className) => {
let modalIdx = this.modals.indexOf(modal);
let containerIdx = this.containers.indexOf(container);
if (modalIdx !== -1) {
return modalIdx;
}
modalIdx = this.modals.length;
this.modals.push(modal);
if (this.hideSiblingNodes) {
hideSiblings(container, modal.mountNode);
}
if (containerIdx !== -1) {
this.data[containerIdx].modals.push(modal);
return modalIdx;
}
let data = {
modals: [ modal ],
//right now only the first modal of a container will have its classes applied
classes: className ? className.split(/\s+/) : [],
overflowing: isOverflowing(container)
};
if (this.handleContainerOverflow) {
setContainerStyle(data, container)
}
data.classes.forEach(
classes.addClass.bind(null, container));
this.containers.push(container);
this.data.push(data);
return modalIdx;
}
remove = (modal) => {
let modalIdx = this.modals.indexOf(modal);
if (modalIdx === -1) {
return;
}
let containerIdx = findContainer(this.data, modal);
let data = this.data[containerIdx];
let container = this.containers[containerIdx];
data.modals.splice(
data.modals.indexOf(modal), 1);
this.modals.splice(modalIdx, 1);
// if that was the last modal in a container,
// clean up the container
if (data.modals.length === 0){
data.classes.forEach(
classes.removeClass.bind(null, container));
if (this.handleContainerOverflow) {
removeContainerStyle(data, container)
}
if (this.hideSiblingNodes) {
showSiblings(container, modal.mountNode);
}
this.containers.splice(containerIdx, 1);
this.data.splice(containerIdx, 1);
}
else if (this.hideSiblingNodes) {
//otherwise make sure the next top modal is visible to a SR
ariaHidden(false, data.modals[data.modals.length - 1].mountNode);
}
}
isTopModal = (modal) => {
return !!this.modals.length
&& this.modals[this.modals.length - 1] === modal;
}
}
export default ModalManager;