@khanacademy/wonder-blocks-modal 8.4.5 → 8.5.0

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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @khanacademy/wonder-blocks-modal@8.4.5 build:css /home/runner/work/wonder-blocks/wonder-blocks/packages/wonder-blocks-modal
2
+ > @khanacademy/wonder-blocks-modal@8.5.0 build:css /home/runner/work/wonder-blocks/wonder-blocks/packages/wonder-blocks-modal
3
3
  > pnpm exec wonder-blocks-tokens .
4
4
 
5
5
  CSS variables generated successfully in: /home/runner/work/wonder-blocks/wonder-blocks/packages/wonder-blocks-modal/dist/css
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @khanacademy/wonder-blocks-modal
2
2
 
3
+ ## 8.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 9a989a4: Adds support to OnePaneDialog for shorter-height and zoomed screens
8
+
9
+ ### Patch Changes
10
+
11
+ - 1d94230: Converts some parts of WB Modal to functional components
12
+
13
+ ## 8.4.6
14
+
15
+ ### Patch Changes
16
+
17
+ - @khanacademy/wonder-blocks-icon-button@10.5.3
18
+
3
19
  ## 8.4.5
4
20
 
5
21
  ### Patch Changes
@@ -18,7 +18,5 @@ type Props = {
18
18
  */
19
19
  testId?: string;
20
20
  };
21
- export default class CloseButton extends React.Component<Props> {
22
- render(): React.ReactNode;
23
- }
24
- export {};
21
+ declare const CloseButton: ({ onClick, style, testId }: Props) => React.ReactElement;
22
+ export default CloseButton;
@@ -30,11 +30,11 @@ declare const _default: {
30
30
  }) => React.ReactNode) | undefined;
31
31
  readonly testId?: string | undefined;
32
32
  readonly initialFocusId?: string | undefined;
33
- readonly backdropDismissEnabled?: boolean | undefined;
33
+ onClose?: (() => unknown) | (() => unknown) | undefined;
34
34
  readonly modal: DrawerModalElement | DrawerModalFunction;
35
+ readonly backdropDismissEnabled?: boolean | undefined;
35
36
  readonly closedFocusId?: string | undefined;
36
37
  opened?: boolean | undefined;
37
- onClose?: (() => unknown) | (() => unknown) | undefined;
38
38
  readonly styles?: {
39
39
  container?: StyleType;
40
40
  } | undefined;
@@ -65,7 +65,6 @@ declare function FlexiblePanel({ closeButtonVisible, content, title, onClose, st
65
65
  declare namespace FlexiblePanel {
66
66
  var defaultProps: {
67
67
  closeButtonVisible: boolean;
68
- scrollOverflow: boolean;
69
68
  };
70
69
  }
71
70
  export default FlexiblePanel;
@@ -25,33 +25,5 @@ type Props = {
25
25
  */
26
26
  style?: StyleType;
27
27
  };
28
- export default class FocusTrap extends React.Component<Props> {
29
- /**
30
- * Tabbing is restricted to descendents of this element.
31
- */
32
- modalRoot: Node | null | undefined;
33
- getModalRoot: (node?: any) => void;
34
- /**
35
- * Try to focus the given node. Return true if successful.
36
- */
37
- tryToFocus(node: Node): boolean | null | undefined;
38
- /**
39
- * Focus the next available focusable element within the modal root.
40
- *
41
- * @param {boolean} isLast Used to determine the next available item. true =
42
- * First element within the modal, false = Last element within the modal.
43
- */
44
- focusElementIn(isLast: boolean): void;
45
- /**
46
- * Triggered when the focus is set to the first sentinel. This way, the
47
- * focus will be redirected to the last element inside the modal dialog.
48
- */
49
- handleFocusMoveToLast: () => void;
50
- /**
51
- * Triggered when the focus is set to the last sentinel. This way, the focus
52
- * will be redirected to the first element inside the modal dialog.
53
- */
54
- handleFocusMoveToFirst: () => void;
55
- render(): React.ReactNode;
56
- }
57
- export {};
28
+ declare const FocusTrap: ({ children, style }: Props) => React.ReactElement;
29
+ export default FocusTrap;
@@ -14,38 +14,5 @@ type Props = {
14
14
  */
15
15
  testId?: string;
16
16
  };
17
- /**
18
- * A private component used by ModalLauncher. This is the fixed-position
19
- * container element that gets mounted outside the DOM. It overlays the modal
20
- * content (provided as `children`) over the content, with a gray backdrop
21
- * behind it.
22
- *
23
- * This component is also responsible for cloning the provided modal `children`,
24
- * and adding an `onClose` prop that will call `onCloseModal`. If an
25
- * `onClose` prop is already provided, the two are merged.
26
- */
27
- export default class ModalBackdrop extends React.Component<Props> {
28
- componentDidMount(): void;
29
- _mousePressedOutside: boolean;
30
- /**
31
- * Returns an element specified by the user
32
- */
33
- _getInitialFocusElement(node: HTMLElement): HTMLElement | null;
34
- /**
35
- * Returns the first focusable element found inside the Dialog
36
- */
37
- _getFirstFocusableElement(node: HTMLElement): HTMLElement | null;
38
- /**
39
- * Returns the dialog element
40
- */
41
- _getDialogElement(node: HTMLElement): HTMLElement;
42
- /**
43
- * When the user clicks on the gray backdrop area (i.e., the click came
44
- * _directly_ from the positioner, not bubbled up from its children), close
45
- * the modal.
46
- */
47
- handleMouseDown: (e: React.SyntheticEvent) => void;
48
- handleMouseUp: (e: React.SyntheticEvent) => void;
49
- render(): React.ReactNode;
50
- }
51
- export {};
17
+ declare const ModalBackdrop: ({ children, initialFocusId, onCloseModal, testId, }: Props) => React.ReactElement;
18
+ export default ModalBackdrop;
@@ -7,13 +7,13 @@ declare const _default: {
7
7
  }) => React.ReactNode) | undefined;
8
8
  readonly testId?: string | undefined;
9
9
  readonly initialFocusId?: string | undefined;
10
- readonly backdropDismissEnabled?: boolean | undefined;
10
+ readonly onClose?: (() => unknown) | undefined;
11
11
  readonly modal: ModalElement | ((props: {
12
12
  closeModal: () => void;
13
13
  }) => ModalElement);
14
+ readonly backdropDismissEnabled?: boolean | undefined;
14
15
  readonly closedFocusId?: string | undefined;
15
16
  readonly opened?: boolean | undefined;
16
- readonly onClose?: (() => unknown) | undefined;
17
17
  }): React.JSX.Element;
18
18
  displayName: string;
19
19
  };
@@ -25,8 +25,10 @@ type Props = {
25
25
  /**
26
26
  * Should the contents of the panel become scrollable should they
27
27
  * become too tall?
28
+ *
29
+ * Defaults to true.
28
30
  */
29
- scrollOverflow: boolean;
31
+ scrollOverflow?: boolean;
30
32
  /**
31
33
  * Any optional styling to apply to the panel.
32
34
  */
@@ -1,7 +1,6 @@
1
1
  import * as React from "react";
2
2
  import { Breadcrumbs } from "@khanacademy/wonder-blocks-breadcrumbs";
3
3
  import type { StyleType } from "@khanacademy/wonder-blocks-core";
4
- import ModalHeader from "./modal-header";
5
4
  type Common = {
6
5
  /**
7
6
  * The content of the modal, appearing between the titlebar and footer.
@@ -82,43 +81,5 @@ type WithBreadcrumbs = Common & {
82
81
  breadcrumbs: React.ReactElement<React.ComponentProps<typeof Breadcrumbs>>;
83
82
  };
84
83
  type Props = Common | WithSubtitle | WithBreadcrumbs;
85
- type DefaultProps = {
86
- closeButtonVisible: Props["closeButtonVisible"];
87
- };
88
- /**
89
- * This is the standard layout for most straightforward modal experiences.
90
- *
91
- * The ModalHeader is required, but the ModalFooter is optional.
92
- * The content of the dialog itself is fully customizable, but the
93
- * left/right/top/bottom padding is fixed.
94
- *
95
- * ### Usage
96
- *
97
- * ```jsx
98
- * import {OnePaneDialog} from "@khanacademy/wonder-blocks-modal";
99
- * import {BodyText} from "@khanacademy/wonder-blocks-typography";
100
- *
101
- * <OnePaneDialog
102
- * title="Some title"
103
- * content={
104
- * <BodyText>
105
- * {`Lorem ipsum dolor sit amet, consectetur adipiscing
106
- * elit, sed do eiusmod tempor incididunt ut labore et
107
- * dolore magna aliqua. Ut enim ad minim veniam,
108
- * quis nostrud exercitation ullamco laboris nisi ut
109
- * aliquip ex ea commodo consequat. Duis aute irure
110
- * dolor in reprehenderit in voluptate velit esse
111
- * cillum dolore eu fugiat nulla pariatur. Excepteur
112
- * sint occaecat cupidatat non proident, sunt in culpa
113
- * qui officia deserunt mollit anim id est.`}
114
- * </BodyText>
115
- * }
116
- * />
117
- * ```
118
- */
119
- export default class OnePaneDialog extends React.Component<Props> {
120
- static defaultProps: DefaultProps;
121
- renderHeader(uniqueId: string): React.ReactElement<React.ComponentProps<typeof ModalHeader>>;
122
- render(): React.ReactNode;
123
- }
124
- export {};
84
+ declare const OnePaneDialog: (props: Props) => React.ReactElement;
85
+ export default OnePaneDialog;
@@ -5,20 +5,9 @@
5
5
  * unfortunately, and this handles that in an encapsulated way.
6
6
  *
7
7
  * NOTE(mdr): This component was copied from webapp. Be wary of sync issues. It
8
- * also doesn't have unit tests, and we haven't added any, since it's a
8
+ * also doesn't have any unit tests, and we haven't added any, since it's a
9
9
  * relatively stable component that has now been stress-tested lots in prod.
10
10
  */
11
- import * as React from "react";
12
11
  type Props = Record<any, any>;
13
- declare class ScrollDisabler extends React.Component<Props> {
14
- static oldOverflow: string;
15
- static oldPosition: string;
16
- static oldScrollY: number;
17
- static oldWidth: string;
18
- static oldTop: string;
19
- componentDidMount(): void;
20
- componentWillUnmount(): void;
21
- static numModalsOpened: number;
22
- render(): React.ReactElement | null;
23
- }
12
+ declare const ScrollDisabler: (_props: Props) => null;
24
13
  export default ScrollDisabler;
package/dist/es/index.js CHANGED
@@ -9,41 +9,40 @@ import { withActionScheduler } from '@khanacademy/wonder-blocks-timing';
9
9
  import { focusStyles } from '@khanacademy/wonder-blocks-styles';
10
10
  import xIcon from '@phosphor-icons/core/bold/x-bold.svg';
11
11
  import IconButton from '@khanacademy/wonder-blocks-icon-button';
12
- import { MediaLayout } from '@khanacademy/wonder-blocks-layout';
12
+
13
+ const ModalLauncherPortalAttributeName="data-modal-launcher-portal";const height={smMin:500};const width={mid:767};const modalMediaQuery={midOrSmaller:`@media screen and (max-width: ${width.mid}px) /* breakpoint.mediaQuery.midOrSmaller */`,midOrLarger:`@media screen and (min-width: ${width.mid}px) /* breakpoint.mediaQuery.midOrLarger */`,smMinOrSmallerHeight:`@media screen and (max-height:${height.smMin}px)`};
13
14
 
14
15
  const theme$1={root:{border:{radius:border.radius.radius_040}},dialog:{layout:{padding:sizing.size_160},shadow:{default:"none"}},header:{layout:{padding:{block:sizing.size_240,inline:{default:sizing.size_320,small:sizing.size_160}},gap:{default:sizing.size_080,title:{default:sizing.size_160,small:sizing.size_320}}}},panel:{layout:{gap:{default:sizing.size_320,small:sizing.size_160}}},footer:{layout:{padding:{inline:sizing.size_160,block:sizing.size_080}}},closeButton:{layout:{gapRight:sizing.size_080,gapTop:sizing.size_080}}};
15
16
 
16
17
  var theme = mapValuesToCssVars(theme$1,"--wb-c-modal-");
17
18
 
18
- const ModalDialog=React.forwardRef(function ModalDialog(props,ref){const{above,below,role="dialog",style,children,testId,"aria-label":ariaLabel,"aria-labelledby":ariaLabelledBy,"aria-describedby":ariaDescribedBy}=props;return jsxs(View,{style:[styles$7.wrapper,style],children:[below&&jsx(View,{style:styles$7.below,children:below}),jsx(View,{role:role,"aria-modal":"true","aria-label":ariaLabel,"aria-labelledby":ariaLabelledBy,"aria-describedby":ariaDescribedBy,ref:ref,style:styles$7.dialog,testId:testId,children:children}),above&&jsx(View,{style:styles$7.above,children:above})]})});const small$2="@media (max-width: 767px)";const styles$7=StyleSheet.create({wrapper:{color:semanticColor.core.foreground.neutral.strong,flexDirection:"row",alignItems:"stretch",width:"100%",height:"100%",position:"relative",boxShadow:theme.dialog.shadow.default,borderRadius:theme.root.border.radius,[small$2]:{padding:theme.dialog.layout.padding,flexDirection:"column"}},dialog:{width:"100%",height:"100%",borderRadius:theme.root.border.radius,overflow:"hidden"},above:{pointerEvents:"none",position:"absolute",top:0,left:0,bottom:0,right:0,zIndex:1},below:{pointerEvents:"none",position:"absolute",top:0,left:0,bottom:0,right:0,zIndex:-1}});ModalDialog.displayName="ModalDialog";
19
-
20
- function ModalFooter({children}){return jsx(View,{style:styles$6.footer,children:children})}ModalFooter.__IS_MODAL_FOOTER__=true;ModalFooter.isComponentOf=instance=>{return instance&&instance.type&&instance.type.__IS_MODAL_FOOTER__};const styles$6=StyleSheet.create({footer:{flex:"0 0 auto",boxSizing:"border-box",minHeight:sizing.size_640,paddingInline:theme.footer.layout.padding.inline,paddingBlock:theme.footer.layout.padding.block,display:"flex",flexDirection:"row",alignItems:"center",justifyContent:"flex-end",boxShadow:`0px -1px 0px ${semanticColor.core.border.neutral.subtle}`}});
19
+ const ModalDialog=React.forwardRef(function ModalDialog(props,ref){const{above,below,role="dialog",style,children,testId,"aria-label":ariaLabel,"aria-labelledby":ariaLabelledBy,"aria-describedby":ariaDescribedBy}=props;return jsxs(View,{style:[componentStyles$2.wrapper,style],children:[below&&jsx(View,{style:componentStyles$2.below,children:below}),jsx(View,{role:role,"aria-modal":"true","aria-label":ariaLabel,"aria-labelledby":ariaLabelledBy,"aria-describedby":ariaDescribedBy,ref:ref,style:[componentStyles$2.dialog],testId:testId,children:children}),above&&jsx(View,{style:componentStyles$2.above,children:above})]})});const componentStyles$2=StyleSheet.create({wrapper:{color:semanticColor.core.foreground.neutral.strong,flexDirection:"row",alignItems:"stretch",width:"100%",height:"100%",position:"relative",boxShadow:theme.dialog.shadow.default,borderRadius:theme.root.border.radius,[modalMediaQuery.midOrSmaller]:{padding:theme.dialog.layout.padding,flexDirection:"column"},[modalMediaQuery.smMinOrSmallerHeight]:{overflow:"auto"}},dialog:{width:"100%",height:"100%",borderRadius:theme.root.border.radius,overflow:"hidden",[modalMediaQuery.smMinOrSmallerHeight]:{overflow:"auto"}},above:{pointerEvents:"none",position:"absolute",top:0,left:0,bottom:0,right:0,zIndex:1},below:{pointerEvents:"none",position:"absolute",top:0,left:0,bottom:0,right:0,zIndex:-1}});ModalDialog.displayName="ModalDialog";
21
20
 
22
- function ModalHeader(props){const{breadcrumbs=undefined,subtitle=undefined,testId,title,titleId}=props;if(subtitle&&breadcrumbs){throw new Error("'subtitle' and 'breadcrumbs' can't be used together")}return jsxs(View,{style:[styles$5.header],testId:testId,children:[breadcrumbs&&jsx(View,{style:styles$5.breadcrumbs,children:breadcrumbs}),jsx(Heading,{size:"large",tag:"h2",style:styles$5.title,id:titleId,testId:testId&&`${testId}-title`,children:title}),subtitle&&jsx(BodyText,{size:"small",style:styles$5.subtitle,testId:testId&&`${testId}-subtitle`,children:subtitle})]})}const small$1="@media (max-width: 767px)";const styles$5=StyleSheet.create({header:{boxShadow:`0px 1px 0px ${semanticColor.core.border.neutral.subtle}`,display:"flex",flexDirection:"column",minHeight:66,paddingBlock:theme.header.layout.padding.block,paddingInline:theme.header.layout.padding.inline.default,position:"relative",width:"100%",[small$1]:{paddingInline:theme.header.layout.padding.inline.small}},breadcrumbs:{color:semanticColor.core.foreground.neutral.default,marginBlockEnd:theme.header.layout.gap.default},title:{paddingInlineEnd:theme.header.layout.gap.title.default,[small$1]:{paddingInlineEnd:theme.header.layout.gap.title.small}},subtitle:{color:semanticColor.core.foreground.neutral.default,marginBlockStart:theme.header.layout.gap.default}});
21
+ function ModalFooter({children}){return jsx(View,{style:styles$7.footer,children:children})}ModalFooter.__IS_MODAL_FOOTER__=true;ModalFooter.isComponentOf=instance=>{return instance&&instance.type&&instance.type.__IS_MODAL_FOOTER__};const styles$7=StyleSheet.create({footer:{flex:"0 0 auto",boxSizing:"border-box",minHeight:sizing.size_640,paddingInline:theme.footer.layout.padding.inline,paddingBlock:theme.footer.layout.padding.block,display:"flex",flexDirection:"row",alignItems:"center",justifyContent:"flex-end",boxShadow:`0px -1px 0px ${semanticColor.core.border.neutral.subtle}`}});
23
22
 
24
- const FOCUSABLE_ELEMENTS$1='button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';class FocusTrap extends React.Component{tryToFocus(node){if(node instanceof HTMLElement){try{node.focus();}catch(e){}return document.activeElement===node}}focusElementIn(isLast){const modalRootAsHtmlEl=this.modalRoot;const focusableNodes=Array.from(modalRootAsHtmlEl.querySelectorAll(FOCUSABLE_ELEMENTS$1)).filter(element=>{const style=window.getComputedStyle(element);return style.display!=="none"&&style.visibility!=="hidden"});const nodeIndex=!isLast?focusableNodes.length-1:0;const focusableNode=focusableNodes[nodeIndex];this.tryToFocus(focusableNode);}render(){const{style}=this.props;return jsxs(React.Fragment,{children:[jsx("div",{tabIndex:0,className:"modal-focus-trap-first",onFocus:this.handleFocusMoveToLast,style:{position:"fixed"}}),jsx(View,{style:style,ref:this.getModalRoot,children:this.props.children}),jsx("div",{tabIndex:0,className:"modal-focus-trap-last",onFocus:this.handleFocusMoveToFirst,style:{position:"fixed"}})]})}constructor(...args){super(...args),this.getModalRoot=node=>{if(!node){return}const modalRoot=ReactDOM.findDOMNode(node);if(!modalRoot){throw new Error("Assertion error: modal root should exist after mount")}this.modalRoot=modalRoot;},this.handleFocusMoveToLast=()=>{this.focusElementIn(false);},this.handleFocusMoveToFirst=()=>{this.focusElementIn(true);};}}
23
+ function ModalHeader(props){const{breadcrumbs=undefined,subtitle=undefined,testId,title,titleId}=props;if(subtitle&&breadcrumbs){throw new Error("'subtitle' and 'breadcrumbs' can't be used together")}return jsxs(View,{style:[styles$6.header],testId:testId,children:[breadcrumbs&&jsx(View,{style:styles$6.breadcrumbs,children:breadcrumbs}),jsx(Heading,{size:"large",tag:"h2",style:styles$6.title,id:titleId,testId:testId&&`${testId}-title`,children:title}),subtitle&&jsx(BodyText,{size:"small",style:styles$6.subtitle,testId:testId&&`${testId}-subtitle`,children:subtitle})]})}const styles$6=StyleSheet.create({header:{boxShadow:`0px 1px 0px ${semanticColor.core.border.neutral.subtle}`,display:"flex",flexDirection:"column",minHeight:66,paddingBlock:theme.header.layout.padding.block,paddingInline:theme.header.layout.padding.inline.default,position:"relative",width:"100%",[modalMediaQuery.midOrSmaller]:{paddingInline:theme.header.layout.padding.inline.small},[modalMediaQuery.smMinOrSmallerHeight]:{minHeight:"unset"}},breadcrumbs:{color:semanticColor.core.foreground.neutral.default,marginBlockEnd:theme.header.layout.gap.default},title:{paddingInlineEnd:theme.header.layout.gap.title.default,[modalMediaQuery.midOrSmaller]:{paddingInlineEnd:theme.header.layout.gap.title.small}},subtitle:{color:semanticColor.core.foreground.neutral.default,marginBlockStart:theme.header.layout.gap.default}});
25
24
 
26
- const ModalLauncherPortalAttributeName="data-modal-launcher-portal";
25
+ const FOCUSABLE_ELEMENTS$1='button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';const FocusTrap=({children,style})=>{const modalRootRef=React.useRef(null);const tryToFocus=React.useCallback(node=>{if(node instanceof HTMLElement){try{node.focus();}catch(e){}return document.activeElement===node}},[]);const focusElementIn=React.useCallback(isLast=>{if(!modalRootRef.current){return}const modalRootAsHtmlEl=modalRootRef.current;const focusableNodes=Array.from(modalRootAsHtmlEl.querySelectorAll(FOCUSABLE_ELEMENTS$1)).filter(element=>{const style=window.getComputedStyle(element);return style.display!=="none"&&style.visibility!=="hidden"});const nodeIndex=!isLast?focusableNodes.length-1:0;const focusableNode=focusableNodes[nodeIndex];tryToFocus(focusableNode);},[tryToFocus]);const handleFocusMoveToLast=React.useCallback(()=>{focusElementIn(false);},[focusElementIn]);const handleFocusMoveToFirst=React.useCallback(()=>{focusElementIn(true);},[focusElementIn]);const getModalRoot=React.useCallback(node=>{if(!node){return}const modalRoot=ReactDOM.findDOMNode(node);if(!modalRoot){throw new Error("Assertion error: modal root should exist after mount")}modalRootRef.current=modalRoot;},[]);return jsxs(React.Fragment,{children:[jsx("div",{tabIndex:0,className:"modal-focus-trap-first",onFocus:handleFocusMoveToLast,style:{position:"fixed"}}),jsx(View,{style:style,ref:getModalRoot,children:children}),jsx("div",{tabIndex:0,className:"modal-focus-trap-last",onFocus:handleFocusMoveToFirst,style:{position:"fixed"}})]})};
27
26
 
28
27
  const FOCUSABLE_ELEMENTS="a[href], details, input, textarea, select, button";function findFocusableNodes(root){return Array.from(root.querySelectorAll(FOCUSABLE_ELEMENTS))}
29
28
 
30
- class ModalBackdrop extends React.Component{componentDidMount(){const node=ReactDOM.findDOMNode(this);if(!node){return}const firstFocusableElement=this._getInitialFocusElement(node)||this._getFirstFocusableElement(node)||this._getDialogElement(node);setTimeout(()=>{firstFocusableElement.focus();},0);}_getInitialFocusElement(node){const{initialFocusId}=this.props;if(!initialFocusId){return null}return ReactDOM.findDOMNode(node.querySelector(`#${initialFocusId}`))}_getFirstFocusableElement(node){const focusableElements=findFocusableNodes(node);if(!focusableElements){return null}return focusableElements[0]}_getDialogElement(node){const dialogElement=ReactDOM.findDOMNode(node.querySelector('[role="dialog"]'));dialogElement.tabIndex=-1;return dialogElement}render(){const{children,testId}=this.props;const backdropProps={[ModalLauncherPortalAttributeName]:true};return jsx(View,{style:styles$4.modalPositioner,onMouseDown:this.handleMouseDown,onMouseUp:this.handleMouseUp,testId:testId,...backdropProps,children:children})}constructor(...args){super(...args),this._mousePressedOutside=false,this.handleMouseDown=e=>{this._mousePressedOutside=e.target===e.currentTarget;},this.handleMouseUp=e=>{if(e.target===e.currentTarget&&this._mousePressedOutside){this.props.onCloseModal();}this._mousePressedOutside=false;};}}const styles$4=StyleSheet.create({modalPositioner:{position:"fixed",left:0,top:0,width:"100%",height:"100%",alignItems:"center",justifyContent:"center",overflow:"auto",background:semanticColor.core.background.overlay.default}});
29
+ const getInitialFocusElement=(node,initialFocusId)=>{if(!initialFocusId){return null}return ReactDOM.findDOMNode(node.querySelector(`#${initialFocusId}`))};const getFirstFocusableElement=node=>{const focusableElements=findFocusableNodes(node);if(!focusableElements){return null}return focusableElements[0]};const getDialogElement=node=>{const dialogElement=ReactDOM.findDOMNode(node.querySelector('[role="dialog"]'));dialogElement.tabIndex=-1;return dialogElement};const ModalBackdrop=({children,initialFocusId,onCloseModal,testId})=>{const backdropRef=React.useRef(null);const[mousePressedOutside,setMousePressedOutside]=React.useState(false);React.useEffect(()=>{const node=ReactDOM.findDOMNode(backdropRef.current);if(!node){return}const firstFocusableElement=getInitialFocusElement(node,initialFocusId)||getFirstFocusableElement(node)||getDialogElement(node);setTimeout(()=>{firstFocusableElement.focus();},0);},[initialFocusId]);const handleMouseDown=React.useCallback(e=>{setMousePressedOutside(e.target===e.currentTarget);},[]);const handleMouseUp=React.useCallback(e=>{if(e.target===e.currentTarget&&mousePressedOutside){onCloseModal();}setMousePressedOutside(false);},[mousePressedOutside,onCloseModal]);const backdropProps={[ModalLauncherPortalAttributeName]:true};return jsx(View,{ref:backdropRef,style:styles$5.modalPositioner,onMouseDown:handleMouseDown,onMouseUp:handleMouseUp,testId:testId,...backdropProps,children:children})};const styles$5=StyleSheet.create({modalPositioner:{position:"fixed",left:0,top:0,width:"100%",height:"100%",alignItems:"center",justifyContent:"center",overflow:"auto",background:semanticColor.core.background.overlay.default}});
31
30
 
32
- const needsHackyMobileSafariScrollDisabler=(()=>{if(typeof window==="undefined"){return false}const userAgent=window.navigator.userAgent;return userAgent.indexOf("iPad")>-1||userAgent.indexOf("iPhone")>-1})();class ScrollDisabler extends React.Component{componentDidMount(){if(ScrollDisabler.numModalsOpened===0){const body=document.body;if(!body){throw new Error("couldn't find document.body")}ScrollDisabler.oldOverflow=body.style.overflow;ScrollDisabler.oldScrollY=window.scrollY;if(needsHackyMobileSafariScrollDisabler){ScrollDisabler.oldPosition=body.style.position;ScrollDisabler.oldWidth=body.style.width;ScrollDisabler.oldTop=body.style.top;}body.style.overflow="hidden";if(needsHackyMobileSafariScrollDisabler){body.style.position="fixed";body.style.width="100%";body.style.top=`${-ScrollDisabler.oldScrollY}px`;}}ScrollDisabler.numModalsOpened++;}componentWillUnmount(){ScrollDisabler.numModalsOpened--;if(ScrollDisabler.numModalsOpened===0){const body=document.body;if(!body){throw new Error("couldn't find document.body")}body.style.overflow=ScrollDisabler.oldOverflow;if(needsHackyMobileSafariScrollDisabler){body.style.position=ScrollDisabler.oldPosition;body.style.width=ScrollDisabler.oldWidth;body.style.top=ScrollDisabler.oldTop;}if(typeof window!=="undefined"&&window.scrollTo){window.scrollTo(0,ScrollDisabler.oldScrollY);}}}render(){return null}}ScrollDisabler.numModalsOpened=0;
31
+ const needsHackyMobileSafariScrollDisabler=(()=>{if(typeof window==="undefined"){return false}const userAgent=window.navigator.userAgent;return userAgent.indexOf("iPad")>-1||userAgent.indexOf("iPhone")>-1})();let numModalsOpened=0;let oldOverflow;let oldPosition;let oldScrollY;let oldWidth;let oldTop;const ScrollDisabler=_props=>{React.useEffect(()=>{if(numModalsOpened===0){const body=document.body;if(!body){throw new Error("couldn't find document.body")}oldOverflow=body.style.overflow;oldScrollY=window.scrollY;if(needsHackyMobileSafariScrollDisabler){oldPosition=body.style.position;oldWidth=body.style.width;oldTop=body.style.top;}body.style.overflow="hidden";if(needsHackyMobileSafariScrollDisabler){body.style.position="fixed";body.style.width="100%";body.style.top=`${-oldScrollY}px`;}}numModalsOpened++;return ()=>{numModalsOpened--;if(numModalsOpened===0){const body=document.body;if(!body){throw new Error("couldn't find document.body")}body.style.overflow=oldOverflow;if(needsHackyMobileSafariScrollDisabler){body.style.position=oldPosition;body.style.width=oldWidth;body.style.top=oldTop;}if(typeof window!=="undefined"&&window.scrollTo){window.scrollTo(0,oldScrollY);}}}},[]);return null};
33
32
 
34
33
  const defaultContext={closeModal:undefined};const ModalContext=React.createContext(defaultContext);ModalContext.displayName="ModalContext";
35
34
 
36
- class ModalLauncher extends React.Component{static getDerivedStateFromProps(props,state){if(typeof props.opened==="boolean"&&props.children){console.warn("'children' and 'opened' can't be used together");}if(typeof props.opened==="boolean"&&!props.onClose){console.warn("'onClose' should be used with 'opened'");}if(typeof props.opened!=="boolean"&&!props.children){console.warn("either 'children' or 'opened' must be set");}return {opened:typeof props.opened==="boolean"?props.opened:state.opened}}componentDidUpdate(prevProps){if(!prevProps.opened&&this.props.opened){this._saveLastElementFocused();}}_renderModal(){if(typeof this.props.modal==="function"){return this.props.modal({closeModal:this.handleCloseModal})}else {return this.props.modal}}render(){const renderedChildren=this.props.children?this.props.children({openModal:this._openModal}):null;const{body}=document;if(!body){return null}return jsxs(ModalContext.Provider,{value:{closeModal:this.handleCloseModal},children:[renderedChildren,this.state.opened&&ReactDOM.createPortal(jsx(FocusTrap,{style:styles$3.container,children:jsx(ModalBackdrop,{initialFocusId:this.props.initialFocusId,testId:this.props.testId,onCloseModal:this.props.backdropDismissEnabled?this.handleCloseModal:()=>{},children:this._renderModal()})}),body),this.state.opened&&jsx(ModalLauncherKeypressListener,{onClose:this.handleCloseModal}),this.state.opened&&jsx(ScrollDisabler,{})]})}constructor(...args){super(...args),this.state={opened:false},this._saveLastElementFocused=()=>{this.lastElementFocusedOutsideModal=document.activeElement;},this._openModal=()=>{this._saveLastElementFocused();this.setState({opened:true});},this._returnFocus=()=>{const{closedFocusId,schedule}=this.props;const lastElement=this.lastElementFocusedOutsideModal;if(closedFocusId){const focusElement=ReactDOM.findDOMNode(document.getElementById(closedFocusId));if(focusElement){schedule.animationFrame(()=>{focusElement.focus();});return}}if(lastElement!=null){schedule.animationFrame(()=>{lastElement.focus();});}},this.handleCloseModal=()=>{this.setState({opened:false},()=>{const{onClose}=this.props;onClose?.();this._returnFocus();});};}}ModalLauncher.defaultProps={backdropDismissEnabled:true};class ModalLauncherKeypressListener extends React.Component{componentDidMount(){window.addEventListener("keyup",this._handleKeyup);}componentWillUnmount(){window.removeEventListener("keyup",this._handleKeyup);}render(){return null}constructor(...args){super(...args),this._handleKeyup=e=>{if(e.key==="Escape"){e.preventDefault();e.stopPropagation();this.props.onClose();}};}}const styles$3=StyleSheet.create({container:{zIndex:1080}});var modalLauncher = withActionScheduler(ModalLauncher);
35
+ const ModalLauncher=props=>{const{backdropDismissEnabled=true,children,closedFocusId,initialFocusId,modal,onClose,opened:controlledOpened,schedule,testId}=props;const lastElementFocusedOutsideModalRef=React.useRef(null);const[opened,setOpened]=React.useState(false);const isOpened=typeof controlledOpened==="boolean"?controlledOpened:opened;React.useEffect(()=>{if(typeof controlledOpened==="boolean"&&children){console.warn("'children' and 'opened' can't be used together");}if(typeof controlledOpened==="boolean"&&!onClose){console.warn("'onClose' should be used with 'opened'");}if(typeof controlledOpened!=="boolean"&&!children){console.warn("either 'children' or 'opened' must be set");}},[controlledOpened,children,onClose]);const saveLastElementFocused=React.useCallback(()=>{lastElementFocusedOutsideModalRef.current=document.activeElement;},[]);React.useEffect(()=>{if(!opened&&controlledOpened){saveLastElementFocused();}},[controlledOpened,opened,saveLastElementFocused]);const openModal=React.useCallback(()=>{saveLastElementFocused();setOpened(true);},[saveLastElementFocused]);const returnFocus=React.useCallback(()=>{if(closedFocusId){const focusElement=ReactDOM.findDOMNode(document.getElementById(closedFocusId));if(focusElement){schedule.animationFrame(()=>{focusElement.focus();});return}}const lastElement=lastElementFocusedOutsideModalRef.current;if(lastElement!=null){schedule.animationFrame(()=>{lastElement.focus();});}},[closedFocusId,schedule]);const handleCloseModal=React.useCallback(()=>{setOpened(false);onClose?.();returnFocus();},[onClose,returnFocus]);const renderModal=React.useCallback(()=>{if(typeof modal==="function"){return modal({closeModal:handleCloseModal})}else {return modal}},[modal,handleCloseModal]);const renderedChildren=children?children({openModal}):null;const{body}=document;if(!body){return null}return jsxs(ModalContext.Provider,{value:{closeModal:handleCloseModal},children:[renderedChildren,isOpened&&ReactDOM.createPortal(jsx(FocusTrap,{style:styles$4.container,children:jsx(ModalBackdrop,{initialFocusId:initialFocusId,testId:testId,onCloseModal:backdropDismissEnabled?handleCloseModal:()=>{},children:renderModal()})}),body),isOpened&&jsx(ModalLauncherKeypressListener,{onClose:handleCloseModal}),isOpened&&jsx(ScrollDisabler,{})]})};const ModalLauncherKeypressListener=({onClose})=>{const handleKeyup=React.useCallback(e=>{if(e.key==="Escape"){e.preventDefault();e.stopPropagation();onClose();}},[onClose]);React.useEffect(()=>{window.addEventListener("keyup",handleKeyup);return ()=>{window.removeEventListener("keyup",handleKeyup);}},[handleKeyup]);return null};const styles$4=StyleSheet.create({container:{zIndex:1080}});var modalLauncher = withActionScheduler(ModalLauncher);
37
36
 
38
- function ModalContent(props){const{scrollOverflow,style,children}=props;return jsx(View,{style:[styles$2.wrapper,scrollOverflow&&styles$2.scrollOverflow],children:jsx(View,{style:[styles$2.content,style],children:children})})}ModalContent.__IS_MODAL_CONTENT__=true;ModalContent.isComponentOf=instance=>{return instance&&instance.type&&instance.type.__IS_MODAL_CONTENT__};const small="@media (max-width: 767px)";const styles$2=StyleSheet.create({wrapper:{flex:1,display:"block"},scrollOverflow:{overflow:"auto"},content:{flex:1,minHeight:"100%",padding:theme.panel.layout.gap.default,boxSizing:"border-box",[small]:{paddingInline:theme.panel.layout.gap.small}}});ModalContent.defaultProps={scrollOverflow:true};
37
+ function ModalContent(props){const{scrollOverflow,style,children}=props;return jsx(View,{style:[styles$3.wrapper,scrollOverflow&&styles$3.scrollOverflow],children:jsx(View,{style:[styles$3.content,style],children:children})})}ModalContent.__IS_MODAL_CONTENT__=true;ModalContent.isComponentOf=instance=>{return instance&&instance.type&&instance.type.__IS_MODAL_CONTENT__};const styles$3=StyleSheet.create({wrapper:{flex:1,display:"block",[modalMediaQuery.smMinOrSmallerHeight]:{flex:"unset",minHeight:"unset",overflow:"unset"}},scrollOverflow:{overflow:"auto"},content:{flex:1,minHeight:"100%",padding:theme.panel.layout.gap.default,boxSizing:"border-box",[modalMediaQuery.midOrSmaller]:{paddingInline:theme.panel.layout.gap.small},[modalMediaQuery.smMinOrSmallerHeight]:{flex:"unset",minHeight:"unset",overflow:"unset"}}});ModalContent.defaultProps={scrollOverflow:true};
39
38
 
40
- class CloseButton extends React.Component{render(){const{onClick,style,testId}=this.props;return jsx(ModalContext.Consumer,{children:({closeModal})=>{if(closeModal&&onClick){throw new Error("You've specified 'onClose' on a modal when using ModalLauncher. Please specify 'onClose' on the ModalLauncher instead")}return jsx(IconButton,{icon:xIcon,"aria-label":"Close modal",onClick:onClick||closeModal,kind:"tertiary",actionType:"neutral",style:style,testId:testId})}})}}
39
+ const CloseButton=({onClick,style,testId})=>{return jsx(ModalContext.Consumer,{children:({closeModal})=>{if(closeModal&&onClick){throw new Error("You've specified 'onClose' on a modal when using ModalLauncher. Please specify 'onClose' on the ModalLauncher instead")}return jsx(IconButton,{icon:xIcon,"aria-label":"Close modal",onClick:onClick||closeModal,kind:"tertiary",actionType:"neutral",style:style,testId:testId})}})};
41
40
 
42
- function ModalPanel({closeButtonVisible=true,scrollOverflow=true,content,footer,header,onClose,style,testId}){const renderMainContent=React.useCallback(()=>{const mainContent=ModalContent.isComponentOf(content)?content:jsx(ModalContent,{children:content});if(!mainContent){return mainContent}return React.cloneElement(mainContent,{scrollOverflow,style:[!!footer&&styles$1.hasFooter,mainContent.props.style]})},[content,footer,scrollOverflow]);const mainContent=renderMainContent();return jsxs(View,{style:[styles$1.wrapper,style],testId:testId&&`${testId}-panel`,children:[closeButtonVisible&&jsx(CloseButton,{onClick:onClose,style:[styles$1.closeButton],testId:testId&&`${testId}-close`}),header,mainContent,!footer||ModalFooter.isComponentOf(footer)?footer:jsx(ModalFooter,{children:footer})]})}ModalPanel.defaultProps={closeButtonVisible:true,scrollOverflow:true};const styles$1=StyleSheet.create({wrapper:{flex:"1 1 auto",flexDirection:"column",background:semanticColor.core.background.base.default,boxSizing:"border-box",overflow:"hidden",height:"100%",width:"100%"},closeButton:{position:"absolute",insetInlineEnd:theme.closeButton.layout.gapRight,top:theme.closeButton.layout.gapTop,zIndex:1,":focus":focusStyles.focus[":focus-visible"]},hasFooter:{paddingBlockEnd:theme.panel.layout.gap.default}});
41
+ function ModalPanel({closeButtonVisible=true,scrollOverflow=true,content,footer,header,onClose,style,testId}){const renderMainContent=React.useCallback(()=>{const mainContent=ModalContent.isComponentOf(content)?content:jsx(ModalContent,{children:content});if(!mainContent){return mainContent}return React.cloneElement(mainContent,{scrollOverflow,style:[!!footer&&styles$2.hasFooter,mainContent.props.style]})},[content,footer,scrollOverflow]);const mainContent=renderMainContent();return jsxs(View,{style:[styles$2.wrapper,style],testId:testId&&`${testId}-panel`,children:[closeButtonVisible&&jsx(CloseButton,{onClick:onClose,style:[styles$2.closeButton],testId:testId&&`${testId}-close`}),header,mainContent,!footer||ModalFooter.isComponentOf(footer)?footer:jsx(ModalFooter,{children:footer})]})}ModalPanel.defaultProps={closeButtonVisible:true,scrollOverflow:true};const styles$2=StyleSheet.create({wrapper:{flex:"1 1 auto",flexDirection:"column",background:semanticColor.core.background.base.default,boxSizing:"border-box",overflow:"hidden",height:"100%",width:"100%",[modalMediaQuery.smMinOrSmallerHeight]:{overflow:"auto"}},closeButton:{position:"absolute",insetInlineEnd:theme.closeButton.layout.gapRight,top:theme.closeButton.layout.gapTop,zIndex:1,":focus":focusStyles.focus[":focus-visible"]},hasFooter:{paddingBlockEnd:theme.panel.layout.gap.default}});
43
42
 
44
- class OnePaneDialog extends React.Component{renderHeader(uniqueId){const{title,breadcrumbs=undefined,subtitle=undefined,testId}=this.props;if(breadcrumbs){return jsx(ModalHeader,{title:title,breadcrumbs:breadcrumbs,titleId:uniqueId,testId:testId&&`${testId}-header`})}else if(subtitle){return jsx(ModalHeader,{title:title,subtitle:subtitle,titleId:uniqueId,testId:testId&&`${testId}-header`})}else {return jsx(ModalHeader,{title:title,titleId:uniqueId,testId:testId&&`${testId}-header`})}}render(){const{onClose,footer,content,above,below,style,closeButtonVisible,testId,titleId,role,"aria-describedby":ariaDescribedBy}=this.props;return jsx(MediaLayout,{styleSheets:styleSheets,children:({styles})=>jsx(Id,{id:titleId,children:uniqueId=>jsx(ModalDialog,{style:[styles.dialog,style],above:above,below:below,testId:testId,"aria-labelledby":uniqueId,"aria-describedby":ariaDescribedBy,role:role,children:jsx(ModalPanel,{onClose:onClose,header:this.renderHeader(uniqueId),content:content,footer:footer,closeButtonVisible:closeButtonVisible,testId:testId})})})})}}OnePaneDialog.defaultProps={closeButtonVisible:true};const styleSheets={small:StyleSheet.create({dialog:{width:"100%",height:"100%",overflow:"hidden"}}),mdOrLarger:StyleSheet.create({dialog:{width:"93.75%",maxWidth:576,height:"81.25%",maxHeight:624}})};
43
+ const renderHeader=(props,uniqueId)=>{const{title,breadcrumbs=undefined,subtitle=undefined,testId}=props;if(breadcrumbs){return jsx(ModalHeader,{title:title,breadcrumbs:breadcrumbs,titleId:uniqueId,testId:testId&&`${testId}-header`})}else if(subtitle){return jsx(ModalHeader,{title:title,subtitle:subtitle,titleId:uniqueId,testId:testId&&`${testId}-header`})}else {return jsx(ModalHeader,{title:title,titleId:uniqueId,testId:testId&&`${testId}-header`})}};const OnePaneDialog=props=>{const{onClose,footer,content,above,below,style,closeButtonVisible=true,testId,titleId,role,"aria-describedby":ariaDescribedBy}=props;return jsx(Id,{id:titleId,children:uniqueId=>jsx(ModalDialog,{style:[styles$1.dialog,style],above:above,below:below,testId:testId,"aria-labelledby":uniqueId,"aria-describedby":ariaDescribedBy,role:role,children:jsx(ModalPanel,{onClose:onClose,header:renderHeader(props,uniqueId),content:content,footer:footer,closeButtonVisible:closeButtonVisible,testId:testId})})})};const styles$1=StyleSheet.create({dialog:{maxInlineSize:576,[modalMediaQuery.midOrSmaller]:{width:"100%",height:"100%",overflow:"hidden"},[modalMediaQuery.midOrLarger]:{width:"93.75%",height:"81.25%",maxBlockSize:624}}});
45
44
 
46
- function FlexiblePanel({closeButtonVisible=true,content,title,onClose,styles,testId}){const panelRef=React.useRef(null);const renderMainContent=React.useCallback(()=>{const contentNode=typeof content==="function"?content({title}):jsxs(Fragment,{children:[title,content]});const mainContent=ModalContent.isComponentOf(contentNode)?contentNode:jsx(ModalContent,{style:styles?.content,children:contentNode});if(!mainContent){return mainContent}return React.cloneElement(mainContent,{style:[mainContent.props.style]})},[title,content,styles?.content]);const mainContent=renderMainContent();const defaultBackgroundStyle={backgroundColor:semanticColor.core.background.base.default};const combinedBackgroundStyles={...defaultBackgroundStyle,...styles?.panel};return jsxs(View,{style:[componentStyles$1.wrapper,combinedBackgroundStyles],testId:testId&&`${testId}-panel`,ref:panelRef,children:[closeButtonVisible&&jsx(CloseButton,{onClick:onClose,style:[componentStyles$1.closeButton,styles?.closeButton],testId:testId&&`${testId}-close`}),mainContent]})}FlexiblePanel.defaultProps={closeButtonVisible:true,scrollOverflow:true};const componentStyles$1=StyleSheet.create({wrapper:{flex:"1 1 auto",flexDirection:"column",boxSizing:"border-box",overflow:"hidden",height:"100%",width:"100%"},closeButton:{position:"absolute",insetInlineEnd:theme.closeButton.layout.gapRight,top:theme.closeButton.layout.gapTop,zIndex:1,":focus":focusStyles.focus[":focus-visible"]}});
45
+ function FlexiblePanel({closeButtonVisible=true,content,title,onClose,styles,testId}){const panelRef=React.useRef(null);const renderMainContent=React.useCallback(()=>{const contentNode=typeof content==="function"?content({title}):jsxs(Fragment,{children:[title,content]});const mainContent=ModalContent.isComponentOf(contentNode)?contentNode:jsx(ModalContent,{style:styles?.content,children:contentNode});if(!mainContent){return mainContent}return React.cloneElement(mainContent,{style:[mainContent.props.style]})},[title,content,styles?.content]);const mainContent=renderMainContent();const defaultBackgroundStyle={backgroundColor:semanticColor.core.background.base.default};const combinedBackgroundStyles={...defaultBackgroundStyle,...styles?.panel};return jsxs(View,{style:[componentStyles$1.wrapper,combinedBackgroundStyles],testId:testId&&`${testId}-panel`,ref:panelRef,children:[closeButtonVisible&&jsx(CloseButton,{onClick:onClose,style:[componentStyles$1.closeButton,styles?.closeButton],testId:testId&&`${testId}-close`}),mainContent]})}FlexiblePanel.defaultProps={closeButtonVisible:true};const componentStyles$1=StyleSheet.create({wrapper:{flex:"1 1 auto",flexDirection:"column",boxSizing:"border-box",overflow:"hidden",height:"100%",width:"100%"},closeButton:{position:"absolute",insetInlineEnd:theme.closeButton.layout.gapRight,top:theme.closeButton.layout.gapTop,zIndex:1,":focus":focusStyles.focus[":focus-visible"]}});
47
46
 
48
47
  const FlexibleDialog=React.forwardRef(function FlexibleDialog(props,ref){const{onClose,title,content,styles,closeButtonVisible=true,testId,titleId,role="dialog",...accessibilityProps}=props;const uniqueId=React.useId();const headingId=titleId??uniqueId;const renderedTitle=title==null?null:typeof title==="string"?jsx(Heading,{id:headingId,children:title}):React.cloneElement(title,{id:headingId,testId:"title-heading-wrapper"});return jsx(View,{style:[componentStyles.root,styles?.root],children:jsx(View,{role:role,"aria-modal":"true","aria-label":accessibilityProps["aria-label"],"aria-labelledby":headingId,"aria-describedby":accessibilityProps["aria-describedby"],ref:ref,testId:testId,style:[componentStyles.dialog,styles?.dialog],children:jsx(FlexiblePanel,{styles:{panel:styles?.panel,content:styles?.content,closeButton:styles?.closeButton},onClose:onClose,title:renderedTitle,content:content,closeButtonVisible:closeButtonVisible,testId:testId})})})});const componentStyles=StyleSheet.create({root:{boxShadow:theme.dialog.shadow.default,color:semanticColor.core.foreground.neutral.strong,overflow:"auto",position:"relative",willChange:"transform, opacity",height:"auto",maxHeight:"100vh",maxWidth:576,width:"93.75%"}});
49
48
 
package/dist/index.js CHANGED
@@ -13,7 +13,6 @@ var wonderBlocksTiming = require('@khanacademy/wonder-blocks-timing');
13
13
  var wonderBlocksStyles = require('@khanacademy/wonder-blocks-styles');
14
14
  var xIcon = require('@phosphor-icons/core/bold/x-bold.svg');
15
15
  var IconButton = require('@khanacademy/wonder-blocks-icon-button');
16
- var wonderBlocksLayout = require('@khanacademy/wonder-blocks-layout');
17
16
 
18
17
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
19
18
 
@@ -40,39 +39,39 @@ var ReactDOM__namespace = /*#__PURE__*/_interopNamespace(ReactDOM);
40
39
  var xIcon__default = /*#__PURE__*/_interopDefaultLegacy(xIcon);
41
40
  var IconButton__default = /*#__PURE__*/_interopDefaultLegacy(IconButton);
42
41
 
42
+ const ModalLauncherPortalAttributeName="data-modal-launcher-portal";const height={smMin:500};const width={mid:767};const modalMediaQuery={midOrSmaller:`@media screen and (max-width: ${width.mid}px) /* breakpoint.mediaQuery.midOrSmaller */`,midOrLarger:`@media screen and (min-width: ${width.mid}px) /* breakpoint.mediaQuery.midOrLarger */`,smMinOrSmallerHeight:`@media screen and (max-height:${height.smMin}px)`};
43
+
43
44
  const theme$1={root:{border:{radius:wonderBlocksTokens.border.radius.radius_040}},dialog:{layout:{padding:wonderBlocksTokens.sizing.size_160},shadow:{default:"none"}},header:{layout:{padding:{block:wonderBlocksTokens.sizing.size_240,inline:{default:wonderBlocksTokens.sizing.size_320,small:wonderBlocksTokens.sizing.size_160}},gap:{default:wonderBlocksTokens.sizing.size_080,title:{default:wonderBlocksTokens.sizing.size_160,small:wonderBlocksTokens.sizing.size_320}}}},panel:{layout:{gap:{default:wonderBlocksTokens.sizing.size_320,small:wonderBlocksTokens.sizing.size_160}}},footer:{layout:{padding:{inline:wonderBlocksTokens.sizing.size_160,block:wonderBlocksTokens.sizing.size_080}}},closeButton:{layout:{gapRight:wonderBlocksTokens.sizing.size_080,gapTop:wonderBlocksTokens.sizing.size_080}}};
44
45
 
45
46
  var theme = wonderBlocksTokens.mapValuesToCssVars(theme$1,"--wb-c-modal-");
46
47
 
47
- const ModalDialog=React__namespace.forwardRef(function ModalDialog(props,ref){const{above,below,role="dialog",style,children,testId,"aria-label":ariaLabel,"aria-labelledby":ariaLabelledBy,"aria-describedby":ariaDescribedBy}=props;return jsxRuntime.jsxs(wonderBlocksCore.View,{style:[styles$7.wrapper,style],children:[below&&jsxRuntime.jsx(wonderBlocksCore.View,{style:styles$7.below,children:below}),jsxRuntime.jsx(wonderBlocksCore.View,{role:role,"aria-modal":"true","aria-label":ariaLabel,"aria-labelledby":ariaLabelledBy,"aria-describedby":ariaDescribedBy,ref:ref,style:styles$7.dialog,testId:testId,children:children}),above&&jsxRuntime.jsx(wonderBlocksCore.View,{style:styles$7.above,children:above})]})});const small$2="@media (max-width: 767px)";const styles$7=aphrodite.StyleSheet.create({wrapper:{color:wonderBlocksTokens.semanticColor.core.foreground.neutral.strong,flexDirection:"row",alignItems:"stretch",width:"100%",height:"100%",position:"relative",boxShadow:theme.dialog.shadow.default,borderRadius:theme.root.border.radius,[small$2]:{padding:theme.dialog.layout.padding,flexDirection:"column"}},dialog:{width:"100%",height:"100%",borderRadius:theme.root.border.radius,overflow:"hidden"},above:{pointerEvents:"none",position:"absolute",top:0,left:0,bottom:0,right:0,zIndex:1},below:{pointerEvents:"none",position:"absolute",top:0,left:0,bottom:0,right:0,zIndex:-1}});ModalDialog.displayName="ModalDialog";
48
-
49
- function ModalFooter({children}){return jsxRuntime.jsx(wonderBlocksCore.View,{style:styles$6.footer,children:children})}ModalFooter.__IS_MODAL_FOOTER__=true;ModalFooter.isComponentOf=instance=>{return instance&&instance.type&&instance.type.__IS_MODAL_FOOTER__};const styles$6=aphrodite.StyleSheet.create({footer:{flex:"0 0 auto",boxSizing:"border-box",minHeight:wonderBlocksTokens.sizing.size_640,paddingInline:theme.footer.layout.padding.inline,paddingBlock:theme.footer.layout.padding.block,display:"flex",flexDirection:"row",alignItems:"center",justifyContent:"flex-end",boxShadow:`0px -1px 0px ${wonderBlocksTokens.semanticColor.core.border.neutral.subtle}`}});
48
+ const ModalDialog=React__namespace.forwardRef(function ModalDialog(props,ref){const{above,below,role="dialog",style,children,testId,"aria-label":ariaLabel,"aria-labelledby":ariaLabelledBy,"aria-describedby":ariaDescribedBy}=props;return jsxRuntime.jsxs(wonderBlocksCore.View,{style:[componentStyles$2.wrapper,style],children:[below&&jsxRuntime.jsx(wonderBlocksCore.View,{style:componentStyles$2.below,children:below}),jsxRuntime.jsx(wonderBlocksCore.View,{role:role,"aria-modal":"true","aria-label":ariaLabel,"aria-labelledby":ariaLabelledBy,"aria-describedby":ariaDescribedBy,ref:ref,style:[componentStyles$2.dialog],testId:testId,children:children}),above&&jsxRuntime.jsx(wonderBlocksCore.View,{style:componentStyles$2.above,children:above})]})});const componentStyles$2=aphrodite.StyleSheet.create({wrapper:{color:wonderBlocksTokens.semanticColor.core.foreground.neutral.strong,flexDirection:"row",alignItems:"stretch",width:"100%",height:"100%",position:"relative",boxShadow:theme.dialog.shadow.default,borderRadius:theme.root.border.radius,[modalMediaQuery.midOrSmaller]:{padding:theme.dialog.layout.padding,flexDirection:"column"},[modalMediaQuery.smMinOrSmallerHeight]:{overflow:"auto"}},dialog:{width:"100%",height:"100%",borderRadius:theme.root.border.radius,overflow:"hidden",[modalMediaQuery.smMinOrSmallerHeight]:{overflow:"auto"}},above:{pointerEvents:"none",position:"absolute",top:0,left:0,bottom:0,right:0,zIndex:1},below:{pointerEvents:"none",position:"absolute",top:0,left:0,bottom:0,right:0,zIndex:-1}});ModalDialog.displayName="ModalDialog";
50
49
 
51
- function ModalHeader(props){const{breadcrumbs=undefined,subtitle=undefined,testId,title,titleId}=props;if(subtitle&&breadcrumbs){throw new Error("'subtitle' and 'breadcrumbs' can't be used together")}return jsxRuntime.jsxs(wonderBlocksCore.View,{style:[styles$5.header],testId:testId,children:[breadcrumbs&&jsxRuntime.jsx(wonderBlocksCore.View,{style:styles$5.breadcrumbs,children:breadcrumbs}),jsxRuntime.jsx(wonderBlocksTypography.Heading,{size:"large",tag:"h2",style:styles$5.title,id:titleId,testId:testId&&`${testId}-title`,children:title}),subtitle&&jsxRuntime.jsx(wonderBlocksTypography.BodyText,{size:"small",style:styles$5.subtitle,testId:testId&&`${testId}-subtitle`,children:subtitle})]})}const small$1="@media (max-width: 767px)";const styles$5=aphrodite.StyleSheet.create({header:{boxShadow:`0px 1px 0px ${wonderBlocksTokens.semanticColor.core.border.neutral.subtle}`,display:"flex",flexDirection:"column",minHeight:66,paddingBlock:theme.header.layout.padding.block,paddingInline:theme.header.layout.padding.inline.default,position:"relative",width:"100%",[small$1]:{paddingInline:theme.header.layout.padding.inline.small}},breadcrumbs:{color:wonderBlocksTokens.semanticColor.core.foreground.neutral.default,marginBlockEnd:theme.header.layout.gap.default},title:{paddingInlineEnd:theme.header.layout.gap.title.default,[small$1]:{paddingInlineEnd:theme.header.layout.gap.title.small}},subtitle:{color:wonderBlocksTokens.semanticColor.core.foreground.neutral.default,marginBlockStart:theme.header.layout.gap.default}});
50
+ function ModalFooter({children}){return jsxRuntime.jsx(wonderBlocksCore.View,{style:styles$7.footer,children:children})}ModalFooter.__IS_MODAL_FOOTER__=true;ModalFooter.isComponentOf=instance=>{return instance&&instance.type&&instance.type.__IS_MODAL_FOOTER__};const styles$7=aphrodite.StyleSheet.create({footer:{flex:"0 0 auto",boxSizing:"border-box",minHeight:wonderBlocksTokens.sizing.size_640,paddingInline:theme.footer.layout.padding.inline,paddingBlock:theme.footer.layout.padding.block,display:"flex",flexDirection:"row",alignItems:"center",justifyContent:"flex-end",boxShadow:`0px -1px 0px ${wonderBlocksTokens.semanticColor.core.border.neutral.subtle}`}});
52
51
 
53
- const FOCUSABLE_ELEMENTS$1='button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';class FocusTrap extends React__namespace.Component{tryToFocus(node){if(node instanceof HTMLElement){try{node.focus();}catch(e){}return document.activeElement===node}}focusElementIn(isLast){const modalRootAsHtmlEl=this.modalRoot;const focusableNodes=Array.from(modalRootAsHtmlEl.querySelectorAll(FOCUSABLE_ELEMENTS$1)).filter(element=>{const style=window.getComputedStyle(element);return style.display!=="none"&&style.visibility!=="hidden"});const nodeIndex=!isLast?focusableNodes.length-1:0;const focusableNode=focusableNodes[nodeIndex];this.tryToFocus(focusableNode);}render(){const{style}=this.props;return jsxRuntime.jsxs(React__namespace.Fragment,{children:[jsxRuntime.jsx("div",{tabIndex:0,className:"modal-focus-trap-first",onFocus:this.handleFocusMoveToLast,style:{position:"fixed"}}),jsxRuntime.jsx(wonderBlocksCore.View,{style:style,ref:this.getModalRoot,children:this.props.children}),jsxRuntime.jsx("div",{tabIndex:0,className:"modal-focus-trap-last",onFocus:this.handleFocusMoveToFirst,style:{position:"fixed"}})]})}constructor(...args){super(...args),this.getModalRoot=node=>{if(!node){return}const modalRoot=ReactDOM__namespace.findDOMNode(node);if(!modalRoot){throw new Error("Assertion error: modal root should exist after mount")}this.modalRoot=modalRoot;},this.handleFocusMoveToLast=()=>{this.focusElementIn(false);},this.handleFocusMoveToFirst=()=>{this.focusElementIn(true);};}}
52
+ function ModalHeader(props){const{breadcrumbs=undefined,subtitle=undefined,testId,title,titleId}=props;if(subtitle&&breadcrumbs){throw new Error("'subtitle' and 'breadcrumbs' can't be used together")}return jsxRuntime.jsxs(wonderBlocksCore.View,{style:[styles$6.header],testId:testId,children:[breadcrumbs&&jsxRuntime.jsx(wonderBlocksCore.View,{style:styles$6.breadcrumbs,children:breadcrumbs}),jsxRuntime.jsx(wonderBlocksTypography.Heading,{size:"large",tag:"h2",style:styles$6.title,id:titleId,testId:testId&&`${testId}-title`,children:title}),subtitle&&jsxRuntime.jsx(wonderBlocksTypography.BodyText,{size:"small",style:styles$6.subtitle,testId:testId&&`${testId}-subtitle`,children:subtitle})]})}const styles$6=aphrodite.StyleSheet.create({header:{boxShadow:`0px 1px 0px ${wonderBlocksTokens.semanticColor.core.border.neutral.subtle}`,display:"flex",flexDirection:"column",minHeight:66,paddingBlock:theme.header.layout.padding.block,paddingInline:theme.header.layout.padding.inline.default,position:"relative",width:"100%",[modalMediaQuery.midOrSmaller]:{paddingInline:theme.header.layout.padding.inline.small},[modalMediaQuery.smMinOrSmallerHeight]:{minHeight:"unset"}},breadcrumbs:{color:wonderBlocksTokens.semanticColor.core.foreground.neutral.default,marginBlockEnd:theme.header.layout.gap.default},title:{paddingInlineEnd:theme.header.layout.gap.title.default,[modalMediaQuery.midOrSmaller]:{paddingInlineEnd:theme.header.layout.gap.title.small}},subtitle:{color:wonderBlocksTokens.semanticColor.core.foreground.neutral.default,marginBlockStart:theme.header.layout.gap.default}});
54
53
 
55
- const ModalLauncherPortalAttributeName="data-modal-launcher-portal";
54
+ const FOCUSABLE_ELEMENTS$1='button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';const FocusTrap=({children,style})=>{const modalRootRef=React__namespace.useRef(null);const tryToFocus=React__namespace.useCallback(node=>{if(node instanceof HTMLElement){try{node.focus();}catch(e){}return document.activeElement===node}},[]);const focusElementIn=React__namespace.useCallback(isLast=>{if(!modalRootRef.current){return}const modalRootAsHtmlEl=modalRootRef.current;const focusableNodes=Array.from(modalRootAsHtmlEl.querySelectorAll(FOCUSABLE_ELEMENTS$1)).filter(element=>{const style=window.getComputedStyle(element);return style.display!=="none"&&style.visibility!=="hidden"});const nodeIndex=!isLast?focusableNodes.length-1:0;const focusableNode=focusableNodes[nodeIndex];tryToFocus(focusableNode);},[tryToFocus]);const handleFocusMoveToLast=React__namespace.useCallback(()=>{focusElementIn(false);},[focusElementIn]);const handleFocusMoveToFirst=React__namespace.useCallback(()=>{focusElementIn(true);},[focusElementIn]);const getModalRoot=React__namespace.useCallback(node=>{if(!node){return}const modalRoot=ReactDOM__namespace.findDOMNode(node);if(!modalRoot){throw new Error("Assertion error: modal root should exist after mount")}modalRootRef.current=modalRoot;},[]);return jsxRuntime.jsxs(React__namespace.Fragment,{children:[jsxRuntime.jsx("div",{tabIndex:0,className:"modal-focus-trap-first",onFocus:handleFocusMoveToLast,style:{position:"fixed"}}),jsxRuntime.jsx(wonderBlocksCore.View,{style:style,ref:getModalRoot,children:children}),jsxRuntime.jsx("div",{tabIndex:0,className:"modal-focus-trap-last",onFocus:handleFocusMoveToFirst,style:{position:"fixed"}})]})};
56
55
 
57
56
  const FOCUSABLE_ELEMENTS="a[href], details, input, textarea, select, button";function findFocusableNodes(root){return Array.from(root.querySelectorAll(FOCUSABLE_ELEMENTS))}
58
57
 
59
- class ModalBackdrop extends React__namespace.Component{componentDidMount(){const node=ReactDOM__namespace.findDOMNode(this);if(!node){return}const firstFocusableElement=this._getInitialFocusElement(node)||this._getFirstFocusableElement(node)||this._getDialogElement(node);setTimeout(()=>{firstFocusableElement.focus();},0);}_getInitialFocusElement(node){const{initialFocusId}=this.props;if(!initialFocusId){return null}return ReactDOM__namespace.findDOMNode(node.querySelector(`#${initialFocusId}`))}_getFirstFocusableElement(node){const focusableElements=findFocusableNodes(node);if(!focusableElements){return null}return focusableElements[0]}_getDialogElement(node){const dialogElement=ReactDOM__namespace.findDOMNode(node.querySelector('[role="dialog"]'));dialogElement.tabIndex=-1;return dialogElement}render(){const{children,testId}=this.props;const backdropProps={[ModalLauncherPortalAttributeName]:true};return jsxRuntime.jsx(wonderBlocksCore.View,{style:styles$4.modalPositioner,onMouseDown:this.handleMouseDown,onMouseUp:this.handleMouseUp,testId:testId,...backdropProps,children:children})}constructor(...args){super(...args),this._mousePressedOutside=false,this.handleMouseDown=e=>{this._mousePressedOutside=e.target===e.currentTarget;},this.handleMouseUp=e=>{if(e.target===e.currentTarget&&this._mousePressedOutside){this.props.onCloseModal();}this._mousePressedOutside=false;};}}const styles$4=aphrodite.StyleSheet.create({modalPositioner:{position:"fixed",left:0,top:0,width:"100%",height:"100%",alignItems:"center",justifyContent:"center",overflow:"auto",background:wonderBlocksTokens.semanticColor.core.background.overlay.default}});
58
+ const getInitialFocusElement=(node,initialFocusId)=>{if(!initialFocusId){return null}return ReactDOM__namespace.findDOMNode(node.querySelector(`#${initialFocusId}`))};const getFirstFocusableElement=node=>{const focusableElements=findFocusableNodes(node);if(!focusableElements){return null}return focusableElements[0]};const getDialogElement=node=>{const dialogElement=ReactDOM__namespace.findDOMNode(node.querySelector('[role="dialog"]'));dialogElement.tabIndex=-1;return dialogElement};const ModalBackdrop=({children,initialFocusId,onCloseModal,testId})=>{const backdropRef=React__namespace.useRef(null);const[mousePressedOutside,setMousePressedOutside]=React__namespace.useState(false);React__namespace.useEffect(()=>{const node=ReactDOM__namespace.findDOMNode(backdropRef.current);if(!node){return}const firstFocusableElement=getInitialFocusElement(node,initialFocusId)||getFirstFocusableElement(node)||getDialogElement(node);setTimeout(()=>{firstFocusableElement.focus();},0);},[initialFocusId]);const handleMouseDown=React__namespace.useCallback(e=>{setMousePressedOutside(e.target===e.currentTarget);},[]);const handleMouseUp=React__namespace.useCallback(e=>{if(e.target===e.currentTarget&&mousePressedOutside){onCloseModal();}setMousePressedOutside(false);},[mousePressedOutside,onCloseModal]);const backdropProps={[ModalLauncherPortalAttributeName]:true};return jsxRuntime.jsx(wonderBlocksCore.View,{ref:backdropRef,style:styles$5.modalPositioner,onMouseDown:handleMouseDown,onMouseUp:handleMouseUp,testId:testId,...backdropProps,children:children})};const styles$5=aphrodite.StyleSheet.create({modalPositioner:{position:"fixed",left:0,top:0,width:"100%",height:"100%",alignItems:"center",justifyContent:"center",overflow:"auto",background:wonderBlocksTokens.semanticColor.core.background.overlay.default}});
60
59
 
61
- const needsHackyMobileSafariScrollDisabler=(()=>{if(typeof window==="undefined"){return false}const userAgent=window.navigator.userAgent;return userAgent.indexOf("iPad")>-1||userAgent.indexOf("iPhone")>-1})();class ScrollDisabler extends React__namespace.Component{componentDidMount(){if(ScrollDisabler.numModalsOpened===0){const body=document.body;if(!body){throw new Error("couldn't find document.body")}ScrollDisabler.oldOverflow=body.style.overflow;ScrollDisabler.oldScrollY=window.scrollY;if(needsHackyMobileSafariScrollDisabler){ScrollDisabler.oldPosition=body.style.position;ScrollDisabler.oldWidth=body.style.width;ScrollDisabler.oldTop=body.style.top;}body.style.overflow="hidden";if(needsHackyMobileSafariScrollDisabler){body.style.position="fixed";body.style.width="100%";body.style.top=`${-ScrollDisabler.oldScrollY}px`;}}ScrollDisabler.numModalsOpened++;}componentWillUnmount(){ScrollDisabler.numModalsOpened--;if(ScrollDisabler.numModalsOpened===0){const body=document.body;if(!body){throw new Error("couldn't find document.body")}body.style.overflow=ScrollDisabler.oldOverflow;if(needsHackyMobileSafariScrollDisabler){body.style.position=ScrollDisabler.oldPosition;body.style.width=ScrollDisabler.oldWidth;body.style.top=ScrollDisabler.oldTop;}if(typeof window!=="undefined"&&window.scrollTo){window.scrollTo(0,ScrollDisabler.oldScrollY);}}}render(){return null}}ScrollDisabler.numModalsOpened=0;
60
+ const needsHackyMobileSafariScrollDisabler=(()=>{if(typeof window==="undefined"){return false}const userAgent=window.navigator.userAgent;return userAgent.indexOf("iPad")>-1||userAgent.indexOf("iPhone")>-1})();let numModalsOpened=0;let oldOverflow;let oldPosition;let oldScrollY;let oldWidth;let oldTop;const ScrollDisabler=_props=>{React__namespace.useEffect(()=>{if(numModalsOpened===0){const body=document.body;if(!body){throw new Error("couldn't find document.body")}oldOverflow=body.style.overflow;oldScrollY=window.scrollY;if(needsHackyMobileSafariScrollDisabler){oldPosition=body.style.position;oldWidth=body.style.width;oldTop=body.style.top;}body.style.overflow="hidden";if(needsHackyMobileSafariScrollDisabler){body.style.position="fixed";body.style.width="100%";body.style.top=`${-oldScrollY}px`;}}numModalsOpened++;return ()=>{numModalsOpened--;if(numModalsOpened===0){const body=document.body;if(!body){throw new Error("couldn't find document.body")}body.style.overflow=oldOverflow;if(needsHackyMobileSafariScrollDisabler){body.style.position=oldPosition;body.style.width=oldWidth;body.style.top=oldTop;}if(typeof window!=="undefined"&&window.scrollTo){window.scrollTo(0,oldScrollY);}}}},[]);return null};
62
61
 
63
62
  const defaultContext={closeModal:undefined};const ModalContext=React__namespace.createContext(defaultContext);ModalContext.displayName="ModalContext";
64
63
 
65
- class ModalLauncher extends React__namespace.Component{static getDerivedStateFromProps(props,state){if(typeof props.opened==="boolean"&&props.children){console.warn("'children' and 'opened' can't be used together");}if(typeof props.opened==="boolean"&&!props.onClose){console.warn("'onClose' should be used with 'opened'");}if(typeof props.opened!=="boolean"&&!props.children){console.warn("either 'children' or 'opened' must be set");}return {opened:typeof props.opened==="boolean"?props.opened:state.opened}}componentDidUpdate(prevProps){if(!prevProps.opened&&this.props.opened){this._saveLastElementFocused();}}_renderModal(){if(typeof this.props.modal==="function"){return this.props.modal({closeModal:this.handleCloseModal})}else {return this.props.modal}}render(){const renderedChildren=this.props.children?this.props.children({openModal:this._openModal}):null;const{body}=document;if(!body){return null}return jsxRuntime.jsxs(ModalContext.Provider,{value:{closeModal:this.handleCloseModal},children:[renderedChildren,this.state.opened&&ReactDOM__namespace.createPortal(jsxRuntime.jsx(FocusTrap,{style:styles$3.container,children:jsxRuntime.jsx(ModalBackdrop,{initialFocusId:this.props.initialFocusId,testId:this.props.testId,onCloseModal:this.props.backdropDismissEnabled?this.handleCloseModal:()=>{},children:this._renderModal()})}),body),this.state.opened&&jsxRuntime.jsx(ModalLauncherKeypressListener,{onClose:this.handleCloseModal}),this.state.opened&&jsxRuntime.jsx(ScrollDisabler,{})]})}constructor(...args){super(...args),this.state={opened:false},this._saveLastElementFocused=()=>{this.lastElementFocusedOutsideModal=document.activeElement;},this._openModal=()=>{this._saveLastElementFocused();this.setState({opened:true});},this._returnFocus=()=>{const{closedFocusId,schedule}=this.props;const lastElement=this.lastElementFocusedOutsideModal;if(closedFocusId){const focusElement=ReactDOM__namespace.findDOMNode(document.getElementById(closedFocusId));if(focusElement){schedule.animationFrame(()=>{focusElement.focus();});return}}if(lastElement!=null){schedule.animationFrame(()=>{lastElement.focus();});}},this.handleCloseModal=()=>{this.setState({opened:false},()=>{const{onClose}=this.props;onClose?.();this._returnFocus();});};}}ModalLauncher.defaultProps={backdropDismissEnabled:true};class ModalLauncherKeypressListener extends React__namespace.Component{componentDidMount(){window.addEventListener("keyup",this._handleKeyup);}componentWillUnmount(){window.removeEventListener("keyup",this._handleKeyup);}render(){return null}constructor(...args){super(...args),this._handleKeyup=e=>{if(e.key==="Escape"){e.preventDefault();e.stopPropagation();this.props.onClose();}};}}const styles$3=aphrodite.StyleSheet.create({container:{zIndex:1080}});var modalLauncher = wonderBlocksTiming.withActionScheduler(ModalLauncher);
64
+ const ModalLauncher=props=>{const{backdropDismissEnabled=true,children,closedFocusId,initialFocusId,modal,onClose,opened:controlledOpened,schedule,testId}=props;const lastElementFocusedOutsideModalRef=React__namespace.useRef(null);const[opened,setOpened]=React__namespace.useState(false);const isOpened=typeof controlledOpened==="boolean"?controlledOpened:opened;React__namespace.useEffect(()=>{if(typeof controlledOpened==="boolean"&&children){console.warn("'children' and 'opened' can't be used together");}if(typeof controlledOpened==="boolean"&&!onClose){console.warn("'onClose' should be used with 'opened'");}if(typeof controlledOpened!=="boolean"&&!children){console.warn("either 'children' or 'opened' must be set");}},[controlledOpened,children,onClose]);const saveLastElementFocused=React__namespace.useCallback(()=>{lastElementFocusedOutsideModalRef.current=document.activeElement;},[]);React__namespace.useEffect(()=>{if(!opened&&controlledOpened){saveLastElementFocused();}},[controlledOpened,opened,saveLastElementFocused]);const openModal=React__namespace.useCallback(()=>{saveLastElementFocused();setOpened(true);},[saveLastElementFocused]);const returnFocus=React__namespace.useCallback(()=>{if(closedFocusId){const focusElement=ReactDOM__namespace.findDOMNode(document.getElementById(closedFocusId));if(focusElement){schedule.animationFrame(()=>{focusElement.focus();});return}}const lastElement=lastElementFocusedOutsideModalRef.current;if(lastElement!=null){schedule.animationFrame(()=>{lastElement.focus();});}},[closedFocusId,schedule]);const handleCloseModal=React__namespace.useCallback(()=>{setOpened(false);onClose?.();returnFocus();},[onClose,returnFocus]);const renderModal=React__namespace.useCallback(()=>{if(typeof modal==="function"){return modal({closeModal:handleCloseModal})}else {return modal}},[modal,handleCloseModal]);const renderedChildren=children?children({openModal}):null;const{body}=document;if(!body){return null}return jsxRuntime.jsxs(ModalContext.Provider,{value:{closeModal:handleCloseModal},children:[renderedChildren,isOpened&&ReactDOM__namespace.createPortal(jsxRuntime.jsx(FocusTrap,{style:styles$4.container,children:jsxRuntime.jsx(ModalBackdrop,{initialFocusId:initialFocusId,testId:testId,onCloseModal:backdropDismissEnabled?handleCloseModal:()=>{},children:renderModal()})}),body),isOpened&&jsxRuntime.jsx(ModalLauncherKeypressListener,{onClose:handleCloseModal}),isOpened&&jsxRuntime.jsx(ScrollDisabler,{})]})};const ModalLauncherKeypressListener=({onClose})=>{const handleKeyup=React__namespace.useCallback(e=>{if(e.key==="Escape"){e.preventDefault();e.stopPropagation();onClose();}},[onClose]);React__namespace.useEffect(()=>{window.addEventListener("keyup",handleKeyup);return ()=>{window.removeEventListener("keyup",handleKeyup);}},[handleKeyup]);return null};const styles$4=aphrodite.StyleSheet.create({container:{zIndex:1080}});var modalLauncher = wonderBlocksTiming.withActionScheduler(ModalLauncher);
66
65
 
67
- function ModalContent(props){const{scrollOverflow,style,children}=props;return jsxRuntime.jsx(wonderBlocksCore.View,{style:[styles$2.wrapper,scrollOverflow&&styles$2.scrollOverflow],children:jsxRuntime.jsx(wonderBlocksCore.View,{style:[styles$2.content,style],children:children})})}ModalContent.__IS_MODAL_CONTENT__=true;ModalContent.isComponentOf=instance=>{return instance&&instance.type&&instance.type.__IS_MODAL_CONTENT__};const small="@media (max-width: 767px)";const styles$2=aphrodite.StyleSheet.create({wrapper:{flex:1,display:"block"},scrollOverflow:{overflow:"auto"},content:{flex:1,minHeight:"100%",padding:theme.panel.layout.gap.default,boxSizing:"border-box",[small]:{paddingInline:theme.panel.layout.gap.small}}});ModalContent.defaultProps={scrollOverflow:true};
66
+ function ModalContent(props){const{scrollOverflow,style,children}=props;return jsxRuntime.jsx(wonderBlocksCore.View,{style:[styles$3.wrapper,scrollOverflow&&styles$3.scrollOverflow],children:jsxRuntime.jsx(wonderBlocksCore.View,{style:[styles$3.content,style],children:children})})}ModalContent.__IS_MODAL_CONTENT__=true;ModalContent.isComponentOf=instance=>{return instance&&instance.type&&instance.type.__IS_MODAL_CONTENT__};const styles$3=aphrodite.StyleSheet.create({wrapper:{flex:1,display:"block",[modalMediaQuery.smMinOrSmallerHeight]:{flex:"unset",minHeight:"unset",overflow:"unset"}},scrollOverflow:{overflow:"auto"},content:{flex:1,minHeight:"100%",padding:theme.panel.layout.gap.default,boxSizing:"border-box",[modalMediaQuery.midOrSmaller]:{paddingInline:theme.panel.layout.gap.small},[modalMediaQuery.smMinOrSmallerHeight]:{flex:"unset",minHeight:"unset",overflow:"unset"}}});ModalContent.defaultProps={scrollOverflow:true};
68
67
 
69
- class CloseButton extends React__namespace.Component{render(){const{onClick,style,testId}=this.props;return jsxRuntime.jsx(ModalContext.Consumer,{children:({closeModal})=>{if(closeModal&&onClick){throw new Error("You've specified 'onClose' on a modal when using ModalLauncher. Please specify 'onClose' on the ModalLauncher instead")}return jsxRuntime.jsx(IconButton__default["default"],{icon:xIcon__default["default"],"aria-label":"Close modal",onClick:onClick||closeModal,kind:"tertiary",actionType:"neutral",style:style,testId:testId})}})}}
68
+ const CloseButton=({onClick,style,testId})=>{return jsxRuntime.jsx(ModalContext.Consumer,{children:({closeModal})=>{if(closeModal&&onClick){throw new Error("You've specified 'onClose' on a modal when using ModalLauncher. Please specify 'onClose' on the ModalLauncher instead")}return jsxRuntime.jsx(IconButton__default["default"],{icon:xIcon__default["default"],"aria-label":"Close modal",onClick:onClick||closeModal,kind:"tertiary",actionType:"neutral",style:style,testId:testId})}})};
70
69
 
71
- function ModalPanel({closeButtonVisible=true,scrollOverflow=true,content,footer,header,onClose,style,testId}){const renderMainContent=React__namespace.useCallback(()=>{const mainContent=ModalContent.isComponentOf(content)?content:jsxRuntime.jsx(ModalContent,{children:content});if(!mainContent){return mainContent}return React__namespace.cloneElement(mainContent,{scrollOverflow,style:[!!footer&&styles$1.hasFooter,mainContent.props.style]})},[content,footer,scrollOverflow]);const mainContent=renderMainContent();return jsxRuntime.jsxs(wonderBlocksCore.View,{style:[styles$1.wrapper,style],testId:testId&&`${testId}-panel`,children:[closeButtonVisible&&jsxRuntime.jsx(CloseButton,{onClick:onClose,style:[styles$1.closeButton],testId:testId&&`${testId}-close`}),header,mainContent,!footer||ModalFooter.isComponentOf(footer)?footer:jsxRuntime.jsx(ModalFooter,{children:footer})]})}ModalPanel.defaultProps={closeButtonVisible:true,scrollOverflow:true};const styles$1=aphrodite.StyleSheet.create({wrapper:{flex:"1 1 auto",flexDirection:"column",background:wonderBlocksTokens.semanticColor.core.background.base.default,boxSizing:"border-box",overflow:"hidden",height:"100%",width:"100%"},closeButton:{position:"absolute",insetInlineEnd:theme.closeButton.layout.gapRight,top:theme.closeButton.layout.gapTop,zIndex:1,":focus":wonderBlocksStyles.focusStyles.focus[":focus-visible"]},hasFooter:{paddingBlockEnd:theme.panel.layout.gap.default}});
70
+ function ModalPanel({closeButtonVisible=true,scrollOverflow=true,content,footer,header,onClose,style,testId}){const renderMainContent=React__namespace.useCallback(()=>{const mainContent=ModalContent.isComponentOf(content)?content:jsxRuntime.jsx(ModalContent,{children:content});if(!mainContent){return mainContent}return React__namespace.cloneElement(mainContent,{scrollOverflow,style:[!!footer&&styles$2.hasFooter,mainContent.props.style]})},[content,footer,scrollOverflow]);const mainContent=renderMainContent();return jsxRuntime.jsxs(wonderBlocksCore.View,{style:[styles$2.wrapper,style],testId:testId&&`${testId}-panel`,children:[closeButtonVisible&&jsxRuntime.jsx(CloseButton,{onClick:onClose,style:[styles$2.closeButton],testId:testId&&`${testId}-close`}),header,mainContent,!footer||ModalFooter.isComponentOf(footer)?footer:jsxRuntime.jsx(ModalFooter,{children:footer})]})}ModalPanel.defaultProps={closeButtonVisible:true,scrollOverflow:true};const styles$2=aphrodite.StyleSheet.create({wrapper:{flex:"1 1 auto",flexDirection:"column",background:wonderBlocksTokens.semanticColor.core.background.base.default,boxSizing:"border-box",overflow:"hidden",height:"100%",width:"100%",[modalMediaQuery.smMinOrSmallerHeight]:{overflow:"auto"}},closeButton:{position:"absolute",insetInlineEnd:theme.closeButton.layout.gapRight,top:theme.closeButton.layout.gapTop,zIndex:1,":focus":wonderBlocksStyles.focusStyles.focus[":focus-visible"]},hasFooter:{paddingBlockEnd:theme.panel.layout.gap.default}});
72
71
 
73
- class OnePaneDialog extends React__namespace.Component{renderHeader(uniqueId){const{title,breadcrumbs=undefined,subtitle=undefined,testId}=this.props;if(breadcrumbs){return jsxRuntime.jsx(ModalHeader,{title:title,breadcrumbs:breadcrumbs,titleId:uniqueId,testId:testId&&`${testId}-header`})}else if(subtitle){return jsxRuntime.jsx(ModalHeader,{title:title,subtitle:subtitle,titleId:uniqueId,testId:testId&&`${testId}-header`})}else {return jsxRuntime.jsx(ModalHeader,{title:title,titleId:uniqueId,testId:testId&&`${testId}-header`})}}render(){const{onClose,footer,content,above,below,style,closeButtonVisible,testId,titleId,role,"aria-describedby":ariaDescribedBy}=this.props;return jsxRuntime.jsx(wonderBlocksLayout.MediaLayout,{styleSheets:styleSheets,children:({styles})=>jsxRuntime.jsx(wonderBlocksCore.Id,{id:titleId,children:uniqueId=>jsxRuntime.jsx(ModalDialog,{style:[styles.dialog,style],above:above,below:below,testId:testId,"aria-labelledby":uniqueId,"aria-describedby":ariaDescribedBy,role:role,children:jsxRuntime.jsx(ModalPanel,{onClose:onClose,header:this.renderHeader(uniqueId),content:content,footer:footer,closeButtonVisible:closeButtonVisible,testId:testId})})})})}}OnePaneDialog.defaultProps={closeButtonVisible:true};const styleSheets={small:aphrodite.StyleSheet.create({dialog:{width:"100%",height:"100%",overflow:"hidden"}}),mdOrLarger:aphrodite.StyleSheet.create({dialog:{width:"93.75%",maxWidth:576,height:"81.25%",maxHeight:624}})};
72
+ const renderHeader=(props,uniqueId)=>{const{title,breadcrumbs=undefined,subtitle=undefined,testId}=props;if(breadcrumbs){return jsxRuntime.jsx(ModalHeader,{title:title,breadcrumbs:breadcrumbs,titleId:uniqueId,testId:testId&&`${testId}-header`})}else if(subtitle){return jsxRuntime.jsx(ModalHeader,{title:title,subtitle:subtitle,titleId:uniqueId,testId:testId&&`${testId}-header`})}else {return jsxRuntime.jsx(ModalHeader,{title:title,titleId:uniqueId,testId:testId&&`${testId}-header`})}};const OnePaneDialog=props=>{const{onClose,footer,content,above,below,style,closeButtonVisible=true,testId,titleId,role,"aria-describedby":ariaDescribedBy}=props;return jsxRuntime.jsx(wonderBlocksCore.Id,{id:titleId,children:uniqueId=>jsxRuntime.jsx(ModalDialog,{style:[styles$1.dialog,style],above:above,below:below,testId:testId,"aria-labelledby":uniqueId,"aria-describedby":ariaDescribedBy,role:role,children:jsxRuntime.jsx(ModalPanel,{onClose:onClose,header:renderHeader(props,uniqueId),content:content,footer:footer,closeButtonVisible:closeButtonVisible,testId:testId})})})};const styles$1=aphrodite.StyleSheet.create({dialog:{maxInlineSize:576,[modalMediaQuery.midOrSmaller]:{width:"100%",height:"100%",overflow:"hidden"},[modalMediaQuery.midOrLarger]:{width:"93.75%",height:"81.25%",maxBlockSize:624}}});
74
73
 
75
- function FlexiblePanel({closeButtonVisible=true,content,title,onClose,styles,testId}){const panelRef=React__namespace.useRef(null);const renderMainContent=React__namespace.useCallback(()=>{const contentNode=typeof content==="function"?content({title}):jsxRuntime.jsxs(jsxRuntime.Fragment,{children:[title,content]});const mainContent=ModalContent.isComponentOf(contentNode)?contentNode:jsxRuntime.jsx(ModalContent,{style:styles?.content,children:contentNode});if(!mainContent){return mainContent}return React__namespace.cloneElement(mainContent,{style:[mainContent.props.style]})},[title,content,styles?.content]);const mainContent=renderMainContent();const defaultBackgroundStyle={backgroundColor:wonderBlocksTokens.semanticColor.core.background.base.default};const combinedBackgroundStyles={...defaultBackgroundStyle,...styles?.panel};return jsxRuntime.jsxs(wonderBlocksCore.View,{style:[componentStyles$1.wrapper,combinedBackgroundStyles],testId:testId&&`${testId}-panel`,ref:panelRef,children:[closeButtonVisible&&jsxRuntime.jsx(CloseButton,{onClick:onClose,style:[componentStyles$1.closeButton,styles?.closeButton],testId:testId&&`${testId}-close`}),mainContent]})}FlexiblePanel.defaultProps={closeButtonVisible:true,scrollOverflow:true};const componentStyles$1=aphrodite.StyleSheet.create({wrapper:{flex:"1 1 auto",flexDirection:"column",boxSizing:"border-box",overflow:"hidden",height:"100%",width:"100%"},closeButton:{position:"absolute",insetInlineEnd:theme.closeButton.layout.gapRight,top:theme.closeButton.layout.gapTop,zIndex:1,":focus":wonderBlocksStyles.focusStyles.focus[":focus-visible"]}});
74
+ function FlexiblePanel({closeButtonVisible=true,content,title,onClose,styles,testId}){const panelRef=React__namespace.useRef(null);const renderMainContent=React__namespace.useCallback(()=>{const contentNode=typeof content==="function"?content({title}):jsxRuntime.jsxs(jsxRuntime.Fragment,{children:[title,content]});const mainContent=ModalContent.isComponentOf(contentNode)?contentNode:jsxRuntime.jsx(ModalContent,{style:styles?.content,children:contentNode});if(!mainContent){return mainContent}return React__namespace.cloneElement(mainContent,{style:[mainContent.props.style]})},[title,content,styles?.content]);const mainContent=renderMainContent();const defaultBackgroundStyle={backgroundColor:wonderBlocksTokens.semanticColor.core.background.base.default};const combinedBackgroundStyles={...defaultBackgroundStyle,...styles?.panel};return jsxRuntime.jsxs(wonderBlocksCore.View,{style:[componentStyles$1.wrapper,combinedBackgroundStyles],testId:testId&&`${testId}-panel`,ref:panelRef,children:[closeButtonVisible&&jsxRuntime.jsx(CloseButton,{onClick:onClose,style:[componentStyles$1.closeButton,styles?.closeButton],testId:testId&&`${testId}-close`}),mainContent]})}FlexiblePanel.defaultProps={closeButtonVisible:true};const componentStyles$1=aphrodite.StyleSheet.create({wrapper:{flex:"1 1 auto",flexDirection:"column",boxSizing:"border-box",overflow:"hidden",height:"100%",width:"100%"},closeButton:{position:"absolute",insetInlineEnd:theme.closeButton.layout.gapRight,top:theme.closeButton.layout.gapTop,zIndex:1,":focus":wonderBlocksStyles.focusStyles.focus[":focus-visible"]}});
76
75
 
77
76
  const FlexibleDialog=React__namespace.forwardRef(function FlexibleDialog(props,ref){const{onClose,title,content,styles,closeButtonVisible=true,testId,titleId,role="dialog",...accessibilityProps}=props;const uniqueId=React__namespace.useId();const headingId=titleId??uniqueId;const renderedTitle=title==null?null:typeof title==="string"?jsxRuntime.jsx(wonderBlocksTypography.Heading,{id:headingId,children:title}):React__namespace.cloneElement(title,{id:headingId,testId:"title-heading-wrapper"});return jsxRuntime.jsx(wonderBlocksCore.View,{style:[componentStyles.root,styles?.root],children:jsxRuntime.jsx(wonderBlocksCore.View,{role:role,"aria-modal":"true","aria-label":accessibilityProps["aria-label"],"aria-labelledby":headingId,"aria-describedby":accessibilityProps["aria-describedby"],ref:ref,testId:testId,style:[componentStyles.dialog,styles?.dialog],children:jsxRuntime.jsx(FlexiblePanel,{styles:{panel:styles?.panel,content:styles?.content,closeButton:styles?.closeButton},onClose:onClose,title:renderedTitle,content:content,closeButtonVisible:closeButtonVisible,testId:testId})})})});const componentStyles=aphrodite.StyleSheet.create({root:{boxShadow:theme.dialog.shadow.default,color:wonderBlocksTokens.semanticColor.core.foreground.neutral.strong,overflow:"auto",position:"relative",willChange:"transform, opacity",height:"auto",maxHeight:"100vh",maxWidth:576,width:"93.75%"}});
78
77
 
@@ -2,4 +2,9 @@
2
2
  * The attribute used to identify a modal launcher portal.
3
3
  */
4
4
  declare const ModalLauncherPortalAttributeName = "data-modal-launcher-portal";
5
- export { ModalLauncherPortalAttributeName };
5
+ declare const modalMediaQuery: {
6
+ midOrSmaller: string;
7
+ midOrLarger: string;
8
+ smMinOrSmallerHeight: string;
9
+ };
10
+ export { ModalLauncherPortalAttributeName, modalMediaQuery };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanacademy/wonder-blocks-modal",
3
- "version": "8.4.5",
3
+ "version": "8.5.0",
4
4
  "design": "v2",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -22,7 +22,7 @@
22
22
  "dependencies": {
23
23
  "@khanacademy/wonder-blocks-breadcrumbs": "3.2.4",
24
24
  "@khanacademy/wonder-blocks-core": "12.4.0",
25
- "@khanacademy/wonder-blocks-icon-button": "10.5.2",
25
+ "@khanacademy/wonder-blocks-icon-button": "10.5.3",
26
26
  "@khanacademy/wonder-blocks-layout": "3.1.37",
27
27
  "@khanacademy/wonder-blocks-styles": "0.2.32",
28
28
  "@khanacademy/wonder-blocks-timing": "7.0.2",