@khanacademy/wonder-blocks-modal 2.3.0 → 2.3.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @khanacademy/wonder-blocks-modal
2
2
 
3
+ ## 2.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - @khanacademy/wonder-blocks-breadcrumbs@1.0.30
8
+ - @khanacademy/wonder-blocks-core@4.3.1
9
+ - @khanacademy/wonder-blocks-icon@1.2.27
10
+ - @khanacademy/wonder-blocks-icon-button@3.4.6
11
+ - @khanacademy/wonder-blocks-layout@1.4.9
12
+ - @khanacademy/wonder-blocks-toolbar@2.1.31
13
+ - @khanacademy/wonder-blocks-typography@1.1.31
14
+
3
15
  ## 2.3.0
4
16
 
5
17
  ### Minor Changes
package/dist/es/index.js CHANGED
@@ -11,16 +11,6 @@ import _extends from '@babel/runtime/helpers/extends';
11
11
  import { icons } from '@khanacademy/wonder-blocks-icon';
12
12
  import IconButton from '@khanacademy/wonder-blocks-icon-button';
13
13
 
14
- /**
15
- * `ModalDialog` is a component that contains these elements:
16
- * - The visual dialog element itself (`<div role="dialog"/>`)
17
- * - The custom contents below and/or above the Dialog itself (e.g. decorative graphics).
18
- *
19
- * **Accessibility notes:**
20
- * - By default (e.g. using `OnePaneDialog`), `aria-labelledby` is populated automatically using the dialog title `id`.
21
- * - If there is a custom Dialog implementation (e.g. `TwoPaneDialog`), the dialog element doesn’t have to have
22
- * the `aria-labelledby` attribute however this is recommended. It should match the `id` of the dialog title.
23
- */
24
14
  class ModalDialog extends React.Component {
25
15
  render() {
26
16
  const {
@@ -36,23 +26,23 @@ class ModalDialog extends React.Component {
36
26
  ssrSize: "large",
37
27
  mediaSpec: MEDIA_MODAL_SPEC
38
28
  };
39
- return /*#__PURE__*/React.createElement(MediaLayoutContext.Provider, {
29
+ return React.createElement(MediaLayoutContext.Provider, {
40
30
  value: contextValue
41
- }, /*#__PURE__*/React.createElement(MediaLayout, {
31
+ }, React.createElement(MediaLayout, {
42
32
  styleSheets: styleSheets$3
43
33
  }, ({
44
34
  styles
45
- }) => /*#__PURE__*/React.createElement(View, {
35
+ }) => React.createElement(View, {
46
36
  style: [styles.wrapper, style]
47
- }, below && /*#__PURE__*/React.createElement(View, {
37
+ }, below && React.createElement(View, {
48
38
  style: styles.below
49
- }, below), /*#__PURE__*/React.createElement(View, {
39
+ }, below), React.createElement(View, {
50
40
  role: role,
51
41
  "aria-modal": "true",
52
42
  "aria-labelledby": ariaLabelledBy,
53
43
  style: styles.dialog,
54
44
  testId: testId
55
- }, children), above && /*#__PURE__*/React.createElement(View, {
45
+ }, children), above && React.createElement(View, {
56
46
  style: styles.above
57
47
  }, above))));
58
48
  }
@@ -71,10 +61,6 @@ const styleSheets$3 = {
71
61
  height: "100%",
72
62
  position: "relative"
73
63
  },
74
-
75
- /**
76
- * Ensures the dialog container uses the container size
77
- */
78
64
  dialog: {
79
65
  width: "100%",
80
66
  height: "100%",
@@ -108,15 +94,6 @@ const styleSheets$3 = {
108
94
  })
109
95
  };
110
96
 
111
- /**
112
- * Modal footer included after the content.
113
- *
114
- * **Implementation notes**:
115
- *
116
- * If you are creating a custom Dialog, make sure to follow these guidelines:
117
- * - Make sure to include it as part of [ModalPanel](/#modalpanel) by using the `footer` prop.
118
- * - The footer is completely flexible. Meaning the developer needs to add its own custom layout to match design specs.
119
- */
120
97
  class ModalFooter extends React.Component {
121
98
  static isClassOf(instance) {
122
99
  return instance && instance.type && instance.type.__IS_MODAL_FOOTER__;
@@ -126,7 +103,7 @@ class ModalFooter extends React.Component {
126
103
  const {
127
104
  children
128
105
  } = this.props;
129
- return /*#__PURE__*/React.createElement(View, {
106
+ return React.createElement(View, {
130
107
  style: styles$3.footer
131
108
  }, children);
132
109
  }
@@ -150,49 +127,6 @@ const styles$3 = StyleSheet.create({
150
127
  }
151
128
  });
152
129
 
153
- /**
154
- * This is a helper component that is never rendered by itself. It is always
155
- * pinned to the top of the dialog, is responsive using the same behavior as its
156
- * parent dialog, and has the following properties:
157
- * - title
158
- * - breadcrumb OR subtitle, but not both.
159
- *
160
- * **Accessibility notes:**
161
- *
162
- * - By default (e.g. using [OnePaneDialog](/#onepanedialog)), `titleId` is
163
- * populated automatically by the parent container.
164
- * - If there is a custom Dialog implementation (e.g. `TwoPaneDialog`), the
165
- * ModalHeader doesn’t have to have the `titleId` prop however this is
166
- * recommended. It should match the `aria-labelledby` prop of the
167
- * [ModalDialog](/#modaldialog) component. If you want to see an example of
168
- * how to generate this ID, check [IDProvider](/#idprovider).
169
- *
170
- * **Implementation notes:**
171
- *
172
- * If you are creating a custom Dialog, make sure to follow these guidelines:
173
- * - Make sure to include it as part of [ModalPanel](/#modalpanel) by using the
174
- * `header` prop.
175
- * - Add a title (required).
176
- * - Optionally add a subtitle or breadcrumbs.
177
- * - We encourage you to add `titleId` (see Accessibility notes).
178
- * - If the `ModalPanel` has a dark background, make sure to set `light` to
179
- * `false`.
180
- * - If you need to create e2e tests, make sure to pass a `testId` prop and
181
- * add a sufix to scope the testId to this component: e.g.
182
- * `some-random-id-ModalHeader`. This scope will also be passed to the title
183
- * and subtitle elements: e.g. `some-random-id-ModalHeader-title`.
184
- *
185
- * Example:
186
- *
187
- * ```js
188
- * <ModalHeader
189
- * title="Sidebar using ModalHeader"
190
- * subtitle="subtitle"
191
- * titleId="uniqueTitleId"
192
- * light={false}
193
- * />
194
- * ```
195
- */
196
130
  class ModalHeader extends React.Component {
197
131
  render() {
198
132
  const {
@@ -208,20 +142,20 @@ class ModalHeader extends React.Component {
208
142
  throw new Error("'subtitle' and 'breadcrumbs' can't be used together");
209
143
  }
210
144
 
211
- return /*#__PURE__*/React.createElement(MediaLayout, {
145
+ return React.createElement(MediaLayout, {
212
146
  styleSheets: styleSheets$2
213
147
  }, ({
214
148
  styles
215
- }) => /*#__PURE__*/React.createElement(View, {
149
+ }) => React.createElement(View, {
216
150
  style: [styles.header, !light && styles.dark],
217
151
  testId: testId
218
- }, breadcrumbs && /*#__PURE__*/React.createElement(View, {
152
+ }, breadcrumbs && React.createElement(View, {
219
153
  style: styles.breadcrumbs
220
- }, breadcrumbs), /*#__PURE__*/React.createElement(HeadingMedium, {
154
+ }, breadcrumbs), React.createElement(HeadingMedium, {
221
155
  style: styles.title,
222
156
  id: titleId,
223
157
  testId: testId && `${testId}-title`
224
- }, title), subtitle && /*#__PURE__*/React.createElement(LabelSmall, {
158
+ }, title), subtitle && React.createElement(LabelSmall, {
225
159
  style: light && styles.subtitle,
226
160
  testId: testId && `${testId}-subtitle`
227
161
  }, subtitle)));
@@ -251,7 +185,6 @@ const styleSheets$2 = {
251
185
  marginBottom: Spacing.xSmall_8
252
186
  },
253
187
  title: {
254
- // Prevent title from overlapping the close button
255
188
  paddingRight: Spacing.medium_16
256
189
  },
257
190
  subtitle: {
@@ -271,22 +204,11 @@ const styleSheets$2 = {
271
204
  };
272
205
 
273
206
  class FocusTrap extends React.Component {
274
- /** The most recent node _inside this component_ to receive focus. */
275
-
276
- /**
277
- * Whether we're currently applying programmatic focus, and should therefore
278
- * ignore focus change events.
279
- */
280
-
281
- /**
282
- * Tabbing is restricted to descendents of this element.
283
- */
284
207
  constructor(props) {
285
208
  super(props);
286
209
 
287
210
  this.getModalRoot = node => {
288
211
  if (!node) {
289
- // The component is being umounted
290
212
  return;
291
213
  }
292
214
 
@@ -300,8 +222,6 @@ class FocusTrap extends React.Component {
300
222
  };
301
223
 
302
224
  this.handleGlobalFocus = e => {
303
- // If we're busy applying our own programmatic focus, we ignore focus
304
- // changes, to avoid an infinite loop.
305
225
  if (this.ignoreFocusChanges) {
306
226
  return;
307
227
  }
@@ -309,7 +229,6 @@ class FocusTrap extends React.Component {
309
229
  const target = e.target;
310
230
 
311
231
  if (!(target instanceof Node)) {
312
- // Sometimes focus events trigger on the document itself. Ignore!
313
232
  return;
314
233
  }
315
234
 
@@ -320,25 +239,13 @@ class FocusTrap extends React.Component {
320
239
  }
321
240
 
322
241
  if (modalRoot.contains(target)) {
323
- // If the newly focused node is inside the modal, we just keep track
324
- // of that.
325
242
  this.lastNodeFocusedInModal = target;
326
243
  } else {
327
- // If the newly focused node is outside the modal, we try refocusing
328
- // the first focusable node of the modal. (This could be the user
329
- // pressing Tab on the last node of the modal, or focus escaping in
330
- // some other way.)
331
- this.focusFirstElementIn(modalRoot); // But, if it turns out that the first focusable node of the modal
332
- // was what we were previously focusing, then this is probably the
333
- // user pressing Shift-Tab on the first node, wanting to go to the
334
- // end. So, we instead try focusing the last focusable node of the
335
- // modal.
244
+ this.focusFirstElementIn(modalRoot);
336
245
 
337
246
  if (document.activeElement === this.lastNodeFocusedInModal) {
338
247
  this.focusLastElementIn(modalRoot);
339
- } // Focus should now be inside the modal, so record the newly-focused
340
- // node as the last node focused in the modal.
341
-
248
+ }
342
249
 
343
250
  this.lastNodeFocusedInModal = document.activeElement;
344
251
  }
@@ -356,27 +263,18 @@ class FocusTrap extends React.Component {
356
263
  window.removeEventListener("focus", this.handleGlobalFocus, true);
357
264
  }
358
265
 
359
- /** Try to focus the given node. Return true iff successful. */
360
266
  tryToFocus(node) {
361
267
  if (node instanceof HTMLElement) {
362
268
  this.ignoreFocusChanges = true;
363
269
 
364
270
  try {
365
271
  node.focus();
366
- } catch (e) {// ignore error
367
- }
272
+ } catch (e) {}
368
273
 
369
274
  this.ignoreFocusChanges = false;
370
275
  return document.activeElement === node;
371
276
  }
372
277
  }
373
- /**
374
- * Focus the first focusable descendant of the given node.
375
- *
376
- * Return true if we succeed. Or, if the given node has no focusable
377
- * descendants, return false.
378
- */
379
-
380
278
 
381
279
  focusFirstElementIn(currentParent) {
382
280
  const children = currentParent.childNodes;
@@ -391,13 +289,6 @@ class FocusTrap extends React.Component {
391
289
 
392
290
  return false;
393
291
  }
394
- /**
395
- * Focus the last focusable descendant of the given node.
396
- *
397
- * Return true if we succeed. Or, if the given node has no focusable
398
- * descendants, return false.
399
- */
400
-
401
292
 
402
293
  focusLastElementIn(currentParent) {
403
294
  const children = currentParent.childNodes;
@@ -412,22 +303,20 @@ class FocusTrap extends React.Component {
412
303
 
413
304
  return false;
414
305
  }
415
- /** This method is called when any node on the page is focused. */
416
-
417
306
 
418
307
  render() {
419
308
  const {
420
309
  style
421
310
  } = this.props;
422
- return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
311
+ return React.createElement(React.Fragment, null, React.createElement("div", {
423
312
  tabIndex: "0",
424
313
  style: {
425
314
  position: "fixed"
426
315
  }
427
- }), /*#__PURE__*/React.createElement(View, {
316
+ }), React.createElement(View, {
428
317
  style: style,
429
318
  ref: this.getModalRoot
430
- }, this.props.children), /*#__PURE__*/React.createElement("div", {
319
+ }, this.props.children), React.createElement("div", {
431
320
  tabIndex: "0",
432
321
  style: {
433
322
  position: "fixed"
@@ -437,43 +326,23 @@ class FocusTrap extends React.Component {
437
326
 
438
327
  }
439
328
 
440
- /**
441
- * The attribute used to identify a modal launcher portal.
442
- */
443
329
  const ModalLauncherPortalAttributeName = "data-modal-launcher-portal";
444
330
 
445
- /**
446
- * List of elements that can be focused
447
- * @see https://www.w3.org/TR/html5/editing.html#can-be-focused
448
- */
449
331
  const FOCUSABLE_ELEMENTS = 'a[href], details, input, textarea, select, button:not([aria-label^="Close"])';
450
332
  function findFocusableNodes(root) {
451
333
  return Array.from(root.querySelectorAll(FOCUSABLE_ELEMENTS));
452
334
  }
453
335
 
454
- /**
455
- * A private component used by ModalLauncher. This is the fixed-position
456
- * container element that gets mounted outside the DOM. It overlays the modal
457
- * content (provided as `children`) over the content, with a gray backdrop
458
- * behind it.
459
- *
460
- * This component is also responsible for cloning the provided modal `children`,
461
- * and adding an `onClose` prop that will call `onCloseModal`. If an
462
- * `onClose` prop is already provided, the two are merged.
463
- */
464
336
  class ModalBackdrop extends React.Component {
465
337
  constructor(...args) {
466
338
  super(...args);
467
339
  this._mousePressedOutside = false;
468
340
 
469
341
  this.handleMouseDown = e => {
470
- // Confirm that it is the backdrop that is being clicked, not the child
471
342
  this._mousePressedOutside = e.target === e.currentTarget;
472
343
  };
473
344
 
474
345
  this.handleMouseUp = e => {
475
- // Confirm that it is the backdrop that is being clicked, not the child
476
- // and that the mouse was pressed in the backdrop first.
477
346
  if (e.target === e.currentTarget && this._mousePressedOutside) {
478
347
  this.props.onCloseModal();
479
348
  }
@@ -489,20 +358,13 @@ class ModalBackdrop extends React.Component {
489
358
  return;
490
359
  }
491
360
 
492
- const firstFocusableElement = // 1. try to get element specified by the user
493
- this._getInitialFocusElement(node) || // 2. get first occurence from list of focusable elements
494
- this._getFirstFocusableElement(node) || // 3. get the dialog itself
495
- this._getDialogElement(node); // wait for styles to applied
496
-
361
+ const firstFocusableElement = this._getInitialFocusElement(node) || this._getFirstFocusableElement(node) || this._getDialogElement(node);
497
362
 
498
363
  setTimeout(() => {
499
364
  firstFocusableElement.focus();
500
365
  }, 0);
501
366
  }
502
367
 
503
- /**
504
- * Returns an element specified by the user
505
- */
506
368
  _getInitialFocusElement(node) {
507
369
  const {
508
370
  initialFocusId
@@ -514,41 +376,22 @@ class ModalBackdrop extends React.Component {
514
376
 
515
377
  return ReactDOM.findDOMNode(node.querySelector(`#${initialFocusId}`));
516
378
  }
517
- /**
518
- * Returns the first focusable element found inside the Dialog
519
- */
520
-
521
379
 
522
380
  _getFirstFocusableElement(node) {
523
- // get a collection of elements that can be focused
524
381
  const focusableElements = findFocusableNodes(node);
525
382
 
526
383
  if (!focusableElements) {
527
384
  return null;
528
- } // if found, return the first focusable element
529
-
385
+ }
530
386
 
531
387
  return focusableElements[0];
532
388
  }
533
- /**
534
- * Returns the dialog element
535
- */
536
-
537
389
 
538
390
  _getDialogElement(node) {
539
- // If no focusable elements are found,
540
- // the dialog content element itself will receive focus.
541
- const dialogElement = ReactDOM.findDOMNode(node.querySelector('[role="dialog"]')); // add tabIndex to make the Dialog focusable
542
-
391
+ const dialogElement = ReactDOM.findDOMNode(node.querySelector('[role="dialog"]'));
543
392
  dialogElement.tabIndex = -1;
544
393
  return dialogElement;
545
394
  }
546
- /**
547
- * When the user clicks on the gray backdrop area (i.e., the click came
548
- * _directly_ from the positioner, not bubbled up from its children), close
549
- * the modal.
550
- */
551
-
552
395
 
553
396
  render() {
554
397
  const {
@@ -558,7 +401,7 @@ class ModalBackdrop extends React.Component {
558
401
  const backdropProps = {
559
402
  [ModalLauncherPortalAttributeName]: true
560
403
  };
561
- return /*#__PURE__*/React.createElement(View, _extends({
404
+ return React.createElement(View, _extends({
562
405
  style: styles$2.modalPositioner,
563
406
  onMouseDown: this.handleMouseDown,
564
407
  onMouseUp: this.handleMouseUp,
@@ -576,30 +419,11 @@ const styles$2 = StyleSheet.create({
576
419
  height: "100%",
577
420
  alignItems: "center",
578
421
  justifyContent: "center",
579
- // If the modal ends up being too big for the viewport (e.g., the min
580
- // height is triggered), add another scrollbar specifically for
581
- // scrolling modal content.
582
- //
583
- // TODO(mdr): The specified behavior is that the modal should scroll
584
- // with the rest of the page, rather than separately, if overflow
585
- // turns out to be necessary. That sounds hard to do; punting for
586
- // now!
587
422
  overflow: "auto",
588
423
  background: Color.offBlack64
589
424
  }
590
425
  });
591
426
 
592
- /**
593
- * A UI-less component that lets `ModalLauncher` disable page scroll.
594
- *
595
- * The positioning of the modal requires some global page state changed
596
- * unfortunately, and this handles that in an encapsulated way.
597
- *
598
- * NOTE(mdr): This component was copied from webapp. Be wary of sync issues. It
599
- * also doesn't have unit tests, and we haven't added any, since it's a
600
- * relatively stable component that has now been stress-tested lots in prod.
601
- */
602
-
603
427
  const needsHackyMobileSafariScrollDisabler = (() => {
604
428
  if (typeof window === "undefined") {
605
429
  return false;
@@ -616,13 +440,10 @@ class ScrollDisabler extends React.Component {
616
440
 
617
441
  if (!body) {
618
442
  throw new Error("couldn't find document.body");
619
- } // Prevent scrolling of the background, the first time a modal is
620
- // opened.
621
-
443
+ }
622
444
 
623
445
  ScrollDisabler.oldOverflow = body.style.overflow;
624
- ScrollDisabler.oldScrollY = window.scrollY; // We need to grab all of the original style properties before we
625
- // modified any of them.
446
+ ScrollDisabler.oldScrollY = window.scrollY;
626
447
 
627
448
  if (needsHackyMobileSafariScrollDisabler) {
628
449
  ScrollDisabler.oldPosition = body.style.position;
@@ -630,9 +451,7 @@ class ScrollDisabler extends React.Component {
630
451
  ScrollDisabler.oldTop = body.style.top;
631
452
  }
632
453
 
633
- body.style.overflow = "hidden"; // On mobile Safari, overflow: hidden is not enough, position:
634
- // fixed is also required. Setting style.top = -scollTop maintains
635
- // the scroll position (without which we'd scroll to the top).
454
+ body.style.overflow = "hidden";
636
455
 
637
456
  if (needsHackyMobileSafariScrollDisabler) {
638
457
  body.style.position = "fixed";
@@ -652,8 +471,7 @@ class ScrollDisabler extends React.Component {
652
471
 
653
472
  if (!body) {
654
473
  throw new Error("couldn't find document.body");
655
- } // Reset all values on the closing of the final modal.
656
-
474
+ }
657
475
 
658
476
  body.style.overflow = ScrollDisabler.oldOverflow;
659
477
 
@@ -680,24 +498,8 @@ ScrollDisabler.numModalsOpened = 0;
680
498
  const defaultContext = {
681
499
  closeModal: undefined
682
500
  };
683
- var ModalContext = /*#__PURE__*/React.createContext(defaultContext);
684
-
685
- /**
686
- * This component enables you to launch a modal, covering the screen.
687
- *
688
- * Children have access to `openModal` function via the function-as-children
689
- * pattern, so one common use case is for this component to wrap a button:
690
- *
691
- * ```js
692
- * <ModalLauncher modal={<TwoColumnModal ... />}>
693
- * {({openModal}) => <button onClick={openModal}>Learn more</button>}
694
- * </ModalLauncher>
695
- * ```
696
- *
697
- * The actual modal itself is constructed separately, using a layout component
698
- * like OnePaneDialog and is provided via
699
- * the `modal` prop.
700
- */
501
+ var ModalContext = React.createContext(defaultContext);
502
+
701
503
  class ModalLauncher extends React.Component {
702
504
  constructor(...args) {
703
505
  super(...args);
@@ -706,7 +508,6 @@ class ModalLauncher extends React.Component {
706
508
  };
707
509
 
708
510
  this._saveLastElementFocused = () => {
709
- // keep a reference of the element that triggers the modal
710
511
  this.lastElementFocusedOutsideModal = document.activeElement;
711
512
  };
712
513
 
@@ -723,14 +524,12 @@ class ModalLauncher extends React.Component {
723
524
  closedFocusId,
724
525
  schedule
725
526
  } = this.props;
726
- const lastElement = this.lastElementFocusedOutsideModal; // Focus on the specified element after closing the modal.
527
+ const lastElement = this.lastElementFocusedOutsideModal;
727
528
 
728
529
  if (closedFocusId) {
729
530
  const focusElement = ReactDOM.findDOMNode(document.getElementById(closedFocusId));
730
531
 
731
532
  if (focusElement) {
732
- // Wait for the modal to leave the DOM before trying
733
- // to focus on the specified element.
734
533
  schedule.animationFrame(() => {
735
534
  focusElement.focus();
736
535
  });
@@ -739,8 +538,6 @@ class ModalLauncher extends React.Component {
739
538
  }
740
539
 
741
540
  if (lastElement != null) {
742
- // Wait for the modal to leave the DOM before trying to
743
- // return focus to the element that triggered the modal.
744
541
  schedule.animationFrame(() => {
745
542
  lastElement.focus();
746
543
  });
@@ -763,17 +560,14 @@ class ModalLauncher extends React.Component {
763
560
 
764
561
  static getDerivedStateFromProps(props, state) {
765
562
  if (typeof props.opened === "boolean" && props.children) {
766
- // eslint-disable-next-line no-console
767
563
  console.warn("'children' and 'opened' can't be used together");
768
564
  }
769
565
 
770
566
  if (typeof props.opened === "boolean" && !props.onClose) {
771
- // eslint-disable-next-line no-console
772
567
  console.warn("'onClose' should be used with 'opened'");
773
568
  }
774
569
 
775
570
  if (typeof props.opened !== "boolean" && !props.children) {
776
- // eslint-disable-next-line no-console
777
571
  console.warn("either 'children' or 'opened' must be set");
778
572
  }
779
573
 
@@ -783,7 +577,6 @@ class ModalLauncher extends React.Component {
783
577
  }
784
578
 
785
579
  componentDidUpdate(prevProps) {
786
- // ensures the element is stored only when the modal is opened
787
580
  if (!prevProps.opened && this.props.opened) {
788
581
  this._saveLastElementFocused();
789
582
  }
@@ -811,35 +604,22 @@ class ModalLauncher extends React.Component {
811
604
  return null;
812
605
  }
813
606
 
814
- return (
815
- /*#__PURE__*/
816
- // This flow check is valid, it's the babel plugin which is broken,
817
- // see modal-context.js for details.
818
- // $FlowFixMe
819
- React.createElement(ModalContext.Provider, {
820
- value: {
821
- closeModal: this.handleCloseModal
822
- }
823
- }, renderedChildren, this.state.opened && /*#__PURE__*/ReactDOM.createPortal(
824
- /*#__PURE__*/
825
-
826
- /* We need the container View that FocusTrap creates to be at the
827
- correct z-index so that it'll be above the global nav in webapp. */
828
- React.createElement(FocusTrap, {
829
- style: styles$1.container
830
- }, /*#__PURE__*/React.createElement(ModalBackdrop, {
831
- initialFocusId: this.props.initialFocusId,
832
- testId: this.props.testId,
833
- onCloseModal: this.props.backdropDismissEnabled ? this.handleCloseModal : () => {}
834
- }, this._renderModal())), body), this.state.opened && /*#__PURE__*/React.createElement(ModalLauncherKeypressListener, {
835
- onClose: this.handleCloseModal
836
- }), this.state.opened && /*#__PURE__*/React.createElement(ScrollDisabler, null))
837
- );
607
+ return React.createElement(ModalContext.Provider, {
608
+ value: {
609
+ closeModal: this.handleCloseModal
610
+ }
611
+ }, renderedChildren, this.state.opened && ReactDOM.createPortal(React.createElement(FocusTrap, {
612
+ style: styles$1.container
613
+ }, React.createElement(ModalBackdrop, {
614
+ initialFocusId: this.props.initialFocusId,
615
+ testId: this.props.testId,
616
+ onCloseModal: this.props.backdropDismissEnabled ? this.handleCloseModal : () => {}
617
+ }, this._renderModal())), body), this.state.opened && React.createElement(ModalLauncherKeypressListener, {
618
+ onClose: this.handleCloseModal
619
+ }), this.state.opened && React.createElement(ScrollDisabler, null));
838
620
  }
839
621
 
840
622
  }
841
- /** A component that, when mounted, calls `onClose` when Escape is pressed. */
842
-
843
623
 
844
624
  ModalLauncher.defaultProps = {
845
625
  backdropDismissEnabled: true
@@ -850,16 +630,7 @@ class ModalLauncherKeypressListener extends React.Component {
850
630
  super(...args);
851
631
 
852
632
  this._handleKeyup = e => {
853
- // We check the key as that's keyboard layout agnostic and also avoids
854
- // the minefield of deprecated number type properties like keyCode and
855
- // which, with the replacement code, which uses a string instead.
856
633
  if (e.key === "Escape") {
857
- // Stop the event going any further.
858
- // For cancellation events, like the Escape key, we generally should
859
- // air on the side of caution and only allow it to cancel one thing.
860
- // So, it's polite for us to stop propagation of the event.
861
- // Otherwise, we end up with UX where one Escape key press
862
- // unexpectedly cancels multiple things.
863
634
  e.preventDefault();
864
635
  e.stopPropagation();
865
636
  this.props.onClose();
@@ -883,19 +654,11 @@ class ModalLauncherKeypressListener extends React.Component {
883
654
 
884
655
  const styles$1 = StyleSheet.create({
885
656
  container: {
886
- // This z-index is copied from the Khan Academy webapp.
887
- //
888
- // TODO(mdr): Should we keep this in a constants file somewhere? Or
889
- // not hardcode it at all, and provide it to Wonder Blocks via
890
- // configuration?
891
657
  zIndex: 1080
892
658
  }
893
659
  });
894
660
  var modalLauncher = withActionScheduler(ModalLauncher);
895
661
 
896
- /**
897
- * The Modal content included after the header
898
- */
899
662
  class ModalContent extends React.Component {
900
663
  static isClassOf(instance) {
901
664
  return instance && instance.type && instance.type.__IS_MODAL_CONTENT__;
@@ -907,13 +670,13 @@ class ModalContent extends React.Component {
907
670
  style,
908
671
  children
909
672
  } = this.props;
910
- return /*#__PURE__*/React.createElement(MediaLayout, {
673
+ return React.createElement(MediaLayout, {
911
674
  styleSheets: styleSheets$1
912
675
  }, ({
913
676
  styles
914
- }) => /*#__PURE__*/React.createElement(View, {
677
+ }) => React.createElement(View, {
915
678
  style: [styles.wrapper, scrollOverflow && styles.scrollOverflow]
916
- }, /*#__PURE__*/React.createElement(View, {
679
+ }, React.createElement(View, {
917
680
  style: [styles.content, style]
918
681
  }, children)));
919
682
  }
@@ -927,8 +690,6 @@ const styleSheets$1 = {
927
690
  all: StyleSheet.create({
928
691
  wrapper: {
929
692
  flex: 1,
930
- // This helps to ensure that the paddingBottom is preserved when
931
- // the contents start to overflow, this goes away on display: flex
932
693
  display: "block"
933
694
  },
934
695
  scrollOverflow: {
@@ -956,17 +717,15 @@ class CloseButton extends React.Component {
956
717
  style,
957
718
  testId
958
719
  } = this.props;
959
- return /*#__PURE__*/React.createElement(ModalContext.Consumer, null, ({
720
+ return React.createElement(ModalContext.Consumer, null, ({
960
721
  closeModal
961
722
  }) => {
962
723
  if (closeModal && onClick) {
963
724
  throw new Error("You've specified 'onClose' on a modal when using ModalLauncher. Please specify 'onClose' on the ModalLauncher instead");
964
725
  }
965
726
 
966
- return /*#__PURE__*/React.createElement(IconButton, {
967
- icon: icons.dismiss // TODO(mdr): Translate this string for i18n.
968
- // TODO(kevinb): provide a way to set this label
969
- ,
727
+ return React.createElement(IconButton, {
728
+ icon: icons.dismiss,
970
729
  "aria-label": "Close modal",
971
730
  onClick: onClick || closeModal,
972
731
  kind: light ? "primary" : "tertiary",
@@ -979,26 +738,6 @@ class CloseButton extends React.Component {
979
738
 
980
739
  }
981
740
 
982
- /**
983
- * ModalPanel is the content container.
984
- *
985
- * **Implementation notes:**
986
- *
987
- * If you are creating a custom Dialog, make sure to follow these guidelines:
988
- * - Make sure to add this component inside the [ModalDialog](/#modaldialog).
989
- * - If needed, you can also add a [ModalHeader](/#modalheader) using the
990
- * `header` prop. Same goes for [ModalFooter](/#modalfooter).
991
- * - If you need to create e2e tests, make sure to pass a `testId` prop. This
992
- * will be passed down to this component using a sufix: e.g.
993
- * `some-random-id-ModalPanel`. This scope will be propagated to the
994
- * CloseButton element as well: e.g. `some-random-id-CloseButton`.
995
- *
996
- * ```js
997
- * <ModalDialog>
998
- * <ModalPanel content={"custom content goes here"} />
999
- * </ModalDialog>
1000
- * ```
1001
- */
1002
741
  class ModalPanel extends React.Component {
1003
742
  renderMainContent() {
1004
743
  const {
@@ -1006,19 +745,14 @@ class ModalPanel extends React.Component {
1006
745
  footer,
1007
746
  scrollOverflow
1008
747
  } = this.props;
1009
- const mainContent = ModalContent.isClassOf(content) ? content : /*#__PURE__*/React.createElement(ModalContent, null, content);
748
+ const mainContent = ModalContent.isClassOf(content) ? content : React.createElement(ModalContent, null, content);
1010
749
 
1011
750
  if (!mainContent) {
1012
751
  return mainContent;
1013
752
  }
1014
753
 
1015
- return /*#__PURE__*/React.cloneElement(mainContent, {
1016
- // Pass the scrollOverflow and header in to the main content
754
+ return React.cloneElement(mainContent, {
1017
755
  scrollOverflow,
1018
- // We override the styling of the main content to help position
1019
- // it if there is a footer or close button being
1020
- // shown. We have to do this here as the ModalContent doesn't
1021
- // know about things being positioned around it.
1022
756
  style: [!!footer && styles.hasFooter, mainContent.props.style]
1023
757
  });
1024
758
  }
@@ -1034,15 +768,15 @@ class ModalPanel extends React.Component {
1034
768
  testId
1035
769
  } = this.props;
1036
770
  const mainContent = this.renderMainContent();
1037
- return /*#__PURE__*/React.createElement(View, {
771
+ return React.createElement(View, {
1038
772
  style: [styles.wrapper, !light && styles.dark, style],
1039
773
  testId: testId && `${testId}-panel`
1040
- }, closeButtonVisible && /*#__PURE__*/React.createElement(CloseButton, {
774
+ }, closeButtonVisible && React.createElement(CloseButton, {
1041
775
  light: !light,
1042
776
  onClick: onClose,
1043
777
  style: styles.closeButton,
1044
778
  testId: testId && `${testId}-close`
1045
- }), header, mainContent, !footer || ModalFooter.isClassOf(footer) ? footer : /*#__PURE__*/React.createElement(ModalFooter, null, footer));
779
+ }), header, mainContent, !footer || ModalFooter.isClassOf(footer) ? footer : React.createElement(ModalFooter, null, footer));
1046
780
  }
1047
781
 
1048
782
  }
@@ -1067,8 +801,6 @@ const styles = StyleSheet.create({
1067
801
  position: "absolute",
1068
802
  right: Spacing.medium_16,
1069
803
  top: Spacing.medium_16,
1070
- // This is to allow the button to be tab-ordered before the modal
1071
- // content but still be above the header and content.
1072
804
  zIndex: 1
1073
805
  },
1074
806
  dark: {
@@ -1080,12 +812,6 @@ const styles = StyleSheet.create({
1080
812
  }
1081
813
  });
1082
814
 
1083
- /**
1084
- * This is the standard layout for most straightforward modal experiences.
1085
- *
1086
- * The ModalHeader is required, but the ModalFooter is optional.
1087
- * The content of the dialog itself is fully customizable, but the left/right/top/bottom padding is fixed.
1088
- */
1089
815
  class OnePaneDialog extends React.Component {
1090
816
  renderHeader(uniqueId) {
1091
817
  const {
@@ -1096,21 +822,21 @@ class OnePaneDialog extends React.Component {
1096
822
  } = this.props;
1097
823
 
1098
824
  if (breadcrumbs) {
1099
- return /*#__PURE__*/React.createElement(ModalHeader, {
825
+ return React.createElement(ModalHeader, {
1100
826
  title: title,
1101
827
  breadcrumbs: breadcrumbs,
1102
828
  titleId: uniqueId,
1103
829
  testId: testId && `${testId}-header`
1104
830
  });
1105
831
  } else if (subtitle) {
1106
- return /*#__PURE__*/React.createElement(ModalHeader, {
832
+ return React.createElement(ModalHeader, {
1107
833
  title: title,
1108
834
  subtitle: subtitle,
1109
835
  titleId: uniqueId,
1110
836
  testId: testId && `${testId}-header`
1111
837
  });
1112
838
  } else {
1113
- return /*#__PURE__*/React.createElement(ModalHeader, {
839
+ return React.createElement(ModalHeader, {
1114
840
  title: title,
1115
841
  titleId: uniqueId,
1116
842
  testId: testId && `${testId}-header`
@@ -1131,21 +857,21 @@ class OnePaneDialog extends React.Component {
1131
857
  titleId,
1132
858
  role
1133
859
  } = this.props;
1134
- return /*#__PURE__*/React.createElement(MediaLayout, {
860
+ return React.createElement(MediaLayout, {
1135
861
  styleSheets: styleSheets
1136
862
  }, ({
1137
863
  styles
1138
- }) => /*#__PURE__*/React.createElement(IDProvider, {
864
+ }) => React.createElement(IDProvider, {
1139
865
  id: titleId,
1140
866
  scope: "modal"
1141
- }, uniqueId => /*#__PURE__*/React.createElement(ModalDialog, {
867
+ }, uniqueId => React.createElement(ModalDialog, {
1142
868
  style: [styles.dialog, style],
1143
869
  above: above,
1144
870
  below: below,
1145
871
  testId: testId,
1146
872
  "aria-labelledby": uniqueId,
1147
873
  role: role
1148
- }, /*#__PURE__*/React.createElement(ModalPanel, {
874
+ }, React.createElement(ModalPanel, {
1149
875
  onClose: onClose,
1150
876
  header: this.renderHeader(uniqueId),
1151
877
  content: content,
@@ -1177,14 +903,6 @@ const styleSheets = {
1177
903
  })
1178
904
  };
1179
905
 
1180
- /**
1181
- * From a given element, finds its next ancestor that is a modal launcher portal
1182
- * element.
1183
- * @param {?(Element | Text)} element The element whose ancestors are to be
1184
- * walked.
1185
- * @returns {?Element} The nearest parent modal launcher portal.
1186
- */
1187
-
1188
906
  function maybeGetNextAncestorModalLauncherPortal(element) {
1189
907
  let candidateElement = element && element.parentElement;
1190
908
 
@@ -1194,15 +912,6 @@ function maybeGetNextAncestorModalLauncherPortal(element) {
1194
912
 
1195
913
  return candidateElement;
1196
914
  }
1197
- /**
1198
- * From a given element, finds the next modal host that has been mounted in
1199
- * a modal portal.
1200
- * @param {?(Element | Text)} element The element whose ancestors are to be
1201
- * walked.
1202
- * @returns {?Element} The next portal-mounted modal host element.
1203
- * TODO(kevinb): look into getting rid of this
1204
- */
1205
-
1206
915
 
1207
916
  function maybeGetPortalMountedModalHostElement(element) {
1208
917
  return maybeGetNextAncestorModalLauncherPortal(element);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanacademy/wonder-blocks-modal",
3
- "version": "2.3.0",
3
+ "version": "2.3.1",
4
4
  "design": "v2",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -16,15 +16,15 @@
16
16
  "license": "MIT",
17
17
  "dependencies": {
18
18
  "@babel/runtime": "^7.16.3",
19
- "@khanacademy/wonder-blocks-breadcrumbs": "^1.0.29",
19
+ "@khanacademy/wonder-blocks-breadcrumbs": "^1.0.30",
20
20
  "@khanacademy/wonder-blocks-color": "^1.1.20",
21
- "@khanacademy/wonder-blocks-core": "^4.3.0",
22
- "@khanacademy/wonder-blocks-icon": "^1.2.26",
23
- "@khanacademy/wonder-blocks-icon-button": "^3.4.5",
24
- "@khanacademy/wonder-blocks-layout": "^1.4.8",
21
+ "@khanacademy/wonder-blocks-core": "^4.3.1",
22
+ "@khanacademy/wonder-blocks-icon": "^1.2.27",
23
+ "@khanacademy/wonder-blocks-icon-button": "^3.4.6",
24
+ "@khanacademy/wonder-blocks-layout": "^1.4.9",
25
25
  "@khanacademy/wonder-blocks-spacing": "^3.0.5",
26
- "@khanacademy/wonder-blocks-toolbar": "^2.1.30",
27
- "@khanacademy/wonder-blocks-typography": "^1.1.30"
26
+ "@khanacademy/wonder-blocks-toolbar": "^2.1.31",
27
+ "@khanacademy/wonder-blocks-typography": "^1.1.31"
28
28
  },
29
29
  "peerDependencies": {
30
30
  "aphrodite": "^1.2.5",
@@ -32,6 +32,6 @@
32
32
  "react-dom": "16.14.0"
33
33
  },
34
34
  "devDependencies": {
35
- "wb-dev-build-settings": "^0.3.0"
35
+ "wb-dev-build-settings": "^0.4.0"
36
36
  }
37
37
  }
@@ -81,7 +81,7 @@ export const Simple: StoryComponentType = () => {
81
81
 
82
82
  const styles = StyleSheet.create({
83
83
  above: {
84
- background: "url(/modal-above.png)",
84
+ background: "url(./modal-above.png)",
85
85
  width: 874,
86
86
  height: 551,
87
87
  position: "absolute",
@@ -90,7 +90,7 @@ const styles = StyleSheet.create({
90
90
  },
91
91
 
92
92
  below: {
93
- background: "url(/modal-below.png)",
93
+ background: "url(./modal-below.png)",
94
94
  width: 868,
95
95
  height: 521,
96
96
  position: "absolute",