@khanacademy/wonder-blocks-modal 2.1.41 → 2.1.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/index.js +601 -945
- package/dist/index.js +1089 -1422
- package/package.json +15 -14
- package/src/components/__tests__/modal-backdrop.test.js +35 -51
- package/src/components/__tests__/modal-launcher.test.js +36 -6
- package/src/components/modal-backdrop.js +3 -8
- package/src/components/one-pane-dialog.stories.js +8 -10
- package/src/util/find-focusable-nodes.js +14 -0
- package/src/util/maybe-get-portal-mounted-modal-host-element.test.js +2 -3
- package/src/__tests__/index.test.js +0 -23
package/dist/es/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Component, createElement, Fragment, createContext, cloneElement } from 'react';
|
|
2
2
|
import { StyleSheet } from 'aphrodite';
|
|
3
3
|
import { MediaLayoutContext, MediaLayout, MEDIA_MODAL_SPEC } from '@khanacademy/wonder-blocks-layout';
|
|
4
4
|
import { View, IDProvider } from '@khanacademy/wonder-blocks-core';
|
|
@@ -6,143 +6,10 @@ import Spacing from '@khanacademy/wonder-blocks-spacing';
|
|
|
6
6
|
import Color from '@khanacademy/wonder-blocks-color';
|
|
7
7
|
import { HeadingMedium, LabelSmall } from '@khanacademy/wonder-blocks-typography';
|
|
8
8
|
import { findDOMNode, createPortal } from 'react-dom';
|
|
9
|
+
import _extends from '@babel/runtime/helpers/extends';
|
|
9
10
|
import { icons } from '@khanacademy/wonder-blocks-icon';
|
|
10
11
|
import IconButton from '@khanacademy/wonder-blocks-icon-button';
|
|
11
12
|
|
|
12
|
-
function _classCallCheck(instance, Constructor) {
|
|
13
|
-
if (!(instance instanceof Constructor)) {
|
|
14
|
-
throw new TypeError("Cannot call a class as a function");
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function _defineProperties(target, props) {
|
|
19
|
-
for (var i = 0; i < props.length; i++) {
|
|
20
|
-
var descriptor = props[i];
|
|
21
|
-
descriptor.enumerable = descriptor.enumerable || false;
|
|
22
|
-
descriptor.configurable = true;
|
|
23
|
-
if ("value" in descriptor) descriptor.writable = true;
|
|
24
|
-
Object.defineProperty(target, descriptor.key, descriptor);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function _createClass(Constructor, protoProps, staticProps) {
|
|
29
|
-
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
|
|
30
|
-
if (staticProps) _defineProperties(Constructor, staticProps);
|
|
31
|
-
return Constructor;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function _defineProperty(obj, key, value) {
|
|
35
|
-
if (key in obj) {
|
|
36
|
-
Object.defineProperty(obj, key, {
|
|
37
|
-
value: value,
|
|
38
|
-
enumerable: true,
|
|
39
|
-
configurable: true,
|
|
40
|
-
writable: true
|
|
41
|
-
});
|
|
42
|
-
} else {
|
|
43
|
-
obj[key] = value;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return obj;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function _extends() {
|
|
50
|
-
_extends = Object.assign || function (target) {
|
|
51
|
-
for (var i = 1; i < arguments.length; i++) {
|
|
52
|
-
var source = arguments[i];
|
|
53
|
-
|
|
54
|
-
for (var key in source) {
|
|
55
|
-
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
56
|
-
target[key] = source[key];
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return target;
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
return _extends.apply(this, arguments);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function _inherits(subClass, superClass) {
|
|
68
|
-
if (typeof superClass !== "function" && superClass !== null) {
|
|
69
|
-
throw new TypeError("Super expression must either be null or a function");
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
subClass.prototype = Object.create(superClass && superClass.prototype, {
|
|
73
|
-
constructor: {
|
|
74
|
-
value: subClass,
|
|
75
|
-
writable: true,
|
|
76
|
-
configurable: true
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
if (superClass) _setPrototypeOf(subClass, superClass);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function _getPrototypeOf(o) {
|
|
83
|
-
_getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
|
|
84
|
-
return o.__proto__ || Object.getPrototypeOf(o);
|
|
85
|
-
};
|
|
86
|
-
return _getPrototypeOf(o);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function _setPrototypeOf(o, p) {
|
|
90
|
-
_setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
|
|
91
|
-
o.__proto__ = p;
|
|
92
|
-
return o;
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
return _setPrototypeOf(o, p);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function _isNativeReflectConstruct() {
|
|
99
|
-
if (typeof Reflect === "undefined" || !Reflect.construct) return false;
|
|
100
|
-
if (Reflect.construct.sham) return false;
|
|
101
|
-
if (typeof Proxy === "function") return true;
|
|
102
|
-
|
|
103
|
-
try {
|
|
104
|
-
Date.prototype.toString.call(Reflect.construct(Date, [], function () {}));
|
|
105
|
-
return true;
|
|
106
|
-
} catch (e) {
|
|
107
|
-
return false;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function _assertThisInitialized(self) {
|
|
112
|
-
if (self === void 0) {
|
|
113
|
-
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return self;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function _possibleConstructorReturn(self, call) {
|
|
120
|
-
if (call && (typeof call === "object" || typeof call === "function")) {
|
|
121
|
-
return call;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return _assertThisInitialized(self);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function _createSuper(Derived) {
|
|
128
|
-
var hasNativeReflectConstruct = _isNativeReflectConstruct();
|
|
129
|
-
|
|
130
|
-
return function _createSuperInternal() {
|
|
131
|
-
var Super = _getPrototypeOf(Derived),
|
|
132
|
-
result;
|
|
133
|
-
|
|
134
|
-
if (hasNativeReflectConstruct) {
|
|
135
|
-
var NewTarget = _getPrototypeOf(this).constructor;
|
|
136
|
-
|
|
137
|
-
result = Reflect.construct(Super, arguments, NewTarget);
|
|
138
|
-
} else {
|
|
139
|
-
result = Super.apply(this, arguments);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return _possibleConstructorReturn(this, result);
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
|
|
146
13
|
/**
|
|
147
14
|
* `ModalDialog` is a component that contains these elements:
|
|
148
15
|
* - The visual dialog element itself (`<div role="dialog"/>`)
|
|
@@ -153,62 +20,47 @@ function _createSuper(Derived) {
|
|
|
153
20
|
* - If there is a custom Dialog implementation (e.g. `TwoPaneDialog`), the dialog element doesn’t have to have
|
|
154
21
|
* the `aria-labelledby` attribute however this is recommended. It should match the `id` of the dialog title.
|
|
155
22
|
*/
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
23
|
+
class ModalDialog extends Component {
|
|
24
|
+
render() {
|
|
25
|
+
const {
|
|
26
|
+
above,
|
|
27
|
+
below,
|
|
28
|
+
role,
|
|
29
|
+
style,
|
|
30
|
+
children,
|
|
31
|
+
testId,
|
|
32
|
+
"aria-labelledby": ariaLabelledBy
|
|
33
|
+
} = this.props;
|
|
34
|
+
const contextValue = {
|
|
35
|
+
ssrSize: "large",
|
|
36
|
+
mediaSpec: MEDIA_MODAL_SPEC
|
|
37
|
+
};
|
|
38
|
+
return /*#__PURE__*/createElement(MediaLayoutContext.Provider, {
|
|
39
|
+
value: contextValue
|
|
40
|
+
}, /*#__PURE__*/createElement(MediaLayout, {
|
|
41
|
+
styleSheets: styleSheets
|
|
42
|
+
}, ({
|
|
43
|
+
styles
|
|
44
|
+
}) => /*#__PURE__*/createElement(View, {
|
|
45
|
+
style: [styles.wrapper, style]
|
|
46
|
+
}, below && /*#__PURE__*/createElement(View, {
|
|
47
|
+
style: styles.below
|
|
48
|
+
}, below), /*#__PURE__*/createElement(View, {
|
|
49
|
+
role: role,
|
|
50
|
+
"aria-modal": "true",
|
|
51
|
+
"aria-labelledby": ariaLabelledBy,
|
|
52
|
+
style: styles.dialog,
|
|
53
|
+
testId: testId
|
|
54
|
+
}, children), above && /*#__PURE__*/createElement(View, {
|
|
55
|
+
style: styles.above
|
|
56
|
+
}, above))));
|
|
165
57
|
}
|
|
166
58
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
value: function render() {
|
|
170
|
-
var _this$props = this.props,
|
|
171
|
-
above = _this$props.above,
|
|
172
|
-
below = _this$props.below,
|
|
173
|
-
role = _this$props.role,
|
|
174
|
-
style = _this$props.style,
|
|
175
|
-
children = _this$props.children,
|
|
176
|
-
testId = _this$props.testId,
|
|
177
|
-
ariaLabelledBy = _this$props["aria-labelledby"];
|
|
178
|
-
var contextValue = {
|
|
179
|
-
ssrSize: "large",
|
|
180
|
-
mediaSpec: MEDIA_MODAL_SPEC
|
|
181
|
-
};
|
|
182
|
-
return /*#__PURE__*/createElement(MediaLayoutContext.Provider, {
|
|
183
|
-
value: contextValue
|
|
184
|
-
}, /*#__PURE__*/createElement(MediaLayout, {
|
|
185
|
-
styleSheets: styleSheets
|
|
186
|
-
}, function (_ref) {
|
|
187
|
-
var styles = _ref.styles;
|
|
188
|
-
return /*#__PURE__*/createElement(View, {
|
|
189
|
-
style: [styles.wrapper, style]
|
|
190
|
-
}, below && /*#__PURE__*/createElement(View, {
|
|
191
|
-
style: styles.below
|
|
192
|
-
}, below), /*#__PURE__*/createElement(View, {
|
|
193
|
-
role: role,
|
|
194
|
-
"aria-modal": "true",
|
|
195
|
-
"aria-labelledby": ariaLabelledBy,
|
|
196
|
-
style: styles.dialog,
|
|
197
|
-
testId: testId
|
|
198
|
-
}, children), above && /*#__PURE__*/createElement(View, {
|
|
199
|
-
style: styles.above
|
|
200
|
-
}, above));
|
|
201
|
-
}));
|
|
202
|
-
}
|
|
203
|
-
}]);
|
|
204
|
-
|
|
205
|
-
return ModalDialog;
|
|
206
|
-
}(Component);
|
|
207
|
-
|
|
208
|
-
_defineProperty(ModalDialog, "defaultProps", {
|
|
59
|
+
}
|
|
60
|
+
ModalDialog.defaultProps = {
|
|
209
61
|
role: "dialog"
|
|
210
|
-
}
|
|
211
|
-
|
|
62
|
+
};
|
|
63
|
+
const styleSheets = {
|
|
212
64
|
all: StyleSheet.create({
|
|
213
65
|
wrapper: {
|
|
214
66
|
display: "flex",
|
|
@@ -264,37 +116,23 @@ var styleSheets = {
|
|
|
264
116
|
* - Make sure to include it as part of [ModalPanel](/#modalpanel) by using the `footer` prop.
|
|
265
117
|
* - The footer is completely flexible. Meaning the developer needs to add its own custom layout to match design specs.
|
|
266
118
|
*/
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
var _super = _createSuper(ModalFooter);
|
|
271
|
-
|
|
272
|
-
function ModalFooter() {
|
|
273
|
-
_classCallCheck(this, ModalFooter);
|
|
274
|
-
|
|
275
|
-
return _super.apply(this, arguments);
|
|
119
|
+
class ModalFooter extends Component {
|
|
120
|
+
static isClassOf(instance) {
|
|
121
|
+
return instance && instance.type && instance.type.__IS_MODAL_FOOTER__;
|
|
276
122
|
}
|
|
277
123
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}], [{
|
|
287
|
-
key: "isClassOf",
|
|
288
|
-
value: function isClassOf(instance) {
|
|
289
|
-
return instance && instance.type && instance.type.__IS_MODAL_FOOTER__;
|
|
290
|
-
}
|
|
291
|
-
}]);
|
|
292
|
-
|
|
293
|
-
return ModalFooter;
|
|
294
|
-
}(Component);
|
|
124
|
+
render() {
|
|
125
|
+
const {
|
|
126
|
+
children
|
|
127
|
+
} = this.props;
|
|
128
|
+
return /*#__PURE__*/createElement(View, {
|
|
129
|
+
style: styles.footer
|
|
130
|
+
}, children);
|
|
131
|
+
}
|
|
295
132
|
|
|
296
|
-
|
|
297
|
-
|
|
133
|
+
}
|
|
134
|
+
ModalFooter.__IS_MODAL_FOOTER__ = true;
|
|
135
|
+
const styles = StyleSheet.create({
|
|
298
136
|
footer: {
|
|
299
137
|
flex: "0 0 auto",
|
|
300
138
|
boxSizing: "border-box",
|
|
@@ -307,7 +145,7 @@ var styles = StyleSheet.create({
|
|
|
307
145
|
flexDirection: "row",
|
|
308
146
|
alignItems: "center",
|
|
309
147
|
justifyContent: "flex-end",
|
|
310
|
-
boxShadow:
|
|
148
|
+
boxShadow: `0px -1px 0px ${Color.offBlack16}`
|
|
311
149
|
}
|
|
312
150
|
});
|
|
313
151
|
|
|
@@ -354,69 +192,52 @@ var styles = StyleSheet.create({
|
|
|
354
192
|
* />
|
|
355
193
|
* ```
|
|
356
194
|
*/
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
value: function render() {
|
|
371
|
-
var _this$props = this.props,
|
|
372
|
-
_this$props$breadcrum = _this$props.breadcrumbs,
|
|
373
|
-
breadcrumbs = _this$props$breadcrum === void 0 ? undefined : _this$props$breadcrum,
|
|
374
|
-
light = _this$props.light,
|
|
375
|
-
_this$props$subtitle = _this$props.subtitle,
|
|
376
|
-
subtitle = _this$props$subtitle === void 0 ? undefined : _this$props$subtitle,
|
|
377
|
-
testId = _this$props.testId,
|
|
378
|
-
title = _this$props.title,
|
|
379
|
-
titleId = _this$props.titleId;
|
|
380
|
-
|
|
381
|
-
if (subtitle && breadcrumbs) {
|
|
382
|
-
throw new Error("'subtitle' and 'breadcrumbs' can't be used together");
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
return /*#__PURE__*/createElement(MediaLayout, {
|
|
386
|
-
styleSheets: styleSheets$1
|
|
387
|
-
}, function (_ref) {
|
|
388
|
-
var styles = _ref.styles;
|
|
389
|
-
return /*#__PURE__*/createElement(View, {
|
|
390
|
-
style: [styles.header, !light && styles.dark],
|
|
391
|
-
testId: testId
|
|
392
|
-
}, breadcrumbs && /*#__PURE__*/createElement(View, {
|
|
393
|
-
style: styles.breadcrumbs
|
|
394
|
-
}, breadcrumbs), /*#__PURE__*/createElement(HeadingMedium, {
|
|
395
|
-
style: styles.title,
|
|
396
|
-
id: titleId,
|
|
397
|
-
testId: testId && "".concat(testId, "-title")
|
|
398
|
-
}, title), subtitle && /*#__PURE__*/createElement(LabelSmall, {
|
|
399
|
-
style: light && styles.subtitle,
|
|
400
|
-
testId: testId && "".concat(testId, "-subtitle")
|
|
401
|
-
}, subtitle));
|
|
402
|
-
});
|
|
195
|
+
class ModalHeader extends Component {
|
|
196
|
+
render() {
|
|
197
|
+
const {
|
|
198
|
+
breadcrumbs = undefined,
|
|
199
|
+
light,
|
|
200
|
+
subtitle = undefined,
|
|
201
|
+
testId,
|
|
202
|
+
title,
|
|
203
|
+
titleId
|
|
204
|
+
} = this.props;
|
|
205
|
+
|
|
206
|
+
if (subtitle && breadcrumbs) {
|
|
207
|
+
throw new Error("'subtitle' and 'breadcrumbs' can't be used together");
|
|
403
208
|
}
|
|
404
|
-
}]);
|
|
405
209
|
|
|
406
|
-
|
|
407
|
-
|
|
210
|
+
return /*#__PURE__*/createElement(MediaLayout, {
|
|
211
|
+
styleSheets: styleSheets$1
|
|
212
|
+
}, ({
|
|
213
|
+
styles
|
|
214
|
+
}) => /*#__PURE__*/createElement(View, {
|
|
215
|
+
style: [styles.header, !light && styles.dark],
|
|
216
|
+
testId: testId
|
|
217
|
+
}, breadcrumbs && /*#__PURE__*/createElement(View, {
|
|
218
|
+
style: styles.breadcrumbs
|
|
219
|
+
}, breadcrumbs), /*#__PURE__*/createElement(HeadingMedium, {
|
|
220
|
+
style: styles.title,
|
|
221
|
+
id: titleId,
|
|
222
|
+
testId: testId && `${testId}-title`
|
|
223
|
+
}, title), subtitle && /*#__PURE__*/createElement(LabelSmall, {
|
|
224
|
+
style: light && styles.subtitle,
|
|
225
|
+
testId: testId && `${testId}-subtitle`
|
|
226
|
+
}, subtitle)));
|
|
227
|
+
}
|
|
408
228
|
|
|
409
|
-
|
|
229
|
+
}
|
|
230
|
+
ModalHeader.defaultProps = {
|
|
410
231
|
light: true
|
|
411
|
-
}
|
|
412
|
-
|
|
232
|
+
};
|
|
233
|
+
const styleSheets$1 = {
|
|
413
234
|
all: StyleSheet.create({
|
|
414
235
|
header: {
|
|
415
|
-
boxShadow:
|
|
236
|
+
boxShadow: `0px 1px 0px ${Color.offBlack16}`,
|
|
416
237
|
display: "flex",
|
|
417
238
|
flexDirection: "column",
|
|
418
239
|
minHeight: 66,
|
|
419
|
-
padding:
|
|
240
|
+
padding: `${Spacing.large_24}px ${Spacing.xLarge_32}px`,
|
|
420
241
|
position: "relative",
|
|
421
242
|
width: "100%"
|
|
422
243
|
},
|
|
@@ -448,11 +269,7 @@ var styleSheets$1 = {
|
|
|
448
269
|
})
|
|
449
270
|
};
|
|
450
271
|
|
|
451
|
-
|
|
452
|
-
_inherits(FocusTrap, _React$Component);
|
|
453
|
-
|
|
454
|
-
var _super = _createSuper(FocusTrap);
|
|
455
|
-
|
|
272
|
+
class FocusTrap extends Component {
|
|
456
273
|
/** The most recent node _inside this component_ to receive focus. */
|
|
457
274
|
|
|
458
275
|
/**
|
|
@@ -463,49 +280,39 @@ var FocusTrap = /*#__PURE__*/function (_React$Component) {
|
|
|
463
280
|
/**
|
|
464
281
|
* Tabbing is restricted to descendents of this element.
|
|
465
282
|
*/
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
_classCallCheck(this, FocusTrap);
|
|
470
|
-
|
|
471
|
-
_this = _super.call(this, props);
|
|
283
|
+
constructor(props) {
|
|
284
|
+
super(props);
|
|
472
285
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
_defineProperty(_assertThisInitialized(_this), "ignoreFocusChanges", void 0);
|
|
476
|
-
|
|
477
|
-
_defineProperty(_assertThisInitialized(_this), "modalRoot", void 0);
|
|
478
|
-
|
|
479
|
-
_defineProperty(_assertThisInitialized(_this), "getModalRoot", function (node) {
|
|
286
|
+
this.getModalRoot = node => {
|
|
480
287
|
if (!node) {
|
|
481
288
|
// The component is being umounted
|
|
482
289
|
return;
|
|
483
290
|
}
|
|
484
291
|
|
|
485
|
-
|
|
292
|
+
const modalRoot = findDOMNode(node);
|
|
486
293
|
|
|
487
294
|
if (!modalRoot) {
|
|
488
295
|
throw new Error("Assertion error: modal root should exist after mount");
|
|
489
296
|
}
|
|
490
297
|
|
|
491
|
-
|
|
492
|
-
}
|
|
298
|
+
this.modalRoot = modalRoot;
|
|
299
|
+
};
|
|
493
300
|
|
|
494
|
-
|
|
301
|
+
this.handleGlobalFocus = e => {
|
|
495
302
|
// If we're busy applying our own programmatic focus, we ignore focus
|
|
496
303
|
// changes, to avoid an infinite loop.
|
|
497
|
-
if (
|
|
304
|
+
if (this.ignoreFocusChanges) {
|
|
498
305
|
return;
|
|
499
306
|
}
|
|
500
307
|
|
|
501
|
-
|
|
308
|
+
const target = e.target;
|
|
502
309
|
|
|
503
310
|
if (!(target instanceof Node)) {
|
|
504
311
|
// Sometimes focus events trigger on the document itself. Ignore!
|
|
505
312
|
return;
|
|
506
313
|
}
|
|
507
314
|
|
|
508
|
-
|
|
315
|
+
const modalRoot = this.modalRoot;
|
|
509
316
|
|
|
510
317
|
if (!modalRoot) {
|
|
511
318
|
return;
|
|
@@ -514,141 +321,135 @@ var FocusTrap = /*#__PURE__*/function (_React$Component) {
|
|
|
514
321
|
if (modalRoot.contains(target)) {
|
|
515
322
|
// If the newly focused node is inside the modal, we just keep track
|
|
516
323
|
// of that.
|
|
517
|
-
|
|
324
|
+
this.lastNodeFocusedInModal = target;
|
|
518
325
|
} else {
|
|
519
326
|
// If the newly focused node is outside the modal, we try refocusing
|
|
520
327
|
// the first focusable node of the modal. (This could be the user
|
|
521
328
|
// pressing Tab on the last node of the modal, or focus escaping in
|
|
522
329
|
// some other way.)
|
|
523
|
-
|
|
330
|
+
this.focusFirstElementIn(modalRoot); // But, if it turns out that the first focusable node of the modal
|
|
524
331
|
// was what we were previously focusing, then this is probably the
|
|
525
332
|
// user pressing Shift-Tab on the first node, wanting to go to the
|
|
526
333
|
// end. So, we instead try focusing the last focusable node of the
|
|
527
334
|
// modal.
|
|
528
335
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
_this.focusLastElementIn(modalRoot);
|
|
336
|
+
if (document.activeElement === this.lastNodeFocusedInModal) {
|
|
337
|
+
this.focusLastElementIn(modalRoot);
|
|
532
338
|
} // Focus should now be inside the modal, so record the newly-focused
|
|
533
339
|
// node as the last node focused in the modal.
|
|
534
340
|
|
|
535
341
|
|
|
536
|
-
|
|
342
|
+
this.lastNodeFocusedInModal = document.activeElement;
|
|
537
343
|
}
|
|
538
|
-
}
|
|
344
|
+
};
|
|
539
345
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
return _this;
|
|
346
|
+
this.lastNodeFocusedInModal = null;
|
|
347
|
+
this.ignoreFocusChanges = false;
|
|
543
348
|
}
|
|
544
349
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
window.addEventListener("focus", this.handleGlobalFocus, true);
|
|
549
|
-
}
|
|
550
|
-
}, {
|
|
551
|
-
key: "componentWillUnmount",
|
|
552
|
-
value: function componentWillUnmount() {
|
|
553
|
-
window.removeEventListener("focus", this.handleGlobalFocus, true);
|
|
554
|
-
}
|
|
555
|
-
}, {
|
|
556
|
-
key: "tryToFocus",
|
|
350
|
+
componentDidMount() {
|
|
351
|
+
window.addEventListener("focus", this.handleGlobalFocus, true);
|
|
352
|
+
}
|
|
557
353
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
this.ignoreFocusChanges = true;
|
|
354
|
+
componentWillUnmount() {
|
|
355
|
+
window.removeEventListener("focus", this.handleGlobalFocus, true);
|
|
356
|
+
}
|
|
562
357
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
358
|
+
/** Try to focus the given node. Return true iff successful. */
|
|
359
|
+
tryToFocus(node) {
|
|
360
|
+
if (node instanceof HTMLElement) {
|
|
361
|
+
this.ignoreFocusChanges = true;
|
|
567
362
|
|
|
568
|
-
|
|
569
|
-
|
|
363
|
+
try {
|
|
364
|
+
node.focus();
|
|
365
|
+
} catch (e) {// ignore error
|
|
570
366
|
}
|
|
367
|
+
|
|
368
|
+
this.ignoreFocusChanges = false;
|
|
369
|
+
return document.activeElement === node;
|
|
571
370
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Focus the first focusable descendant of the given node.
|
|
374
|
+
*
|
|
375
|
+
* Return true if we succeed. Or, if the given node has no focusable
|
|
376
|
+
* descendants, return false.
|
|
377
|
+
*/
|
|
578
378
|
|
|
579
|
-
}, {
|
|
580
|
-
key: "focusFirstElementIn",
|
|
581
|
-
value: function focusFirstElementIn(currentParent) {
|
|
582
|
-
var children = currentParent.childNodes;
|
|
583
379
|
|
|
584
|
-
|
|
585
|
-
|
|
380
|
+
focusFirstElementIn(currentParent) {
|
|
381
|
+
const children = currentParent.childNodes;
|
|
586
382
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
}
|
|
590
|
-
}
|
|
383
|
+
for (let i = 0; i < children.length; i++) {
|
|
384
|
+
const child = children[i];
|
|
591
385
|
|
|
592
|
-
|
|
386
|
+
if (this.tryToFocus(child) || this.focusFirstElementIn(child)) {
|
|
387
|
+
return true;
|
|
388
|
+
}
|
|
593
389
|
}
|
|
594
|
-
/**
|
|
595
|
-
* Focus the last focusable descendant of the given node.
|
|
596
|
-
*
|
|
597
|
-
* Return true if we succeed. Or, if the given node has no focusable
|
|
598
|
-
* descendants, return false.
|
|
599
|
-
*/
|
|
600
390
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
391
|
+
return false;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Focus the last focusable descendant of the given node.
|
|
395
|
+
*
|
|
396
|
+
* Return true if we succeed. Or, if the given node has no focusable
|
|
397
|
+
* descendants, return false.
|
|
398
|
+
*/
|
|
399
|
+
|
|
605
400
|
|
|
606
|
-
|
|
607
|
-
|
|
401
|
+
focusLastElementIn(currentParent) {
|
|
402
|
+
const children = currentParent.childNodes;
|
|
608
403
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
}
|
|
612
|
-
}
|
|
404
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
405
|
+
const child = children[i];
|
|
613
406
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
}, {
|
|
619
|
-
key: "render",
|
|
620
|
-
value: function render() {
|
|
621
|
-
var style = this.props.style;
|
|
622
|
-
return /*#__PURE__*/createElement(Fragment, null, /*#__PURE__*/createElement("div", {
|
|
623
|
-
tabIndex: "0",
|
|
624
|
-
style: {
|
|
625
|
-
position: "fixed"
|
|
626
|
-
}
|
|
627
|
-
}), /*#__PURE__*/createElement(View, {
|
|
628
|
-
style: style,
|
|
629
|
-
ref: this.getModalRoot
|
|
630
|
-
}, this.props.children), /*#__PURE__*/createElement("div", {
|
|
631
|
-
tabIndex: "0",
|
|
632
|
-
style: {
|
|
633
|
-
position: "fixed"
|
|
634
|
-
}
|
|
635
|
-
}));
|
|
407
|
+
if (this.tryToFocus(child) || this.focusLastElementIn(child)) {
|
|
408
|
+
return true;
|
|
409
|
+
}
|
|
636
410
|
}
|
|
637
|
-
}]);
|
|
638
411
|
|
|
639
|
-
|
|
640
|
-
}
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
/** This method is called when any node on the page is focused. */
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
render() {
|
|
418
|
+
const {
|
|
419
|
+
style
|
|
420
|
+
} = this.props;
|
|
421
|
+
return /*#__PURE__*/createElement(Fragment, null, /*#__PURE__*/createElement("div", {
|
|
422
|
+
tabIndex: "0",
|
|
423
|
+
style: {
|
|
424
|
+
position: "fixed"
|
|
425
|
+
}
|
|
426
|
+
}), /*#__PURE__*/createElement(View, {
|
|
427
|
+
style: style,
|
|
428
|
+
ref: this.getModalRoot
|
|
429
|
+
}, this.props.children), /*#__PURE__*/createElement("div", {
|
|
430
|
+
tabIndex: "0",
|
|
431
|
+
style: {
|
|
432
|
+
position: "fixed"
|
|
433
|
+
}
|
|
434
|
+
}));
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
}
|
|
641
438
|
|
|
642
439
|
/**
|
|
643
440
|
* The attribute used to identify a modal launcher portal.
|
|
644
441
|
*/
|
|
645
|
-
|
|
442
|
+
const ModalLauncherPortalAttributeName = "data-modal-launcher-portal";
|
|
646
443
|
|
|
647
444
|
/**
|
|
648
445
|
* List of elements that can be focused
|
|
649
446
|
* @see https://www.w3.org/TR/html5/editing.html#can-be-focused
|
|
650
447
|
*/
|
|
651
|
-
|
|
448
|
+
const FOCUSABLE_ELEMENTS = 'a[href], details, input, textarea, select, button:not([aria-label^="Close"])';
|
|
449
|
+
function findFocusableNodes(root) {
|
|
450
|
+
return Array.from(root.querySelectorAll(FOCUSABLE_ELEMENTS));
|
|
451
|
+
}
|
|
452
|
+
|
|
652
453
|
/**
|
|
653
454
|
* A private component used by ModalLauncher. This is the fixed-position
|
|
654
455
|
* container element that gets mounted outside the DOM. It overlays the modal
|
|
@@ -659,125 +460,105 @@ var FOCUSABLE_ELEMENTS = 'a[href], details, input, textarea, select, button:not(
|
|
|
659
460
|
* and adding an `onClose` prop that will call `onCloseModal`. If an
|
|
660
461
|
* `onClose` prop is already provided, the two are merged.
|
|
661
462
|
*/
|
|
463
|
+
class ModalBackdrop extends Component {
|
|
464
|
+
constructor(...args) {
|
|
465
|
+
super(...args);
|
|
662
466
|
|
|
663
|
-
|
|
664
|
-
_inherits(ModalBackdrop, _React$Component);
|
|
665
|
-
|
|
666
|
-
var _super = _createSuper(ModalBackdrop);
|
|
667
|
-
|
|
668
|
-
function ModalBackdrop() {
|
|
669
|
-
var _this;
|
|
670
|
-
|
|
671
|
-
_classCallCheck(this, ModalBackdrop);
|
|
672
|
-
|
|
673
|
-
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
674
|
-
args[_key] = arguments[_key];
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
_this = _super.call.apply(_super, [this].concat(args));
|
|
678
|
-
|
|
679
|
-
_defineProperty(_assertThisInitialized(_this), "handleClick", function (e) {
|
|
467
|
+
this.handleClick = e => {
|
|
680
468
|
// Was the lowest-level click target (`e.target`) the positioner element
|
|
681
469
|
// (`e.currentTarget`)?
|
|
682
470
|
if (e.target === e.currentTarget) {
|
|
683
|
-
|
|
471
|
+
this.props.onCloseModal();
|
|
684
472
|
}
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
return _this;
|
|
473
|
+
};
|
|
688
474
|
}
|
|
689
475
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
value: function componentDidMount() {
|
|
693
|
-
var node = findDOMNode(this);
|
|
476
|
+
componentDidMount() {
|
|
477
|
+
const node = findDOMNode(this);
|
|
694
478
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
479
|
+
if (!node) {
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
698
482
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
483
|
+
const firstFocusableElement = // 1. try to get element specified by the user
|
|
484
|
+
this._getInitialFocusElement(node) || // 2. get first occurence from list of focusable elements
|
|
485
|
+
this._getFirstFocusableElement(node) || // 3. get the dialog itself
|
|
486
|
+
this._getDialogElement(node); // wait for styles to applied
|
|
703
487
|
|
|
704
488
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
489
|
+
setTimeout(() => {
|
|
490
|
+
firstFocusableElement.focus();
|
|
491
|
+
}, 0);
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Returns an element specified by the user
|
|
495
|
+
*/
|
|
712
496
|
|
|
713
|
-
}, {
|
|
714
|
-
key: "_getInitialFocusElement",
|
|
715
|
-
value: function _getInitialFocusElement(node) {
|
|
716
|
-
var initialFocusId = this.props.initialFocusId;
|
|
717
497
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
498
|
+
_getInitialFocusElement(node) {
|
|
499
|
+
const {
|
|
500
|
+
initialFocusId
|
|
501
|
+
} = this.props;
|
|
721
502
|
|
|
722
|
-
|
|
503
|
+
if (!initialFocusId) {
|
|
504
|
+
return null;
|
|
723
505
|
}
|
|
724
|
-
/**
|
|
725
|
-
* Returns the first focusable element found inside the Dialog
|
|
726
|
-
*/
|
|
727
506
|
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
507
|
+
return findDOMNode(node.querySelector(`#${initialFocusId}`));
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Returns the first focusable element found inside the Dialog
|
|
511
|
+
*/
|
|
733
512
|
|
|
734
|
-
if (!focusableElements) {
|
|
735
|
-
return null;
|
|
736
|
-
} // if found, return the first focusable element
|
|
737
513
|
|
|
514
|
+
_getFirstFocusableElement(node) {
|
|
515
|
+
// get a collection of elements that can be focused
|
|
516
|
+
const focusableElements = findFocusableNodes(node);
|
|
738
517
|
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
* Returns the dialog element
|
|
743
|
-
*/
|
|
518
|
+
if (!focusableElements) {
|
|
519
|
+
return null;
|
|
520
|
+
} // if found, return the first focusable element
|
|
744
521
|
|
|
745
|
-
}, {
|
|
746
|
-
key: "_getDialogElement",
|
|
747
|
-
value: function _getDialogElement(node) {
|
|
748
|
-
// If no focusable elements are found,
|
|
749
|
-
// the dialog content element itself will receive focus.
|
|
750
|
-
var dialogElement = findDOMNode(node.querySelector('[role="dialog"]')); // add tabIndex to make the Dialog focusable
|
|
751
522
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
* _directly_ from the positioner, not bubbled up from its children), close
|
|
758
|
-
* the modal.
|
|
759
|
-
*/
|
|
523
|
+
return focusableElements[0];
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Returns the dialog element
|
|
527
|
+
*/
|
|
760
528
|
|
|
761
|
-
}, {
|
|
762
|
-
key: "render",
|
|
763
|
-
value: function render() {
|
|
764
|
-
var _this$props = this.props,
|
|
765
|
-
children = _this$props.children,
|
|
766
|
-
testId = _this$props.testId;
|
|
767
529
|
|
|
768
|
-
|
|
530
|
+
_getDialogElement(node) {
|
|
531
|
+
// If no focusable elements are found,
|
|
532
|
+
// the dialog content element itself will receive focus.
|
|
533
|
+
const dialogElement = findDOMNode(node.querySelector('[role="dialog"]')); // add tabIndex to make the Dialog focusable
|
|
769
534
|
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
535
|
+
dialogElement.tabIndex = -1;
|
|
536
|
+
return dialogElement;
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* When the user clicks on the gray backdrop area (i.e., the click came
|
|
540
|
+
* _directly_ from the positioner, not bubbled up from its children), close
|
|
541
|
+
* the modal.
|
|
542
|
+
*/
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
render() {
|
|
546
|
+
const {
|
|
547
|
+
children,
|
|
548
|
+
testId
|
|
549
|
+
} = this.props;
|
|
550
|
+
const backdropProps = {
|
|
551
|
+
[ModalLauncherPortalAttributeName]: true
|
|
552
|
+
};
|
|
553
|
+
return /*#__PURE__*/createElement(View, _extends({
|
|
554
|
+
style: styles$1.modalPositioner,
|
|
555
|
+
onClick: this.handleClick,
|
|
556
|
+
testId: testId
|
|
557
|
+
}, backdropProps), children);
|
|
558
|
+
}
|
|
777
559
|
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
var styles$1 = StyleSheet.create({
|
|
560
|
+
}
|
|
561
|
+
const styles$1 = StyleSheet.create({
|
|
781
562
|
modalPositioner: {
|
|
782
563
|
position: "fixed",
|
|
783
564
|
left: 0,
|
|
@@ -799,113 +580,98 @@ var styles$1 = StyleSheet.create({
|
|
|
799
580
|
}
|
|
800
581
|
});
|
|
801
582
|
|
|
802
|
-
|
|
583
|
+
/**
|
|
584
|
+
* A UI-less component that lets `ModalLauncher` disable page scroll.
|
|
585
|
+
*
|
|
586
|
+
* The positioning of the modal requires some global page state changed
|
|
587
|
+
* unfortunately, and this handles that in an encapsulated way.
|
|
588
|
+
*
|
|
589
|
+
* NOTE(mdr): This component was copied from webapp. Be wary of sync issues. It
|
|
590
|
+
* also doesn't have unit tests, and we haven't added any, since it's a
|
|
591
|
+
* relatively stable component that has now been stress-tested lots in prod.
|
|
592
|
+
*/
|
|
593
|
+
|
|
594
|
+
const needsHackyMobileSafariScrollDisabler = (() => {
|
|
803
595
|
if (typeof window === "undefined") {
|
|
804
596
|
return false;
|
|
805
597
|
}
|
|
806
598
|
|
|
807
|
-
|
|
599
|
+
const userAgent = window.navigator.userAgent;
|
|
808
600
|
return userAgent.indexOf("iPad") > -1 || userAgent.indexOf("iPhone") > -1;
|
|
809
|
-
}();
|
|
810
|
-
|
|
811
|
-
var ScrollDisabler = /*#__PURE__*/function (_React$Component) {
|
|
812
|
-
_inherits(ScrollDisabler, _React$Component);
|
|
813
|
-
|
|
814
|
-
var _super = _createSuper(ScrollDisabler);
|
|
815
|
-
|
|
816
|
-
function ScrollDisabler() {
|
|
817
|
-
_classCallCheck(this, ScrollDisabler);
|
|
818
|
-
|
|
819
|
-
return _super.apply(this, arguments);
|
|
820
|
-
}
|
|
601
|
+
})();
|
|
821
602
|
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
var body = document.body;
|
|
603
|
+
class ScrollDisabler extends Component {
|
|
604
|
+
componentDidMount() {
|
|
605
|
+
if (ScrollDisabler.numModalsOpened === 0) {
|
|
606
|
+
const body = document.body;
|
|
827
607
|
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
608
|
+
if (!body) {
|
|
609
|
+
throw new Error("couldn't find document.body");
|
|
610
|
+
} // Prevent scrolling of the background, the first time a modal is
|
|
611
|
+
// opened.
|
|
832
612
|
|
|
833
613
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
614
|
+
ScrollDisabler.oldOverflow = body.style.overflow;
|
|
615
|
+
ScrollDisabler.oldScrollY = window.scrollY; // We need to grab all of the original style properties before we
|
|
616
|
+
// modified any of them.
|
|
837
617
|
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
618
|
+
if (needsHackyMobileSafariScrollDisabler) {
|
|
619
|
+
ScrollDisabler.oldPosition = body.style.position;
|
|
620
|
+
ScrollDisabler.oldWidth = body.style.width;
|
|
621
|
+
ScrollDisabler.oldTop = body.style.top;
|
|
622
|
+
}
|
|
843
623
|
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
624
|
+
body.style.overflow = "hidden"; // On mobile Safari, overflow: hidden is not enough, position:
|
|
625
|
+
// fixed is also required. Setting style.top = -scollTop maintains
|
|
626
|
+
// the scroll position (without which we'd scroll to the top).
|
|
847
627
|
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
}
|
|
628
|
+
if (needsHackyMobileSafariScrollDisabler) {
|
|
629
|
+
body.style.position = "fixed";
|
|
630
|
+
body.style.width = "100%";
|
|
631
|
+
body.style.top = `${-ScrollDisabler.oldScrollY}px`;
|
|
853
632
|
}
|
|
854
|
-
|
|
855
|
-
ScrollDisabler.numModalsOpened++;
|
|
856
633
|
}
|
|
857
|
-
}, {
|
|
858
|
-
key: "componentWillUnmount",
|
|
859
|
-
value: function componentWillUnmount() {
|
|
860
|
-
ScrollDisabler.numModalsOpened--;
|
|
861
|
-
|
|
862
|
-
if (ScrollDisabler.numModalsOpened === 0) {
|
|
863
|
-
var body = document.body;
|
|
864
|
-
|
|
865
|
-
if (!body) {
|
|
866
|
-
throw new Error("couldn't find document.body");
|
|
867
|
-
} // Reset all values on the closing of the final modal.
|
|
868
634
|
|
|
635
|
+
ScrollDisabler.numModalsOpened++;
|
|
636
|
+
}
|
|
869
637
|
|
|
870
|
-
|
|
638
|
+
componentWillUnmount() {
|
|
639
|
+
ScrollDisabler.numModalsOpened--;
|
|
871
640
|
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
body.style.width = ScrollDisabler.oldWidth;
|
|
875
|
-
body.style.top = ScrollDisabler.oldTop;
|
|
876
|
-
}
|
|
641
|
+
if (ScrollDisabler.numModalsOpened === 0) {
|
|
642
|
+
const body = document.body;
|
|
877
643
|
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
}, {
|
|
884
|
-
key: "render",
|
|
885
|
-
value: function render() {
|
|
886
|
-
return null;
|
|
887
|
-
}
|
|
888
|
-
}]);
|
|
644
|
+
if (!body) {
|
|
645
|
+
throw new Error("couldn't find document.body");
|
|
646
|
+
} // Reset all values on the closing of the final modal.
|
|
889
647
|
|
|
890
|
-
return ScrollDisabler;
|
|
891
|
-
}(Component);
|
|
892
648
|
|
|
893
|
-
|
|
649
|
+
body.style.overflow = ScrollDisabler.oldOverflow;
|
|
894
650
|
|
|
895
|
-
|
|
651
|
+
if (needsHackyMobileSafariScrollDisabler) {
|
|
652
|
+
body.style.position = ScrollDisabler.oldPosition;
|
|
653
|
+
body.style.width = ScrollDisabler.oldWidth;
|
|
654
|
+
body.style.top = ScrollDisabler.oldTop;
|
|
655
|
+
}
|
|
896
656
|
|
|
897
|
-
|
|
657
|
+
if (typeof window !== "undefined" && window.scrollTo) {
|
|
658
|
+
window.scrollTo(0, ScrollDisabler.oldScrollY);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
898
662
|
|
|
899
|
-
|
|
663
|
+
render() {
|
|
664
|
+
return null;
|
|
665
|
+
}
|
|
900
666
|
|
|
901
|
-
|
|
667
|
+
}
|
|
902
668
|
|
|
903
|
-
|
|
669
|
+
ScrollDisabler.numModalsOpened = 0;
|
|
904
670
|
|
|
905
|
-
|
|
671
|
+
const defaultContext = {
|
|
906
672
|
closeModal: undefined
|
|
907
673
|
};
|
|
908
|
-
var ModalContext = createContext(defaultContext);
|
|
674
|
+
var ModalContext = /*#__PURE__*/createContext(defaultContext);
|
|
909
675
|
|
|
910
676
|
/**
|
|
911
677
|
* This component enables you to launch a modal, covering the screen.
|
|
@@ -923,164 +689,128 @@ var ModalContext = createContext(defaultContext);
|
|
|
923
689
|
* like OnePaneDialog and is provided via
|
|
924
690
|
* the `modal` prop.
|
|
925
691
|
*/
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
function ModalLauncher() {
|
|
932
|
-
var _this;
|
|
933
|
-
|
|
934
|
-
_classCallCheck(this, ModalLauncher);
|
|
935
|
-
|
|
936
|
-
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
937
|
-
args[_key] = arguments[_key];
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
_this = _super.call.apply(_super, [this].concat(args));
|
|
941
|
-
|
|
942
|
-
_defineProperty(_assertThisInitialized(_this), "lastElementFocusedOutsideModal", void 0);
|
|
943
|
-
|
|
944
|
-
_defineProperty(_assertThisInitialized(_this), "state", {
|
|
692
|
+
class ModalLauncher extends Component {
|
|
693
|
+
constructor(...args) {
|
|
694
|
+
super(...args);
|
|
695
|
+
this.state = {
|
|
945
696
|
opened: false
|
|
946
|
-
}
|
|
697
|
+
};
|
|
947
698
|
|
|
948
|
-
|
|
699
|
+
this._saveLastElementFocused = () => {
|
|
949
700
|
// keep a reference of the element that triggers the modal
|
|
950
|
-
|
|
951
|
-
}
|
|
701
|
+
this.lastElementFocusedOutsideModal = document.activeElement;
|
|
702
|
+
};
|
|
952
703
|
|
|
953
|
-
|
|
954
|
-
|
|
704
|
+
this._openModal = () => {
|
|
705
|
+
this._saveLastElementFocused();
|
|
955
706
|
|
|
956
|
-
|
|
707
|
+
this.setState({
|
|
957
708
|
opened: true
|
|
958
709
|
});
|
|
959
|
-
}
|
|
710
|
+
};
|
|
960
711
|
|
|
961
|
-
|
|
962
|
-
|
|
712
|
+
this.handleCloseModal = () => {
|
|
713
|
+
this.setState({
|
|
963
714
|
opened: false
|
|
964
|
-
},
|
|
965
|
-
|
|
715
|
+
}, () => {
|
|
716
|
+
this.props.onClose && this.props.onClose();
|
|
966
717
|
|
|
967
|
-
if (
|
|
718
|
+
if (this.lastElementFocusedOutsideModal != null) {
|
|
968
719
|
// return focus to the element that triggered the modal
|
|
969
|
-
|
|
720
|
+
this.lastElementFocusedOutsideModal.focus();
|
|
970
721
|
}
|
|
971
722
|
});
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
return _this;
|
|
723
|
+
};
|
|
975
724
|
}
|
|
976
725
|
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
if (!prevProps.opened && this.props.opened) {
|
|
982
|
-
this._saveLastElementFocused();
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
}, {
|
|
986
|
-
key: "_renderModal",
|
|
987
|
-
value: function _renderModal() {
|
|
988
|
-
if (typeof this.props.modal === "function") {
|
|
989
|
-
return this.props.modal({
|
|
990
|
-
closeModal: this.handleCloseModal
|
|
991
|
-
});
|
|
992
|
-
} else {
|
|
993
|
-
return this.props.modal;
|
|
994
|
-
}
|
|
726
|
+
static getDerivedStateFromProps(props, state) {
|
|
727
|
+
if (typeof props.opened === "boolean" && props.children) {
|
|
728
|
+
// eslint-disable-next-line no-console
|
|
729
|
+
console.warn("'children' and 'opened' can't be used together");
|
|
995
730
|
}
|
|
996
|
-
}, {
|
|
997
|
-
key: "render",
|
|
998
|
-
value: function render() {
|
|
999
|
-
var renderedChildren = this.props.children ? this.props.children({
|
|
1000
|
-
openModal: this._openModal
|
|
1001
|
-
}) : null;
|
|
1002
|
-
var _document = document,
|
|
1003
|
-
body = _document.body;
|
|
1004
731
|
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
return (
|
|
1010
|
-
/*#__PURE__*/
|
|
1011
|
-
// This flow check is valid, it's the babel plugin which is broken,
|
|
1012
|
-
// see modal-context.js for details.
|
|
1013
|
-
// $FlowFixMe
|
|
1014
|
-
createElement(ModalContext.Provider, {
|
|
1015
|
-
value: {
|
|
1016
|
-
closeModal: this.handleCloseModal
|
|
1017
|
-
}
|
|
1018
|
-
}, renderedChildren, this.state.opened && createPortal(
|
|
1019
|
-
/*#__PURE__*/
|
|
1020
|
-
|
|
1021
|
-
/* We need the container View that FocusTrap creates to be at the
|
|
1022
|
-
correct z-index so that it'll be above the global nav in webapp. */
|
|
1023
|
-
createElement(FocusTrap, {
|
|
1024
|
-
style: styles$2.container
|
|
1025
|
-
}, /*#__PURE__*/createElement(ModalBackdrop, {
|
|
1026
|
-
initialFocusId: this.props.initialFocusId,
|
|
1027
|
-
testId: this.props.testId,
|
|
1028
|
-
onCloseModal: this.props.backdropDismissEnabled ? this.handleCloseModal : function () {}
|
|
1029
|
-
}, this._renderModal())), body), this.state.opened && /*#__PURE__*/createElement(ModalLauncherKeypressListener, {
|
|
1030
|
-
onClose: this.handleCloseModal
|
|
1031
|
-
}), this.state.opened && /*#__PURE__*/createElement(ScrollDisabler, null))
|
|
1032
|
-
);
|
|
732
|
+
if (typeof props.opened === "boolean" && !props.onClose) {
|
|
733
|
+
// eslint-disable-next-line no-console
|
|
734
|
+
console.warn("'onClose' should be used with 'opened'");
|
|
1033
735
|
}
|
|
1034
|
-
}], [{
|
|
1035
|
-
key: "getDerivedStateFromProps",
|
|
1036
|
-
value: function getDerivedStateFromProps(props, state) {
|
|
1037
|
-
if (typeof props.opened === "boolean" && props.children) {
|
|
1038
|
-
// eslint-disable-next-line no-console
|
|
1039
|
-
console.warn("'children' and 'opened' can't be used together");
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
if (typeof props.opened === "boolean" && !props.onClose) {
|
|
1043
|
-
// eslint-disable-next-line no-console
|
|
1044
|
-
console.warn("'onClose' should be used with 'opened'");
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
if (typeof props.opened !== "boolean" && !props.children) {
|
|
1048
|
-
// eslint-disable-next-line no-console
|
|
1049
|
-
console.warn("either 'children' or 'opened' must be set");
|
|
1050
|
-
}
|
|
1051
736
|
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
737
|
+
if (typeof props.opened !== "boolean" && !props.children) {
|
|
738
|
+
// eslint-disable-next-line no-console
|
|
739
|
+
console.warn("either 'children' or 'opened' must be set");
|
|
1055
740
|
}
|
|
1056
|
-
}]);
|
|
1057
741
|
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
742
|
+
return {
|
|
743
|
+
opened: typeof props.opened === "boolean" ? props.opened : state.opened
|
|
744
|
+
};
|
|
745
|
+
}
|
|
1061
746
|
|
|
747
|
+
componentDidUpdate(prevProps) {
|
|
748
|
+
// ensures the element is stored only when the modal is opened
|
|
749
|
+
if (!prevProps.opened && this.props.opened) {
|
|
750
|
+
this._saveLastElementFocused();
|
|
751
|
+
}
|
|
752
|
+
}
|
|
1062
753
|
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
754
|
+
_renderModal() {
|
|
755
|
+
if (typeof this.props.modal === "function") {
|
|
756
|
+
return this.props.modal({
|
|
757
|
+
closeModal: this.handleCloseModal
|
|
758
|
+
});
|
|
759
|
+
} else {
|
|
760
|
+
return this.props.modal;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
1066
763
|
|
|
1067
|
-
|
|
1068
|
-
|
|
764
|
+
render() {
|
|
765
|
+
const renderedChildren = this.props.children ? this.props.children({
|
|
766
|
+
openModal: this._openModal
|
|
767
|
+
}) : null;
|
|
768
|
+
const {
|
|
769
|
+
body
|
|
770
|
+
} = document;
|
|
1069
771
|
|
|
1070
|
-
|
|
772
|
+
if (!body) {
|
|
773
|
+
return null;
|
|
774
|
+
}
|
|
1071
775
|
|
|
1072
|
-
|
|
1073
|
-
|
|
776
|
+
return (
|
|
777
|
+
/*#__PURE__*/
|
|
778
|
+
// This flow check is valid, it's the babel plugin which is broken,
|
|
779
|
+
// see modal-context.js for details.
|
|
780
|
+
// $FlowFixMe
|
|
781
|
+
createElement(ModalContext.Provider, {
|
|
782
|
+
value: {
|
|
783
|
+
closeModal: this.handleCloseModal
|
|
784
|
+
}
|
|
785
|
+
}, renderedChildren, this.state.opened && /*#__PURE__*/createPortal(
|
|
786
|
+
/*#__PURE__*/
|
|
787
|
+
|
|
788
|
+
/* We need the container View that FocusTrap creates to be at the
|
|
789
|
+
correct z-index so that it'll be above the global nav in webapp. */
|
|
790
|
+
createElement(FocusTrap, {
|
|
791
|
+
style: styles$2.container
|
|
792
|
+
}, /*#__PURE__*/createElement(ModalBackdrop, {
|
|
793
|
+
initialFocusId: this.props.initialFocusId,
|
|
794
|
+
testId: this.props.testId,
|
|
795
|
+
onCloseModal: this.props.backdropDismissEnabled ? this.handleCloseModal : () => {}
|
|
796
|
+
}, this._renderModal())), body), this.state.opened && /*#__PURE__*/createElement(ModalLauncherKeypressListener, {
|
|
797
|
+
onClose: this.handleCloseModal
|
|
798
|
+
}), this.state.opened && /*#__PURE__*/createElement(ScrollDisabler, null))
|
|
799
|
+
);
|
|
800
|
+
}
|
|
1074
801
|
|
|
1075
|
-
|
|
802
|
+
}
|
|
803
|
+
/** A component that, when mounted, calls `onClose` when Escape is pressed. */
|
|
1076
804
|
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
805
|
+
ModalLauncher.defaultProps = {
|
|
806
|
+
backdropDismissEnabled: true
|
|
807
|
+
};
|
|
1080
808
|
|
|
1081
|
-
|
|
809
|
+
class ModalLauncherKeypressListener extends Component {
|
|
810
|
+
constructor(...args) {
|
|
811
|
+
super(...args);
|
|
1082
812
|
|
|
1083
|
-
|
|
813
|
+
this._handleKeyup = e => {
|
|
1084
814
|
// We check the key as that's keyboard layout agnostic and also avoids
|
|
1085
815
|
// the minefield of deprecated number type properties like keyCode and
|
|
1086
816
|
// which, with the replacement code, which uses a string instead.
|
|
@@ -1093,35 +823,26 @@ var ModalLauncherKeypressListener = /*#__PURE__*/function (_React$Component2) {
|
|
|
1093
823
|
// unexpectedly cancels multiple things.
|
|
1094
824
|
e.preventDefault();
|
|
1095
825
|
e.stopPropagation();
|
|
1096
|
-
|
|
1097
|
-
_this2.props.onClose();
|
|
826
|
+
this.props.onClose();
|
|
1098
827
|
}
|
|
1099
|
-
}
|
|
828
|
+
};
|
|
829
|
+
}
|
|
1100
830
|
|
|
1101
|
-
|
|
831
|
+
componentDidMount() {
|
|
832
|
+
window.addEventListener("keyup", this._handleKeyup);
|
|
1102
833
|
}
|
|
1103
834
|
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
window.addEventListener("keyup", this._handleKeyup);
|
|
1108
|
-
}
|
|
1109
|
-
}, {
|
|
1110
|
-
key: "componentWillUnmount",
|
|
1111
|
-
value: function componentWillUnmount() {
|
|
1112
|
-
window.removeEventListener("keyup", this._handleKeyup);
|
|
1113
|
-
}
|
|
1114
|
-
}, {
|
|
1115
|
-
key: "render",
|
|
1116
|
-
value: function render() {
|
|
1117
|
-
return null;
|
|
1118
|
-
}
|
|
1119
|
-
}]);
|
|
835
|
+
componentWillUnmount() {
|
|
836
|
+
window.removeEventListener("keyup", this._handleKeyup);
|
|
837
|
+
}
|
|
1120
838
|
|
|
1121
|
-
|
|
1122
|
-
|
|
839
|
+
render() {
|
|
840
|
+
return null;
|
|
841
|
+
}
|
|
1123
842
|
|
|
1124
|
-
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
const styles$2 = StyleSheet.create({
|
|
1125
846
|
container: {
|
|
1126
847
|
// This z-index is copied from the Khan Academy webapp.
|
|
1127
848
|
//
|
|
@@ -1135,51 +856,34 @@ var styles$2 = StyleSheet.create({
|
|
|
1135
856
|
/**
|
|
1136
857
|
* The Modal content included after the header
|
|
1137
858
|
*/
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
var _super = _createSuper(ModalContent);
|
|
1142
|
-
|
|
1143
|
-
function ModalContent() {
|
|
1144
|
-
_classCallCheck(this, ModalContent);
|
|
1145
|
-
|
|
1146
|
-
return _super.apply(this, arguments);
|
|
859
|
+
class ModalContent extends Component {
|
|
860
|
+
static isClassOf(instance) {
|
|
861
|
+
return instance && instance.type && instance.type.__IS_MODAL_CONTENT__;
|
|
1147
862
|
}
|
|
1148
863
|
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
});
|
|
1166
|
-
}
|
|
1167
|
-
}], [{
|
|
1168
|
-
key: "isClassOf",
|
|
1169
|
-
value: function isClassOf(instance) {
|
|
1170
|
-
return instance && instance.type && instance.type.__IS_MODAL_CONTENT__;
|
|
1171
|
-
}
|
|
1172
|
-
}]);
|
|
1173
|
-
|
|
1174
|
-
return ModalContent;
|
|
1175
|
-
}(Component);
|
|
864
|
+
render() {
|
|
865
|
+
const {
|
|
866
|
+
scrollOverflow,
|
|
867
|
+
style,
|
|
868
|
+
children
|
|
869
|
+
} = this.props;
|
|
870
|
+
return /*#__PURE__*/createElement(MediaLayout, {
|
|
871
|
+
styleSheets: styleSheets$2
|
|
872
|
+
}, ({
|
|
873
|
+
styles
|
|
874
|
+
}) => /*#__PURE__*/createElement(View, {
|
|
875
|
+
style: [styles.wrapper, scrollOverflow && styles.scrollOverflow]
|
|
876
|
+
}, /*#__PURE__*/createElement(View, {
|
|
877
|
+
style: [styles.content, style]
|
|
878
|
+
}, children)));
|
|
879
|
+
}
|
|
1176
880
|
|
|
1177
|
-
|
|
881
|
+
}
|
|
882
|
+
ModalContent.defaultProps = {
|
|
1178
883
|
scrollOverflow: true
|
|
1179
|
-
}
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
var styleSheets$2 = {
|
|
884
|
+
};
|
|
885
|
+
ModalContent.__IS_MODAL_CONTENT__ = true;
|
|
886
|
+
const styleSheets$2 = {
|
|
1183
887
|
all: StyleSheet.create({
|
|
1184
888
|
wrapper: {
|
|
1185
889
|
flex: 1,
|
|
@@ -1199,54 +903,41 @@ var styleSheets$2 = {
|
|
|
1199
903
|
}),
|
|
1200
904
|
small: StyleSheet.create({
|
|
1201
905
|
content: {
|
|
1202
|
-
padding:
|
|
906
|
+
padding: `${Spacing.xLarge_32}px ${Spacing.medium_16}px`
|
|
1203
907
|
}
|
|
1204
908
|
})
|
|
1205
909
|
};
|
|
1206
910
|
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
return
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
var _this$props = this.props,
|
|
1222
|
-
light = _this$props.light,
|
|
1223
|
-
onClick = _this$props.onClick,
|
|
1224
|
-
style = _this$props.style,
|
|
1225
|
-
testId = _this$props.testId;
|
|
1226
|
-
return /*#__PURE__*/createElement(ModalContext.Consumer, null, function (_ref) {
|
|
1227
|
-
var closeModal = _ref.closeModal;
|
|
1228
|
-
|
|
1229
|
-
if (closeModal && onClick) {
|
|
1230
|
-
throw new Error("You've specified 'onClose' on a modal when using ModalLauncher. Please specify 'onClose' on the ModalLauncher instead");
|
|
1231
|
-
}
|
|
911
|
+
class CloseButton extends Component {
|
|
912
|
+
render() {
|
|
913
|
+
const {
|
|
914
|
+
light,
|
|
915
|
+
onClick,
|
|
916
|
+
style,
|
|
917
|
+
testId
|
|
918
|
+
} = this.props;
|
|
919
|
+
return /*#__PURE__*/createElement(ModalContext.Consumer, null, ({
|
|
920
|
+
closeModal
|
|
921
|
+
}) => {
|
|
922
|
+
if (closeModal && onClick) {
|
|
923
|
+
throw new Error("You've specified 'onClose' on a modal when using ModalLauncher. Please specify 'onClose' on the ModalLauncher instead");
|
|
924
|
+
}
|
|
1232
925
|
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
});
|
|
926
|
+
return /*#__PURE__*/createElement(IconButton, {
|
|
927
|
+
icon: icons.dismiss // TODO(mdr): Translate this string for i18n.
|
|
928
|
+
// TODO(kevinb): provide a way to set this label
|
|
929
|
+
,
|
|
930
|
+
"aria-label": "Close modal",
|
|
931
|
+
onClick: onClick || closeModal,
|
|
932
|
+
kind: light ? "primary" : "tertiary",
|
|
933
|
+
light: light,
|
|
934
|
+
style: style,
|
|
935
|
+
testId: testId
|
|
1244
936
|
});
|
|
1245
|
-
}
|
|
1246
|
-
}
|
|
937
|
+
});
|
|
938
|
+
}
|
|
1247
939
|
|
|
1248
|
-
|
|
1249
|
-
}(Component);
|
|
940
|
+
}
|
|
1250
941
|
|
|
1251
942
|
/**
|
|
1252
943
|
* ModalPanel is the content container.
|
|
@@ -1268,73 +959,59 @@ var CloseButton = /*#__PURE__*/function (_React$Component) {
|
|
|
1268
959
|
* </ModalDialog>
|
|
1269
960
|
* ```
|
|
1270
961
|
*/
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
962
|
+
class ModalPanel extends Component {
|
|
963
|
+
renderMainContent() {
|
|
964
|
+
const {
|
|
965
|
+
content,
|
|
966
|
+
footer,
|
|
967
|
+
scrollOverflow
|
|
968
|
+
} = this.props;
|
|
969
|
+
const mainContent = ModalContent.isClassOf(content) ? content : /*#__PURE__*/createElement(ModalContent, null, content);
|
|
970
|
+
|
|
971
|
+
if (!mainContent) {
|
|
972
|
+
return mainContent;
|
|
973
|
+
}
|
|
1278
974
|
|
|
1279
|
-
return
|
|
975
|
+
return /*#__PURE__*/cloneElement(mainContent, {
|
|
976
|
+
// Pass the scrollOverflow and header in to the main content
|
|
977
|
+
scrollOverflow,
|
|
978
|
+
// We override the styling of the main content to help position
|
|
979
|
+
// it if there is a footer or close button being
|
|
980
|
+
// shown. We have to do this here as the ModalContent doesn't
|
|
981
|
+
// know about things being positioned around it.
|
|
982
|
+
style: [!!footer && styles$3.hasFooter, mainContent.props.style]
|
|
983
|
+
});
|
|
1280
984
|
}
|
|
1281
985
|
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
});
|
|
1304
|
-
}
|
|
1305
|
-
}, {
|
|
1306
|
-
key: "render",
|
|
1307
|
-
value: function render() {
|
|
1308
|
-
var _this$props2 = this.props,
|
|
1309
|
-
closeButtonVisible = _this$props2.closeButtonVisible,
|
|
1310
|
-
footer = _this$props2.footer,
|
|
1311
|
-
header = _this$props2.header,
|
|
1312
|
-
light = _this$props2.light,
|
|
1313
|
-
onClose = _this$props2.onClose,
|
|
1314
|
-
style = _this$props2.style,
|
|
1315
|
-
testId = _this$props2.testId;
|
|
1316
|
-
var mainContent = this.renderMainContent();
|
|
1317
|
-
return /*#__PURE__*/createElement(View, {
|
|
1318
|
-
style: [styles$3.wrapper, !light && styles$3.dark, style],
|
|
1319
|
-
testId: testId && "".concat(testId, "-panel")
|
|
1320
|
-
}, closeButtonVisible && /*#__PURE__*/createElement(CloseButton, {
|
|
1321
|
-
light: !light,
|
|
1322
|
-
onClick: onClose,
|
|
1323
|
-
style: styles$3.closeButton,
|
|
1324
|
-
testId: testId && "".concat(testId, "-close")
|
|
1325
|
-
}), header, mainContent, !footer || ModalFooter.isClassOf(footer) ? footer : /*#__PURE__*/createElement(ModalFooter, null, footer));
|
|
1326
|
-
}
|
|
1327
|
-
}]);
|
|
1328
|
-
|
|
1329
|
-
return ModalPanel;
|
|
1330
|
-
}(Component);
|
|
986
|
+
render() {
|
|
987
|
+
const {
|
|
988
|
+
closeButtonVisible,
|
|
989
|
+
footer,
|
|
990
|
+
header,
|
|
991
|
+
light,
|
|
992
|
+
onClose,
|
|
993
|
+
style,
|
|
994
|
+
testId
|
|
995
|
+
} = this.props;
|
|
996
|
+
const mainContent = this.renderMainContent();
|
|
997
|
+
return /*#__PURE__*/createElement(View, {
|
|
998
|
+
style: [styles$3.wrapper, !light && styles$3.dark, style],
|
|
999
|
+
testId: testId && `${testId}-panel`
|
|
1000
|
+
}, closeButtonVisible && /*#__PURE__*/createElement(CloseButton, {
|
|
1001
|
+
light: !light,
|
|
1002
|
+
onClick: onClose,
|
|
1003
|
+
style: styles$3.closeButton,
|
|
1004
|
+
testId: testId && `${testId}-close`
|
|
1005
|
+
}), header, mainContent, !footer || ModalFooter.isClassOf(footer) ? footer : /*#__PURE__*/createElement(ModalFooter, null, footer));
|
|
1006
|
+
}
|
|
1331
1007
|
|
|
1332
|
-
|
|
1008
|
+
}
|
|
1009
|
+
ModalPanel.defaultProps = {
|
|
1333
1010
|
closeButtonVisible: true,
|
|
1334
1011
|
scrollOverflow: true,
|
|
1335
1012
|
light: true
|
|
1336
|
-
}
|
|
1337
|
-
|
|
1013
|
+
};
|
|
1014
|
+
const styles$3 = StyleSheet.create({
|
|
1338
1015
|
wrapper: {
|
|
1339
1016
|
flex: "1 1 auto",
|
|
1340
1017
|
position: "relative",
|
|
@@ -1369,101 +1046,80 @@ var styles$3 = StyleSheet.create({
|
|
|
1369
1046
|
* The ModalHeader is required, but the ModalFooter is optional.
|
|
1370
1047
|
* The content of the dialog itself is fully customizable, but the left/right/top/bottom padding is fixed.
|
|
1371
1048
|
*/
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
});
|
|
1401
|
-
} else if (subtitle) {
|
|
1402
|
-
return /*#__PURE__*/createElement(ModalHeader, {
|
|
1403
|
-
title: title,
|
|
1404
|
-
subtitle: subtitle,
|
|
1405
|
-
titleId: uniqueId,
|
|
1406
|
-
testId: testId && "".concat(testId, "-header")
|
|
1407
|
-
});
|
|
1408
|
-
} else {
|
|
1409
|
-
return /*#__PURE__*/createElement(ModalHeader, {
|
|
1410
|
-
title: title,
|
|
1411
|
-
titleId: uniqueId,
|
|
1412
|
-
testId: testId && "".concat(testId, "-header")
|
|
1413
|
-
});
|
|
1414
|
-
}
|
|
1415
|
-
}
|
|
1416
|
-
}, {
|
|
1417
|
-
key: "render",
|
|
1418
|
-
value: function render() {
|
|
1419
|
-
var _this = this;
|
|
1420
|
-
|
|
1421
|
-
var _this$props2 = this.props,
|
|
1422
|
-
onClose = _this$props2.onClose,
|
|
1423
|
-
footer = _this$props2.footer,
|
|
1424
|
-
content = _this$props2.content,
|
|
1425
|
-
above = _this$props2.above,
|
|
1426
|
-
below = _this$props2.below,
|
|
1427
|
-
style = _this$props2.style,
|
|
1428
|
-
closeButtonVisible = _this$props2.closeButtonVisible,
|
|
1429
|
-
testId = _this$props2.testId,
|
|
1430
|
-
titleId = _this$props2.titleId,
|
|
1431
|
-
role = _this$props2.role;
|
|
1432
|
-
return /*#__PURE__*/createElement(MediaLayout, {
|
|
1433
|
-
styleSheets: styleSheets$3
|
|
1434
|
-
}, function (_ref) {
|
|
1435
|
-
var styles = _ref.styles;
|
|
1436
|
-
return /*#__PURE__*/createElement(IDProvider, {
|
|
1437
|
-
id: titleId,
|
|
1438
|
-
scope: "modal"
|
|
1439
|
-
}, function (uniqueId) {
|
|
1440
|
-
return /*#__PURE__*/createElement(ModalDialog, {
|
|
1441
|
-
style: [styles.dialog, style],
|
|
1442
|
-
above: above,
|
|
1443
|
-
below: below,
|
|
1444
|
-
testId: testId,
|
|
1445
|
-
"aria-labelledby": uniqueId,
|
|
1446
|
-
role: role
|
|
1447
|
-
}, /*#__PURE__*/createElement(ModalPanel, {
|
|
1448
|
-
onClose: onClose,
|
|
1449
|
-
header: _this.renderHeader(uniqueId),
|
|
1450
|
-
content: content,
|
|
1451
|
-
footer: footer,
|
|
1452
|
-
closeButtonVisible: closeButtonVisible,
|
|
1453
|
-
testId: testId
|
|
1454
|
-
}));
|
|
1455
|
-
});
|
|
1049
|
+
class OnePaneDialog extends Component {
|
|
1050
|
+
renderHeader(uniqueId) {
|
|
1051
|
+
const {
|
|
1052
|
+
title,
|
|
1053
|
+
breadcrumbs = undefined,
|
|
1054
|
+
subtitle = undefined,
|
|
1055
|
+
testId
|
|
1056
|
+
} = this.props;
|
|
1057
|
+
|
|
1058
|
+
if (breadcrumbs) {
|
|
1059
|
+
return /*#__PURE__*/createElement(ModalHeader, {
|
|
1060
|
+
title: title,
|
|
1061
|
+
breadcrumbs: breadcrumbs,
|
|
1062
|
+
titleId: uniqueId,
|
|
1063
|
+
testId: testId && `${testId}-header`
|
|
1064
|
+
});
|
|
1065
|
+
} else if (subtitle) {
|
|
1066
|
+
return /*#__PURE__*/createElement(ModalHeader, {
|
|
1067
|
+
title: title,
|
|
1068
|
+
subtitle: subtitle,
|
|
1069
|
+
titleId: uniqueId,
|
|
1070
|
+
testId: testId && `${testId}-header`
|
|
1071
|
+
});
|
|
1072
|
+
} else {
|
|
1073
|
+
return /*#__PURE__*/createElement(ModalHeader, {
|
|
1074
|
+
title: title,
|
|
1075
|
+
titleId: uniqueId,
|
|
1076
|
+
testId: testId && `${testId}-header`
|
|
1456
1077
|
});
|
|
1457
1078
|
}
|
|
1458
|
-
}
|
|
1079
|
+
}
|
|
1459
1080
|
|
|
1460
|
-
|
|
1461
|
-
|
|
1081
|
+
render() {
|
|
1082
|
+
const {
|
|
1083
|
+
onClose,
|
|
1084
|
+
footer,
|
|
1085
|
+
content,
|
|
1086
|
+
above,
|
|
1087
|
+
below,
|
|
1088
|
+
style,
|
|
1089
|
+
closeButtonVisible,
|
|
1090
|
+
testId,
|
|
1091
|
+
titleId,
|
|
1092
|
+
role
|
|
1093
|
+
} = this.props;
|
|
1094
|
+
return /*#__PURE__*/createElement(MediaLayout, {
|
|
1095
|
+
styleSheets: styleSheets$3
|
|
1096
|
+
}, ({
|
|
1097
|
+
styles
|
|
1098
|
+
}) => /*#__PURE__*/createElement(IDProvider, {
|
|
1099
|
+
id: titleId,
|
|
1100
|
+
scope: "modal"
|
|
1101
|
+
}, uniqueId => /*#__PURE__*/createElement(ModalDialog, {
|
|
1102
|
+
style: [styles.dialog, style],
|
|
1103
|
+
above: above,
|
|
1104
|
+
below: below,
|
|
1105
|
+
testId: testId,
|
|
1106
|
+
"aria-labelledby": uniqueId,
|
|
1107
|
+
role: role
|
|
1108
|
+
}, /*#__PURE__*/createElement(ModalPanel, {
|
|
1109
|
+
onClose: onClose,
|
|
1110
|
+
header: this.renderHeader(uniqueId),
|
|
1111
|
+
content: content,
|
|
1112
|
+
footer: footer,
|
|
1113
|
+
closeButtonVisible: closeButtonVisible,
|
|
1114
|
+
testId: testId
|
|
1115
|
+
}))));
|
|
1116
|
+
}
|
|
1462
1117
|
|
|
1463
|
-
|
|
1118
|
+
}
|
|
1119
|
+
OnePaneDialog.defaultProps = {
|
|
1464
1120
|
closeButtonVisible: true
|
|
1465
|
-
}
|
|
1466
|
-
|
|
1121
|
+
};
|
|
1122
|
+
const styleSheets$3 = {
|
|
1467
1123
|
small: StyleSheet.create({
|
|
1468
1124
|
dialog: {
|
|
1469
1125
|
width: "100%",
|
|
@@ -1490,7 +1146,7 @@ var styleSheets$3 = {
|
|
|
1490
1146
|
*/
|
|
1491
1147
|
|
|
1492
1148
|
function maybeGetNextAncestorModalLauncherPortal(element) {
|
|
1493
|
-
|
|
1149
|
+
let candidateElement = element && element.parentElement;
|
|
1494
1150
|
|
|
1495
1151
|
while (candidateElement && !candidateElement.hasAttribute(ModalLauncherPortalAttributeName)) {
|
|
1496
1152
|
candidateElement = candidateElement.parentElement;
|