@khanacademy/wonder-blocks-modal 3.0.4 → 3.0.6

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/index.js +910 -0
  3. package/package.json +11 -9
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # @khanacademy/wonder-blocks-modal
2
2
 
3
+ ## 3.0.6
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [1a5624d4]
8
+ - @khanacademy/wonder-blocks-icon@1.2.36
9
+ - @khanacademy/wonder-blocks-icon-button@3.4.20
10
+
11
+ ## 3.0.5
12
+
13
+ ### Patch Changes
14
+
15
+ - 496119f2: Cleanup WB interdependencies
16
+ - Updated dependencies [496119f2]
17
+ - @khanacademy/wonder-blocks-breadcrumbs@1.0.37
18
+ - @khanacademy/wonder-blocks-core@4.6.2
19
+ - @khanacademy/wonder-blocks-icon@1.2.35
20
+ - @khanacademy/wonder-blocks-icon-button@3.4.19
21
+ - @khanacademy/wonder-blocks-layout@1.4.15
22
+ - @khanacademy/wonder-blocks-typography@1.1.37
23
+
3
24
  ## 3.0.4
4
25
 
5
26
  ### Patch Changes
package/dist/index.js ADDED
@@ -0,0 +1,910 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var React = require('react');
6
+ var aphrodite = require('aphrodite');
7
+ var wonderBlocksLayout = require('@khanacademy/wonder-blocks-layout');
8
+ var wonderBlocksCore = require('@khanacademy/wonder-blocks-core');
9
+ var Spacing = require('@khanacademy/wonder-blocks-spacing');
10
+ var Color = require('@khanacademy/wonder-blocks-color');
11
+ var wonderBlocksTypography = require('@khanacademy/wonder-blocks-typography');
12
+ var ReactDOM = require('react-dom');
13
+ var wonderBlocksTiming = require('@khanacademy/wonder-blocks-timing');
14
+ var _extends = require('@babel/runtime/helpers/extends');
15
+ var wonderBlocksIcon = require('@khanacademy/wonder-blocks-icon');
16
+ var IconButton = require('@khanacademy/wonder-blocks-icon-button');
17
+
18
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
19
+
20
+ function _interopNamespace(e) {
21
+ if (e && e.__esModule) return e;
22
+ var n = Object.create(null);
23
+ if (e) {
24
+ Object.keys(e).forEach(function (k) {
25
+ if (k !== 'default') {
26
+ var d = Object.getOwnPropertyDescriptor(e, k);
27
+ Object.defineProperty(n, k, d.get ? d : {
28
+ enumerable: true,
29
+ get: function () { return e[k]; }
30
+ });
31
+ }
32
+ });
33
+ }
34
+ n["default"] = e;
35
+ return Object.freeze(n);
36
+ }
37
+
38
+ var React__namespace = /*#__PURE__*/_interopNamespace(React);
39
+ var Spacing__default = /*#__PURE__*/_interopDefaultLegacy(Spacing);
40
+ var Color__default = /*#__PURE__*/_interopDefaultLegacy(Color);
41
+ var ReactDOM__namespace = /*#__PURE__*/_interopNamespace(ReactDOM);
42
+ var _extends__default = /*#__PURE__*/_interopDefaultLegacy(_extends);
43
+ var IconButton__default = /*#__PURE__*/_interopDefaultLegacy(IconButton);
44
+
45
+ class ModalDialog extends React__namespace.Component {
46
+ render() {
47
+ const {
48
+ above,
49
+ below,
50
+ role,
51
+ style,
52
+ children,
53
+ testId,
54
+ "aria-labelledby": ariaLabelledBy,
55
+ "aria-describedby": ariaDescribedBy
56
+ } = this.props;
57
+ const contextValue = {
58
+ ssrSize: "large",
59
+ mediaSpec: wonderBlocksLayout.MEDIA_MODAL_SPEC
60
+ };
61
+ return React__namespace.createElement(wonderBlocksLayout.MediaLayoutContext.Provider, {
62
+ value: contextValue
63
+ }, React__namespace.createElement(wonderBlocksLayout.MediaLayout, {
64
+ styleSheets: styleSheets$3
65
+ }, ({
66
+ styles
67
+ }) => React__namespace.createElement(wonderBlocksCore.View, {
68
+ style: [styles.wrapper, style]
69
+ }, below && React__namespace.createElement(wonderBlocksCore.View, {
70
+ style: styles.below
71
+ }, below), React__namespace.createElement(wonderBlocksCore.View, {
72
+ role: role,
73
+ "aria-modal": "true",
74
+ "aria-labelledby": ariaLabelledBy,
75
+ "aria-describedby": ariaDescribedBy,
76
+ style: styles.dialog,
77
+ testId: testId
78
+ }, children), above && React__namespace.createElement(wonderBlocksCore.View, {
79
+ style: styles.above
80
+ }, above))));
81
+ }
82
+
83
+ }
84
+ ModalDialog.defaultProps = {
85
+ role: "dialog"
86
+ };
87
+ const styleSheets$3 = {
88
+ all: aphrodite.StyleSheet.create({
89
+ wrapper: {
90
+ display: "flex",
91
+ flexDirection: "row",
92
+ alignItems: "stretch",
93
+ width: "100%",
94
+ height: "100%",
95
+ position: "relative"
96
+ },
97
+ dialog: {
98
+ width: "100%",
99
+ height: "100%",
100
+ borderRadius: 4,
101
+ overflow: "hidden"
102
+ },
103
+ above: {
104
+ pointerEvents: "none",
105
+ position: "absolute",
106
+ top: 0,
107
+ left: 0,
108
+ bottom: 0,
109
+ right: 0,
110
+ zIndex: 1
111
+ },
112
+ below: {
113
+ pointerEvents: "none",
114
+ position: "absolute",
115
+ top: 0,
116
+ left: 0,
117
+ bottom: 0,
118
+ right: 0,
119
+ zIndex: -1
120
+ }
121
+ }),
122
+ small: aphrodite.StyleSheet.create({
123
+ wrapper: {
124
+ padding: Spacing__default["default"].medium_16,
125
+ flexDirection: "column"
126
+ }
127
+ })
128
+ };
129
+
130
+ class ModalFooter extends React__namespace.Component {
131
+ static isClassOf(instance) {
132
+ return instance && instance.type && instance.type.__IS_MODAL_FOOTER__;
133
+ }
134
+
135
+ render() {
136
+ const {
137
+ children
138
+ } = this.props;
139
+ return React__namespace.createElement(wonderBlocksCore.View, {
140
+ style: styles$3.footer
141
+ }, children);
142
+ }
143
+
144
+ }
145
+ ModalFooter.__IS_MODAL_FOOTER__ = true;
146
+ const styles$3 = aphrodite.StyleSheet.create({
147
+ footer: {
148
+ flex: "0 0 auto",
149
+ boxSizing: "border-box",
150
+ minHeight: Spacing__default["default"].xxxLarge_64,
151
+ paddingLeft: Spacing__default["default"].medium_16,
152
+ paddingRight: Spacing__default["default"].medium_16,
153
+ paddingTop: Spacing__default["default"].xSmall_8,
154
+ paddingBottom: Spacing__default["default"].xSmall_8,
155
+ display: "flex",
156
+ flexDirection: "row",
157
+ alignItems: "center",
158
+ justifyContent: "flex-end",
159
+ boxShadow: `0px -1px 0px ${Color__default["default"].offBlack16}`
160
+ }
161
+ });
162
+
163
+ class ModalHeader extends React__namespace.Component {
164
+ render() {
165
+ const {
166
+ breadcrumbs = undefined,
167
+ light,
168
+ subtitle = undefined,
169
+ testId,
170
+ title,
171
+ titleId
172
+ } = this.props;
173
+
174
+ if (subtitle && breadcrumbs) {
175
+ throw new Error("'subtitle' and 'breadcrumbs' can't be used together");
176
+ }
177
+
178
+ return React__namespace.createElement(wonderBlocksLayout.MediaLayout, {
179
+ styleSheets: styleSheets$2
180
+ }, ({
181
+ styles
182
+ }) => React__namespace.createElement(wonderBlocksCore.View, {
183
+ style: [styles.header, !light && styles.dark],
184
+ testId: testId
185
+ }, breadcrumbs && React__namespace.createElement(wonderBlocksCore.View, {
186
+ style: styles.breadcrumbs
187
+ }, breadcrumbs), React__namespace.createElement(wonderBlocksTypography.HeadingMedium, {
188
+ style: styles.title,
189
+ id: titleId,
190
+ testId: testId && `${testId}-title`
191
+ }, title), subtitle && React__namespace.createElement(wonderBlocksTypography.LabelSmall, {
192
+ style: light && styles.subtitle,
193
+ testId: testId && `${testId}-subtitle`
194
+ }, subtitle)));
195
+ }
196
+
197
+ }
198
+ ModalHeader.defaultProps = {
199
+ light: true
200
+ };
201
+ const styleSheets$2 = {
202
+ all: aphrodite.StyleSheet.create({
203
+ header: {
204
+ boxShadow: `0px 1px 0px ${Color__default["default"].offBlack16}`,
205
+ display: "flex",
206
+ flexDirection: "column",
207
+ minHeight: 66,
208
+ padding: `${Spacing__default["default"].large_24}px ${Spacing__default["default"].xLarge_32}px`,
209
+ position: "relative",
210
+ width: "100%"
211
+ },
212
+ dark: {
213
+ background: Color__default["default"].darkBlue,
214
+ color: Color__default["default"].white
215
+ },
216
+ breadcrumbs: {
217
+ color: Color__default["default"].offBlack64,
218
+ marginBottom: Spacing__default["default"].xSmall_8
219
+ },
220
+ title: {
221
+ paddingRight: Spacing__default["default"].medium_16
222
+ },
223
+ subtitle: {
224
+ color: Color__default["default"].offBlack64,
225
+ marginTop: Spacing__default["default"].xSmall_8
226
+ }
227
+ }),
228
+ small: aphrodite.StyleSheet.create({
229
+ header: {
230
+ paddingLeft: Spacing__default["default"].medium_16,
231
+ paddingRight: Spacing__default["default"].medium_16
232
+ },
233
+ title: {
234
+ paddingRight: Spacing__default["default"].xLarge_32
235
+ }
236
+ })
237
+ };
238
+
239
+ const FOCUSABLE_ELEMENTS$1 = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
240
+ class FocusTrap extends React__namespace.Component {
241
+ constructor(...args) {
242
+ super(...args);
243
+
244
+ this.getModalRoot = node => {
245
+ if (!node) {
246
+ return;
247
+ }
248
+
249
+ const modalRoot = ReactDOM__namespace.findDOMNode(node);
250
+
251
+ if (!modalRoot) {
252
+ throw new Error("Assertion error: modal root should exist after mount");
253
+ }
254
+
255
+ this.modalRoot = modalRoot;
256
+ };
257
+
258
+ this.handleFocusMoveToLast = () => {
259
+ this.focusElementIn(false);
260
+ };
261
+
262
+ this.handleFocusMoveToFirst = () => {
263
+ this.focusElementIn(true);
264
+ };
265
+ }
266
+
267
+ tryToFocus(node) {
268
+ if (node instanceof HTMLElement) {
269
+ try {
270
+ node.focus();
271
+ } catch (e) {}
272
+
273
+ return document.activeElement === node;
274
+ }
275
+ }
276
+
277
+ focusElementIn(isLast) {
278
+ const modalRootAsHtmlEl = this.modalRoot;
279
+ const focusableNodes = Array.from(modalRootAsHtmlEl.querySelectorAll(FOCUSABLE_ELEMENTS$1));
280
+ const nodeIndex = !isLast ? focusableNodes.length - 1 : 0;
281
+ const focusableNode = focusableNodes[nodeIndex];
282
+ this.tryToFocus(focusableNode);
283
+ }
284
+
285
+ render() {
286
+ const {
287
+ style
288
+ } = this.props;
289
+ return React__namespace.createElement(React__namespace.Fragment, null, React__namespace.createElement("div", {
290
+ tabIndex: "0",
291
+ className: "modal-focus-trap-first",
292
+ onFocus: this.handleFocusMoveToLast,
293
+ style: {
294
+ position: "fixed"
295
+ }
296
+ }), React__namespace.createElement(wonderBlocksCore.View, {
297
+ style: style,
298
+ ref: this.getModalRoot
299
+ }, this.props.children), React__namespace.createElement("div", {
300
+ tabIndex: "0",
301
+ className: "modal-focus-trap-last",
302
+ onFocus: this.handleFocusMoveToFirst,
303
+ style: {
304
+ position: "fixed"
305
+ }
306
+ }));
307
+ }
308
+
309
+ }
310
+
311
+ const ModalLauncherPortalAttributeName = "data-modal-launcher-portal";
312
+
313
+ const FOCUSABLE_ELEMENTS = 'a[href], details, input, textarea, select, button:not([aria-label^="Close"])';
314
+ function findFocusableNodes(root) {
315
+ return Array.from(root.querySelectorAll(FOCUSABLE_ELEMENTS));
316
+ }
317
+
318
+ class ModalBackdrop extends React__namespace.Component {
319
+ constructor(...args) {
320
+ super(...args);
321
+ this._mousePressedOutside = false;
322
+
323
+ this.handleMouseDown = e => {
324
+ this._mousePressedOutside = e.target === e.currentTarget;
325
+ };
326
+
327
+ this.handleMouseUp = e => {
328
+ if (e.target === e.currentTarget && this._mousePressedOutside) {
329
+ this.props.onCloseModal();
330
+ }
331
+
332
+ this._mousePressedOutside = false;
333
+ };
334
+ }
335
+
336
+ componentDidMount() {
337
+ const node = ReactDOM__namespace.findDOMNode(this);
338
+
339
+ if (!node) {
340
+ return;
341
+ }
342
+
343
+ const firstFocusableElement = this._getInitialFocusElement(node) || this._getFirstFocusableElement(node) || this._getDialogElement(node);
344
+
345
+ setTimeout(() => {
346
+ firstFocusableElement.focus();
347
+ }, 0);
348
+ }
349
+
350
+ _getInitialFocusElement(node) {
351
+ const {
352
+ initialFocusId
353
+ } = this.props;
354
+
355
+ if (!initialFocusId) {
356
+ return null;
357
+ }
358
+
359
+ return ReactDOM__namespace.findDOMNode(node.querySelector(`#${initialFocusId}`));
360
+ }
361
+
362
+ _getFirstFocusableElement(node) {
363
+ const focusableElements = findFocusableNodes(node);
364
+
365
+ if (!focusableElements) {
366
+ return null;
367
+ }
368
+
369
+ return focusableElements[0];
370
+ }
371
+
372
+ _getDialogElement(node) {
373
+ const dialogElement = ReactDOM__namespace.findDOMNode(node.querySelector('[role="dialog"]'));
374
+ dialogElement.tabIndex = -1;
375
+ return dialogElement;
376
+ }
377
+
378
+ render() {
379
+ const {
380
+ children,
381
+ testId
382
+ } = this.props;
383
+ const backdropProps = {
384
+ [ModalLauncherPortalAttributeName]: true
385
+ };
386
+ return React__namespace.createElement(wonderBlocksCore.View, _extends__default["default"]({
387
+ style: styles$2.modalPositioner,
388
+ onMouseDown: this.handleMouseDown,
389
+ onMouseUp: this.handleMouseUp,
390
+ testId: testId
391
+ }, backdropProps), children);
392
+ }
393
+
394
+ }
395
+ const styles$2 = aphrodite.StyleSheet.create({
396
+ modalPositioner: {
397
+ position: "fixed",
398
+ left: 0,
399
+ top: 0,
400
+ width: "100%",
401
+ height: "100%",
402
+ alignItems: "center",
403
+ justifyContent: "center",
404
+ overflow: "auto",
405
+ background: Color__default["default"].offBlack64
406
+ }
407
+ });
408
+
409
+ const needsHackyMobileSafariScrollDisabler = (() => {
410
+ if (typeof window === "undefined") {
411
+ return false;
412
+ }
413
+
414
+ const userAgent = window.navigator.userAgent;
415
+ return userAgent.indexOf("iPad") > -1 || userAgent.indexOf("iPhone") > -1;
416
+ })();
417
+
418
+ class ScrollDisabler extends React__namespace.Component {
419
+ componentDidMount() {
420
+ if (ScrollDisabler.numModalsOpened === 0) {
421
+ const body = document.body;
422
+
423
+ if (!body) {
424
+ throw new Error("couldn't find document.body");
425
+ }
426
+
427
+ ScrollDisabler.oldOverflow = body.style.overflow;
428
+ ScrollDisabler.oldScrollY = window.scrollY;
429
+
430
+ if (needsHackyMobileSafariScrollDisabler) {
431
+ ScrollDisabler.oldPosition = body.style.position;
432
+ ScrollDisabler.oldWidth = body.style.width;
433
+ ScrollDisabler.oldTop = body.style.top;
434
+ }
435
+
436
+ body.style.overflow = "hidden";
437
+
438
+ if (needsHackyMobileSafariScrollDisabler) {
439
+ body.style.position = "fixed";
440
+ body.style.width = "100%";
441
+ body.style.top = `${-ScrollDisabler.oldScrollY}px`;
442
+ }
443
+ }
444
+
445
+ ScrollDisabler.numModalsOpened++;
446
+ }
447
+
448
+ componentWillUnmount() {
449
+ ScrollDisabler.numModalsOpened--;
450
+
451
+ if (ScrollDisabler.numModalsOpened === 0) {
452
+ const body = document.body;
453
+
454
+ if (!body) {
455
+ throw new Error("couldn't find document.body");
456
+ }
457
+
458
+ body.style.overflow = ScrollDisabler.oldOverflow;
459
+
460
+ if (needsHackyMobileSafariScrollDisabler) {
461
+ body.style.position = ScrollDisabler.oldPosition;
462
+ body.style.width = ScrollDisabler.oldWidth;
463
+ body.style.top = ScrollDisabler.oldTop;
464
+ }
465
+
466
+ if (typeof window !== "undefined" && window.scrollTo) {
467
+ window.scrollTo(0, ScrollDisabler.oldScrollY);
468
+ }
469
+ }
470
+ }
471
+
472
+ render() {
473
+ return null;
474
+ }
475
+
476
+ }
477
+
478
+ ScrollDisabler.numModalsOpened = 0;
479
+
480
+ const defaultContext = {
481
+ closeModal: undefined
482
+ };
483
+ var ModalContext = React__namespace.createContext(defaultContext);
484
+
485
+ class ModalLauncher extends React__namespace.Component {
486
+ constructor(...args) {
487
+ super(...args);
488
+ this.state = {
489
+ opened: false
490
+ };
491
+
492
+ this._saveLastElementFocused = () => {
493
+ this.lastElementFocusedOutsideModal = document.activeElement;
494
+ };
495
+
496
+ this._openModal = () => {
497
+ this._saveLastElementFocused();
498
+
499
+ this.setState({
500
+ opened: true
501
+ });
502
+ };
503
+
504
+ this._returnFocus = () => {
505
+ const {
506
+ closedFocusId,
507
+ schedule
508
+ } = this.props;
509
+ const lastElement = this.lastElementFocusedOutsideModal;
510
+
511
+ if (closedFocusId) {
512
+ const focusElement = ReactDOM__namespace.findDOMNode(document.getElementById(closedFocusId));
513
+
514
+ if (focusElement) {
515
+ schedule.animationFrame(() => {
516
+ focusElement.focus();
517
+ });
518
+ return;
519
+ }
520
+ }
521
+
522
+ if (lastElement != null) {
523
+ schedule.animationFrame(() => {
524
+ lastElement.focus();
525
+ });
526
+ }
527
+ };
528
+
529
+ this.handleCloseModal = () => {
530
+ this.setState({
531
+ opened: false
532
+ }, () => {
533
+ const {
534
+ onClose
535
+ } = this.props;
536
+ onClose && onClose();
537
+
538
+ this._returnFocus();
539
+ });
540
+ };
541
+ }
542
+
543
+ static getDerivedStateFromProps(props, state) {
544
+ if (typeof props.opened === "boolean" && props.children) {
545
+ console.warn("'children' and 'opened' can't be used together");
546
+ }
547
+
548
+ if (typeof props.opened === "boolean" && !props.onClose) {
549
+ console.warn("'onClose' should be used with 'opened'");
550
+ }
551
+
552
+ if (typeof props.opened !== "boolean" && !props.children) {
553
+ console.warn("either 'children' or 'opened' must be set");
554
+ }
555
+
556
+ return {
557
+ opened: typeof props.opened === "boolean" ? props.opened : state.opened
558
+ };
559
+ }
560
+
561
+ componentDidUpdate(prevProps) {
562
+ if (!prevProps.opened && this.props.opened) {
563
+ this._saveLastElementFocused();
564
+ }
565
+ }
566
+
567
+ _renderModal() {
568
+ if (typeof this.props.modal === "function") {
569
+ return this.props.modal({
570
+ closeModal: this.handleCloseModal
571
+ });
572
+ } else {
573
+ return this.props.modal;
574
+ }
575
+ }
576
+
577
+ render() {
578
+ const renderedChildren = this.props.children ? this.props.children({
579
+ openModal: this._openModal
580
+ }) : null;
581
+ const {
582
+ body
583
+ } = document;
584
+
585
+ if (!body) {
586
+ return null;
587
+ }
588
+
589
+ return React__namespace.createElement(ModalContext.Provider, {
590
+ value: {
591
+ closeModal: this.handleCloseModal
592
+ }
593
+ }, renderedChildren, this.state.opened && ReactDOM__namespace.createPortal(React__namespace.createElement(FocusTrap, {
594
+ style: styles$1.container
595
+ }, React__namespace.createElement(ModalBackdrop, {
596
+ initialFocusId: this.props.initialFocusId,
597
+ testId: this.props.testId,
598
+ onCloseModal: this.props.backdropDismissEnabled ? this.handleCloseModal : () => {}
599
+ }, this._renderModal())), body), this.state.opened && React__namespace.createElement(ModalLauncherKeypressListener, {
600
+ onClose: this.handleCloseModal
601
+ }), this.state.opened && React__namespace.createElement(ScrollDisabler, null));
602
+ }
603
+
604
+ }
605
+
606
+ ModalLauncher.defaultProps = {
607
+ backdropDismissEnabled: true
608
+ };
609
+
610
+ class ModalLauncherKeypressListener extends React__namespace.Component {
611
+ constructor(...args) {
612
+ super(...args);
613
+
614
+ this._handleKeyup = e => {
615
+ if (e.key === "Escape") {
616
+ e.preventDefault();
617
+ e.stopPropagation();
618
+ this.props.onClose();
619
+ }
620
+ };
621
+ }
622
+
623
+ componentDidMount() {
624
+ window.addEventListener("keyup", this._handleKeyup);
625
+ }
626
+
627
+ componentWillUnmount() {
628
+ window.removeEventListener("keyup", this._handleKeyup);
629
+ }
630
+
631
+ render() {
632
+ return null;
633
+ }
634
+
635
+ }
636
+
637
+ const styles$1 = aphrodite.StyleSheet.create({
638
+ container: {
639
+ zIndex: 1080
640
+ }
641
+ });
642
+ var modalLauncher = wonderBlocksTiming.withActionScheduler(ModalLauncher);
643
+
644
+ class ModalContent extends React__namespace.Component {
645
+ static isClassOf(instance) {
646
+ return instance && instance.type && instance.type.__IS_MODAL_CONTENT__;
647
+ }
648
+
649
+ render() {
650
+ const {
651
+ scrollOverflow,
652
+ style,
653
+ children
654
+ } = this.props;
655
+ return React__namespace.createElement(wonderBlocksLayout.MediaLayout, {
656
+ styleSheets: styleSheets$1
657
+ }, ({
658
+ styles
659
+ }) => React__namespace.createElement(wonderBlocksCore.View, {
660
+ style: [styles.wrapper, scrollOverflow && styles.scrollOverflow]
661
+ }, React__namespace.createElement(wonderBlocksCore.View, {
662
+ style: [styles.content, style]
663
+ }, children)));
664
+ }
665
+
666
+ }
667
+ ModalContent.defaultProps = {
668
+ scrollOverflow: true
669
+ };
670
+ ModalContent.__IS_MODAL_CONTENT__ = true;
671
+ const styleSheets$1 = {
672
+ all: aphrodite.StyleSheet.create({
673
+ wrapper: {
674
+ flex: 1,
675
+ display: "block"
676
+ },
677
+ scrollOverflow: {
678
+ overflow: "auto"
679
+ },
680
+ content: {
681
+ flex: 1,
682
+ minHeight: "100%",
683
+ padding: Spacing__default["default"].xLarge_32,
684
+ boxSizing: "border-box"
685
+ }
686
+ }),
687
+ small: aphrodite.StyleSheet.create({
688
+ content: {
689
+ padding: `${Spacing__default["default"].xLarge_32}px ${Spacing__default["default"].medium_16}px`
690
+ }
691
+ })
692
+ };
693
+
694
+ class CloseButton extends React__namespace.Component {
695
+ render() {
696
+ const {
697
+ light,
698
+ onClick,
699
+ style,
700
+ testId
701
+ } = this.props;
702
+ return React__namespace.createElement(ModalContext.Consumer, null, ({
703
+ closeModal
704
+ }) => {
705
+ if (closeModal && onClick) {
706
+ throw new Error("You've specified 'onClose' on a modal when using ModalLauncher. Please specify 'onClose' on the ModalLauncher instead");
707
+ }
708
+
709
+ return React__namespace.createElement(IconButton__default["default"], {
710
+ icon: wonderBlocksIcon.icons.dismiss,
711
+ "aria-label": "Close modal",
712
+ onClick: onClick || closeModal,
713
+ kind: light ? "primary" : "tertiary",
714
+ light: light,
715
+ style: style,
716
+ testId: testId
717
+ });
718
+ });
719
+ }
720
+
721
+ }
722
+
723
+ class ModalPanel extends React__namespace.Component {
724
+ renderMainContent() {
725
+ const {
726
+ content,
727
+ footer,
728
+ scrollOverflow
729
+ } = this.props;
730
+ const mainContent = ModalContent.isClassOf(content) ? content : React__namespace.createElement(ModalContent, null, content);
731
+
732
+ if (!mainContent) {
733
+ return mainContent;
734
+ }
735
+
736
+ return React__namespace.cloneElement(mainContent, {
737
+ scrollOverflow,
738
+ style: [!!footer && styles.hasFooter, mainContent.props.style]
739
+ });
740
+ }
741
+
742
+ render() {
743
+ const {
744
+ closeButtonVisible,
745
+ footer,
746
+ header,
747
+ light,
748
+ onClose,
749
+ style,
750
+ testId
751
+ } = this.props;
752
+ const mainContent = this.renderMainContent();
753
+ return React__namespace.createElement(wonderBlocksCore.View, {
754
+ style: [styles.wrapper, !light && styles.dark, style],
755
+ testId: testId && `${testId}-panel`
756
+ }, closeButtonVisible && React__namespace.createElement(CloseButton, {
757
+ light: !light,
758
+ onClick: onClose,
759
+ style: styles.closeButton,
760
+ testId: testId && `${testId}-close`
761
+ }), header, mainContent, !footer || ModalFooter.isClassOf(footer) ? footer : React__namespace.createElement(ModalFooter, null, footer));
762
+ }
763
+
764
+ }
765
+ ModalPanel.defaultProps = {
766
+ closeButtonVisible: true,
767
+ scrollOverflow: true,
768
+ light: true
769
+ };
770
+ const styles = aphrodite.StyleSheet.create({
771
+ wrapper: {
772
+ flex: "1 1 auto",
773
+ position: "relative",
774
+ display: "flex",
775
+ flexDirection: "column",
776
+ background: "white",
777
+ boxSizing: "border-box",
778
+ overflow: "hidden",
779
+ height: "100%",
780
+ width: "100%"
781
+ },
782
+ closeButton: {
783
+ position: "absolute",
784
+ right: Spacing__default["default"].medium_16,
785
+ top: Spacing__default["default"].medium_16,
786
+ zIndex: 1
787
+ },
788
+ dark: {
789
+ background: Color__default["default"].darkBlue,
790
+ color: Color__default["default"].white
791
+ },
792
+ hasFooter: {
793
+ paddingBottom: Spacing__default["default"].xLarge_32
794
+ }
795
+ });
796
+
797
+ class OnePaneDialog extends React__namespace.Component {
798
+ renderHeader(uniqueId) {
799
+ const {
800
+ title,
801
+ breadcrumbs = undefined,
802
+ subtitle = undefined,
803
+ testId
804
+ } = this.props;
805
+
806
+ if (breadcrumbs) {
807
+ return React__namespace.createElement(ModalHeader, {
808
+ title: title,
809
+ breadcrumbs: breadcrumbs,
810
+ titleId: uniqueId,
811
+ testId: testId && `${testId}-header`
812
+ });
813
+ } else if (subtitle) {
814
+ return React__namespace.createElement(ModalHeader, {
815
+ title: title,
816
+ subtitle: subtitle,
817
+ titleId: uniqueId,
818
+ testId: testId && `${testId}-header`
819
+ });
820
+ } else {
821
+ return React__namespace.createElement(ModalHeader, {
822
+ title: title,
823
+ titleId: uniqueId,
824
+ testId: testId && `${testId}-header`
825
+ });
826
+ }
827
+ }
828
+
829
+ render() {
830
+ const {
831
+ onClose,
832
+ footer,
833
+ content,
834
+ above,
835
+ below,
836
+ style,
837
+ closeButtonVisible,
838
+ testId,
839
+ titleId,
840
+ role,
841
+ "aria-describedby": ariaDescribedBy
842
+ } = this.props;
843
+ return React__namespace.createElement(wonderBlocksLayout.MediaLayout, {
844
+ styleSheets: styleSheets
845
+ }, ({
846
+ styles
847
+ }) => React__namespace.createElement(wonderBlocksCore.IDProvider, {
848
+ id: titleId,
849
+ scope: "modal"
850
+ }, uniqueId => React__namespace.createElement(ModalDialog, {
851
+ style: [styles.dialog, style],
852
+ above: above,
853
+ below: below,
854
+ testId: testId,
855
+ "aria-labelledby": uniqueId,
856
+ "aria-describedby": ariaDescribedBy,
857
+ role: role
858
+ }, React__namespace.createElement(ModalPanel, {
859
+ onClose: onClose,
860
+ header: this.renderHeader(uniqueId),
861
+ content: content,
862
+ footer: footer,
863
+ closeButtonVisible: closeButtonVisible,
864
+ testId: testId
865
+ }))));
866
+ }
867
+
868
+ }
869
+ OnePaneDialog.defaultProps = {
870
+ closeButtonVisible: true
871
+ };
872
+ const styleSheets = {
873
+ small: aphrodite.StyleSheet.create({
874
+ dialog: {
875
+ width: "100%",
876
+ height: "100%",
877
+ overflow: "hidden"
878
+ }
879
+ }),
880
+ mdOrLarger: aphrodite.StyleSheet.create({
881
+ dialog: {
882
+ width: "93.75%",
883
+ maxWidth: 576,
884
+ height: "81.25%",
885
+ maxHeight: 624
886
+ }
887
+ })
888
+ };
889
+
890
+ function maybeGetNextAncestorModalLauncherPortal(element) {
891
+ let candidateElement = element && element.parentElement;
892
+
893
+ while (candidateElement && !candidateElement.hasAttribute(ModalLauncherPortalAttributeName)) {
894
+ candidateElement = candidateElement.parentElement;
895
+ }
896
+
897
+ return candidateElement;
898
+ }
899
+
900
+ function maybeGetPortalMountedModalHostElement(element) {
901
+ return maybeGetNextAncestorModalLauncherPortal(element);
902
+ }
903
+
904
+ exports.ModalDialog = ModalDialog;
905
+ exports.ModalFooter = ModalFooter;
906
+ exports.ModalHeader = ModalHeader;
907
+ exports.ModalLauncher = modalLauncher;
908
+ exports.ModalPanel = ModalPanel;
909
+ exports.OnePaneDialog = OnePaneDialog;
910
+ exports.maybeGetPortalMountedModalHostElement = maybeGetPortalMountedModalHostElement;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanacademy/wonder-blocks-modal",
3
- "version": "3.0.4",
3
+ "version": "3.0.6",
4
4
  "design": "v2",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -16,14 +16,15 @@
16
16
  "license": "MIT",
17
17
  "dependencies": {
18
18
  "@babel/runtime": "^7.18.6",
19
- "@khanacademy/wonder-blocks-breadcrumbs": "^1.0.36",
19
+ "@khanacademy/wonder-blocks-breadcrumbs": "^1.0.37",
20
20
  "@khanacademy/wonder-blocks-color": "^1.2.0",
21
- "@khanacademy/wonder-blocks-core": "^4.6.1",
22
- "@khanacademy/wonder-blocks-icon": "^1.2.34",
23
- "@khanacademy/wonder-blocks-icon-button": "^3.4.18",
24
- "@khanacademy/wonder-blocks-layout": "^1.4.14",
21
+ "@khanacademy/wonder-blocks-core": "^4.6.2",
22
+ "@khanacademy/wonder-blocks-icon": "^1.2.36",
23
+ "@khanacademy/wonder-blocks-icon-button": "^3.4.20",
24
+ "@khanacademy/wonder-blocks-layout": "^1.4.15",
25
25
  "@khanacademy/wonder-blocks-spacing": "^3.0.5",
26
- "@khanacademy/wonder-blocks-typography": "^1.1.36"
26
+ "@khanacademy/wonder-blocks-timing": "^2.1.0",
27
+ "@khanacademy/wonder-blocks-typography": "^1.1.37"
27
28
  },
28
29
  "peerDependencies": {
29
30
  "aphrodite": "^1.2.5",
@@ -31,6 +32,7 @@
31
32
  "react-dom": "16.14.0"
32
33
  },
33
34
  "devDependencies": {
34
- "wb-dev-build-settings": "^0.6.0"
35
+ "@khanacademy/wonder-blocks-breadcrumbs": "^1.0.37",
36
+ "wb-dev-build-settings": "^0.7.0"
35
37
  }
36
- }
38
+ }