@khanacademy/wonder-blocks-modal 8.2.3 → 8.3.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.2.3 build:css /home/runner/work/wonder-blocks/wonder-blocks/packages/wonder-blocks-modal
2
+ > @khanacademy/wonder-blocks-modal@8.3.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,17 @@
1
1
  # @khanacademy/wonder-blocks-modal
2
2
 
3
+ ## 8.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - f32064b: Add DrawerLauncher and DrawerDialog components for sliding in from the left, right, or bottom.
8
+
9
+ ### Patch Changes
10
+
11
+ - f32064b: Fix RTL positioning of close button in ModalPanel
12
+ - Updated dependencies [83620b4]
13
+ - @khanacademy/wonder-blocks-icon-button@10.4.0
14
+
3
15
  ## 8.2.3
4
16
 
5
17
  ### Patch Changes
@@ -0,0 +1,28 @@
1
+ import * as React from "react";
2
+ import type { ModalElement } from "../util/types";
3
+ type Props = {
4
+ children: ModalElement;
5
+ onCloseModal: () => unknown;
6
+ /**
7
+ * The selector for the element that will be focused when the dialog shows.
8
+ * When not set, the first tabbable element within the dialog will be used,
9
+ * which usually is the dismiss button (X).
10
+ */
11
+ initialFocusId?: string;
12
+ /**
13
+ * Test ID used for e2e testing.
14
+ */
15
+ testId?: string;
16
+ };
17
+ /**
18
+ * A private component used by DrawerLauncher. 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
+ declare const DrawerBackdrop: ({ children, testId, initialFocusId, onCloseModal, }: Props) => React.JSX.Element;
28
+ export default DrawerBackdrop;
@@ -0,0 +1,102 @@
1
+ import * as React from "react";
2
+ import type { StyleType } from "@khanacademy/wonder-blocks-core";
3
+ type AccessibleDialogProps = {
4
+ title: React.ReactElement | string;
5
+ "aria-label"?: never;
6
+ "aria-labelledby"?: string;
7
+ } | {
8
+ title?: never;
9
+ "aria-label": string;
10
+ "aria-labelledby"?: never;
11
+ } | {
12
+ title?: never;
13
+ "aria-label"?: never;
14
+ "aria-labelledby": string;
15
+ };
16
+ type Props = AccessibleDialogProps & {
17
+ /**
18
+ * An optional id parameter for the main heading. If one is not provided,
19
+ * an ID will be generated.
20
+ */
21
+ titleId?: string;
22
+ /**
23
+ * The content of the modal. Supports a render prop for placing the title in a slot.
24
+ */
25
+ content: React.ReactElement | ((slots: RenderProps) => React.ReactElement);
26
+ /**
27
+ * Called when the close button is clicked.
28
+ *
29
+ * If you're using `DrawerLauncher`, you probably shouldn't use this prop!
30
+ * Instead, to listen for when the modal closes, add an `onClose` handler
31
+ * to the `DrawerLauncher`.
32
+ */
33
+ onClose?: () => unknown;
34
+ /**
35
+ * When true, the close button is shown; otherwise, the close button is not shown.
36
+ */
37
+ closeButtonVisible?: boolean;
38
+ /**
39
+ * When set, overrides the default role value. Default role is "dialog"
40
+ * Roles other than dialog and alertdialog aren't appropriate for this
41
+ * component
42
+ */
43
+ role?: "dialog" | "alertdialog";
44
+ /**
45
+ * Optional custom styles.
46
+ */
47
+ styles?: DrawerDialogStyles;
48
+ /**
49
+ * Test ID used for e2e testing.
50
+ */
51
+ testId?: string;
52
+ /**
53
+ * The ID of the content describing this dialog, if applicable.
54
+ */
55
+ "aria-describedby"?: string;
56
+ };
57
+ export type DrawerDialogStyles = {
58
+ root?: StyleType;
59
+ dialog?: StyleType;
60
+ panel?: StyleType;
61
+ closeButton?: StyleType;
62
+ };
63
+ type RenderProps = {
64
+ title: React.ReactNode | string;
65
+ };
66
+ /**
67
+ * A dialog to be used with DrawerLauncher that builds on top of FlexibleDialog.
68
+ * It can receive a custom background (image or color), a title for the main
69
+ * heading, and that title can optionally render in the content area through
70
+ * a render prop.
71
+ *
72
+ * One of the following is required for labeling the dialog:
73
+ * - title content (React element or string)
74
+ * - aria-label (string)
75
+ * - aria-labelledby (string ID reference)
76
+ *
77
+ * ### Usage
78
+ *
79
+ * ```jsx
80
+ * import {DrawerDialog} from "@khanacademy/wonder-blocks-modal";
81
+ * import {BodyText} from "@khanacademy/wonder-blocks-typography";
82
+ *
83
+ * <DrawerDialog
84
+ * title={<Heading size="xxlarge" id="main-heading">Select mission</Heading>}
85
+ * content={
86
+ * <BodyText>
87
+ * {`Lorem ipsum dolor sit amet, consectetur adipiscing
88
+ * elit, sed do eiusmod tempor incididunt ut labore et
89
+ * dolore magna aliqua. Ut enim ad minim veniam,
90
+ * quis nostrud exercitation ullamco laboris nisi ut
91
+ * aliquip ex ea commodo consequat. Duis aute irure
92
+ * dolor in reprehenderit in voluptate velit esse
93
+ * cillum dolore eu fugiat nulla pariatur. Excepteur
94
+ * sint occaecat cupidatat non proident, sunt in culpa
95
+ * qui officia deserunt mollit anim id est.`}
96
+ * </BodyText>
97
+ * }
98
+ * />
99
+ * ```
100
+ */
101
+ declare const DrawerDialog: React.ForwardRefExoticComponent<Props & React.RefAttributes<HTMLDivElement>>;
102
+ export default DrawerDialog;
@@ -0,0 +1,47 @@
1
+ import * as React from "react";
2
+ import type { StyleType } from "@khanacademy/wonder-blocks-core";
3
+ import type { DrawerAlignment, ModalElement } from "../util/types";
4
+ import { DEFAULT_DRAWER_TIMING_DURATION_MS, DEFAULT_DRAWER_ANIMATED, DEFAULT_DRAWER_BACKDROP_DISMISS_ENABLED, DEFAULT_DRAWER_IS_EXITING } from "../util/drawer-context";
5
+ import type { DrawerDialogStyles } from "./drawer-dialog";
6
+ /**
7
+ * Re-exported centralized default values for the drawer system.
8
+ *
9
+ * These constants provide the default behavior for all drawer components
10
+ * and can be imported by consumers who need to reference or override defaults.
11
+ */
12
+ export { DEFAULT_DRAWER_TIMING_DURATION_MS, DEFAULT_DRAWER_ANIMATED, DEFAULT_DRAWER_BACKDROP_DISMISS_ENABLED, DEFAULT_DRAWER_IS_EXITING, };
13
+ /** @deprecated Use DEFAULT_DRAWER_TIMING_DURATION_MS instead. */
14
+ export declare const DEFAULT_TIMING_DURATION_MS = 400;
15
+ /**
16
+ * A more restrictive type for DrawerLauncher that encourages the use of DrawerDialog.
17
+ */
18
+ type DrawerModalElement = ModalElement;
19
+ /**
20
+ * Function type that should return a DrawerDialog for proper drawer functionality
21
+ */
22
+ type DrawerModalFunction = (props: {
23
+ closeModal: () => void;
24
+ styles?: DrawerDialogStyles;
25
+ }) => DrawerModalElement;
26
+ declare const _default: {
27
+ (props: {
28
+ children?: ((arg1: {
29
+ openModal: () => unknown;
30
+ }) => React.ReactNode) | undefined;
31
+ readonly testId?: string | undefined;
32
+ readonly initialFocusId?: string | undefined;
33
+ readonly backdropDismissEnabled?: boolean | undefined;
34
+ readonly modal: DrawerModalElement | DrawerModalFunction;
35
+ readonly closedFocusId?: string | undefined;
36
+ opened?: boolean | undefined;
37
+ onClose?: (() => unknown) | (() => unknown) | undefined;
38
+ readonly styles?: {
39
+ container?: StyleType;
40
+ } | undefined;
41
+ readonly alignment: DrawerAlignment;
42
+ readonly animated?: boolean | undefined;
43
+ readonly timingDuration?: number | undefined;
44
+ }): React.JSX.Element;
45
+ displayName: string;
46
+ };
47
+ export default _default;
@@ -44,13 +44,9 @@ type Props = AccessibleDialogProps & {
44
44
  /**
45
45
  * Optional custom styles.
46
46
  */
47
- styles?: {
48
- root?: StyleType;
49
- panel?: StyleType;
50
- closeButton?: StyleType;
51
- };
47
+ styles?: FlexibleDialogStyles;
52
48
  /**
53
- * Test ID used for e2e testing. This ID will be passed down to the Dialog.
49
+ * Test ID used for e2e testing.
54
50
  */
55
51
  testId?: string;
56
52
  /**
@@ -58,14 +54,23 @@ type Props = AccessibleDialogProps & {
58
54
  */
59
55
  "aria-describedby"?: string;
60
56
  };
57
+ export type FlexibleDialogStyles = {
58
+ root?: StyleType;
59
+ dialog?: StyleType;
60
+ panel?: StyleType;
61
+ closeButton?: StyleType;
62
+ };
61
63
  type RenderProps = {
62
64
  title: React.ReactNode | string;
63
65
  };
64
66
  /**
65
- * A more flexible modal variant with fewer layout constraints. It can receive
67
+ * A flexible modal variant with fewer layout constraints. It can receive
66
68
  * a custom background (image or color), a title for the main heading, and that
67
69
  * title can optionally render in the content area through a render prop.
68
70
  *
71
+ * It can be used directly with `ModalLauncher`. In a `DrawerLauncher`, use
72
+ * `DrawerDialog` instead, which is a wrapper around `FlexibleDialog`.
73
+ *
69
74
  * One of the following is required for labeling the dialog:
70
75
  * - title content (React element or string)
71
76
  * - aria-label (string)
@@ -95,5 +100,5 @@ type RenderProps = {
95
100
  * />
96
101
  * ```
97
102
  */
98
- declare const FlexibleDialog: ({ onClose, title, content, styles, closeButtonVisible, testId, titleId, role, ...accessibilityProps }: Props) => React.ReactElement;
103
+ declare const FlexibleDialog: React.ForwardRefExoticComponent<Props & React.RefAttributes<HTMLDivElement>>;
99
104
  export default FlexibleDialog;
@@ -23,7 +23,7 @@ type Props = {
23
23
  * Any optional styling to apply to the root (panel background) and close button.
24
24
  */
25
25
  styles?: {
26
- root?: StyleType;
26
+ panel?: StyleType;
27
27
  closeButton?: StyleType;
28
28
  };
29
29
  /**
package/dist/es/index.js CHANGED
@@ -15,11 +15,11 @@ const theme$1={root:{border:{radius:border.radius.radius_040}},dialog:{layout:{p
15
15
 
16
16
  var theme = mapValuesToCssVars(theme$1,"--wb-c-modal-");
17
17
 
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$6.wrapper,style],children:[below&&jsx(View,{style:styles$6.below,children:below}),jsx(View,{role:role,"aria-modal":"true","aria-label":ariaLabel,"aria-labelledby":ariaLabelledBy,"aria-describedby":ariaDescribedBy,ref:ref,style:styles$6.dialog,testId:testId,children:children}),above&&jsx(View,{style:styles$6.above,children:above})]})});const small$2="@media (max-width: 767px)";const styles$6=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";
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
19
 
20
- function ModalFooter({children}){return jsx(View,{style:styles$5.footer,children:children})}ModalFooter.__IS_MODAL_FOOTER__=true;ModalFooter.isComponentOf=instance=>{return instance&&instance.type&&instance.type.__IS_MODAL_FOOTER__};const styles$5=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}`}});
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}`}});
21
21
 
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$4.header],testId:testId,children:[breadcrumbs&&jsx(View,{style:styles$4.breadcrumbs,children:breadcrumbs}),jsx(Heading,{size:"large",tag:"h2",style:styles$4.title,id:titleId,testId:testId&&`${testId}-title`,children:title}),subtitle&&jsx(BodyText,{size:"small",style:styles$4.subtitle,testId:testId&&`${testId}-subtitle`,children:subtitle})]})}const small$1="@media (max-width: 767px)";const styles$4=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}});
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}});
23
23
 
24
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);};}}
25
25
 
@@ -27,26 +27,36 @@ const ModalLauncherPortalAttributeName="data-modal-launcher-portal";
27
27
 
28
28
  const FOCUSABLE_ELEMENTS="a[href], details, input, textarea, select, button";function findFocusableNodes(root){return Array.from(root.querySelectorAll(FOCUSABLE_ELEMENTS))}
29
29
 
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$3.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$3=StyleSheet.create({modalPositioner:{position:"fixed",left:0,top:0,width:"100%",height:"100%",alignItems:"center",justifyContent:"center",overflow:"auto",background:semanticColor.surface.overlay}});
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.surface.overlay}});
31
31
 
32
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;
33
33
 
34
34
  const defaultContext={closeModal:undefined};const ModalContext=React.createContext(defaultContext);ModalContext.displayName="ModalContext";
35
35
 
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$2.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$2=StyleSheet.create({container:{zIndex:1080}});var modalLauncher = withActionScheduler(ModalLauncher);
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);
37
37
 
38
- function ModalContent(props){const{scrollOverflow,style,children}=props;return jsx(View,{style:[styles$1.wrapper,scrollOverflow&&styles$1.scrollOverflow],children:jsx(View,{style:[styles$1.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$1=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};
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};
39
39
 
40
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})}})}}
41
41
 
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.hasFooter,mainContent.props.style]})},[content,footer,scrollOverflow]);const mainContent=renderMainContent();return jsxs(View,{style:[styles.wrapper,style],testId:testId&&`${testId}-panel`,children:[closeButtonVisible&&jsx(CloseButton,{onClick:onClose,style:[styles.closeButton],testId:testId&&`${testId}-close`}),header,mainContent,!footer||ModalFooter.isComponentOf(footer)?footer:jsx(ModalFooter,{children:footer})]})}ModalPanel.defaultProps={closeButtonVisible:true,scrollOverflow:true};const styles=StyleSheet.create({wrapper:{flex:"1 1 auto",flexDirection:"column",background:semanticColor.surface.primary,boxSizing:"border-box",overflow:"hidden",height:"100%",width:"100%"},closeButton:{position:"absolute",right:theme.closeButton.layout.gapRight,top:theme.closeButton.layout.gapTop,zIndex:1,":focus":focusStyles.focus[":focus-visible"]},hasFooter:{paddingBlockEnd:theme.panel.layout.gap.default}});
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.surface.primary,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}});
43
43
 
44
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}})};
45
45
 
46
- function FlexiblePanel({closeButtonVisible=true,content,title,onClose,styles,testId}){const renderMainContent=React.useCallback(()=>{const contentNode=typeof content==="function"?content({title}):jsxs(Fragment,{children:[title,content]});const mainContent=ModalContent.isComponentOf(contentNode)?contentNode:jsx(ModalContent,{children:contentNode});if(!mainContent){return mainContent}return React.cloneElement(mainContent,{style:[mainContent.props.style]})},[title,content]);const mainContent=renderMainContent();const defaultBackgroundStyle={backgroundColor:semanticColor.surface.primary};const combinedBackgroundStyles={...defaultBackgroundStyle,...styles?.root};return jsxs(View,{style:[componentStyles$1.wrapper,combinedBackgroundStyles],testId:testId&&`${testId}-panel`,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",right:theme.closeButton.layout.gapRight,top:theme.closeButton.layout.gapTop,zIndex:1,":focus":focusStyles.focus[":focus-visible"]}});
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,{children:contentNode});if(!mainContent){return mainContent}return React.cloneElement(mainContent,{style:[mainContent.props.style]})},[title,content]);const mainContent=renderMainContent();const defaultBackgroundStyle={backgroundColor:semanticColor.surface.primary};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"]}});
47
47
 
48
- const FlexibleDialog=({onClose,title,content,styles,closeButtonVisible=true,testId,titleId,role,...accessibilityProps})=>{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(ModalDialog,{style:[componentStyles.dialog,styles?.root],testId:testId,"aria-label":accessibilityProps["aria-label"],"aria-labelledby":headingId,"aria-describedby":accessibilityProps["aria-describedby"],role:role,children:jsx(FlexiblePanel,{styles:{root:styles?.panel},onClose:onClose,title:renderedTitle,content:content,closeButtonVisible:closeButtonVisible,testId:testId})})};const componentStyles=StyleSheet.create({dialog:{width:"93.75%",maxWidth:576,height:"auto",maxHeight:"100vh",position:"relative",overflow:"auto",[breakpoint.mediaQuery.sm]:{width:"100%",height:"100vh",maxHeight:"100vh"}}});
48
+ 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,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%",[breakpoint.mediaQuery.sm]:{height:"100vh",maxHeight:"100vh",width:"100%"}}});
49
+
50
+ const DEFAULT_DRAWER_TIMING_DURATION_MS=400;const DEFAULT_DRAWER_ANIMATED=true;const DEFAULT_DRAWER_BACKDROP_DISMISS_ENABLED=true;const DEFAULT_DRAWER_IS_EXITING=false;const defaultDrawerContextValue={animated:DEFAULT_DRAWER_ANIMATED,isExiting:DEFAULT_DRAWER_IS_EXITING,timingDuration:DEFAULT_DRAWER_TIMING_DURATION_MS};const DrawerContext=React.createContext(defaultDrawerContextValue);const useDrawerContext=()=>{return React.useContext(DrawerContext)};
51
+
52
+ const DrawerBackdrop=({children,testId,initialFocusId,onCloseModal})=>{const{alignment,animated,timingDuration,isExiting}=useDrawerContext();const[mousePressedOutside,setMousePressedOutside]=React.useState(false);const backdropRef=React.useRef(null);const computedTimingDuration=animated?timingDuration:0;const getInitialFocusElement=React.useCallback(container=>{if(!initialFocusId){return null}return container.querySelector(`#${initialFocusId}`)},[initialFocusId]);const getFirstFocusableElement=React.useCallback(container=>{const focusableElements=findFocusableNodes(container);if(!focusableElements){return null}return focusableElements[0]},[]);const getDialogElement=React.useCallback(container=>{const dialogElement=container.querySelector('[role="dialog"]');if(dialogElement){dialogElement.tabIndex=-1;}return dialogElement},[]);React.useEffect(()=>{const container=backdropRef.current;if(!container){return}const firstFocusableElement=getInitialFocusElement(container)||getFirstFocusableElement(container)||getDialogElement(container);if(firstFocusableElement){setTimeout(()=>{firstFocusableElement.focus();},computedTimingDuration);}},[getInitialFocusElement,getFirstFocusableElement,getDialogElement,computedTimingDuration]);const handleMouseDown=e=>{if(e.target===e.currentTarget){setMousePressedOutside(true);}};const handleMouseUp=e=>{if(e.target===e.currentTarget&&mousePressedOutside){onCloseModal();}setMousePressedOutside(false);};const backdropProps={[ModalLauncherPortalAttributeName]:true};return jsx(View,{ref:backdropRef,style:[styles.drawerPositioner,alignment&&styles[alignment],animated&&(isExiting?styles.fadeOut:styles.fadeIn)].filter(Boolean),onMouseDown:handleMouseDown,onMouseUp:handleMouseUp,testId:testId,...backdropProps,children:children})};const keyframes={fadeIn:{"0%":{opacity:0},"100%":{opacity:1}},fadeOut:{"0%":{opacity:1},"100%":{opacity:0}}};const styles=StyleSheet.create({drawerPositioner:{position:"fixed",left:0,top:0,width:"100%",height:"100%",display:"flex",overflow:"hidden",background:semanticColor.surface.overlay},inlineStart:{alignItems:"flex-start",justifyContent:"flex-start"},inlineEnd:{alignItems:"flex-end",justifyContent:"flex-start"},blockEnd:{alignItems:"center",justifyContent:"flex-end"},fadeIn:{animationName:keyframes.fadeIn,animationDuration:"400ms",animationTimingFunction:"linear",animationFillMode:"forwards"},fadeOut:{animationName:keyframes.fadeOut,animationDuration:"400ms",animationTimingFunction:"linear",animationFillMode:"forwards"}});
53
+
54
+ const defaultProps={backdropDismissEnabled:DEFAULT_DRAWER_BACKDROP_DISMISS_ENABLED,defaultTimingDuration:DEFAULT_DRAWER_TIMING_DURATION_MS};const DrawerLauncher=props=>{const{modal,backdropDismissEnabled=defaultProps.backdropDismissEnabled,initialFocusId,closedFocusId,testId,opened:controlledOpened,onClose,children,schedule,alignment,styles,animated=DEFAULT_DRAWER_ANIMATED,timingDuration=defaultProps.defaultTimingDuration}=props;const[uncontrolledOpened,setUncontrolledOpened]=React.useState(false);const[isExiting,setIsExiting]=React.useState(false);const lastElementFocusedOutsideModalRef=React.useRef(null);const opened=typeof controlledOpened==="boolean"?controlledOpened:uncontrolledOpened;const saveLastElementFocused=React.useCallback(()=>{lastElementFocusedOutsideModalRef.current=document.activeElement;},[]);React.useEffect(()=>{if(controlledOpened&&!prevControlledOpened.current){saveLastElementFocused();}prevControlledOpened.current=controlledOpened;},[controlledOpened,saveLastElementFocused]);const prevControlledOpened=React.useRef(controlledOpened);const returnFocus=React.useCallback(()=>{const focusElement=closedFocusId?document.getElementById(closedFocusId):null;schedule.animationFrame(()=>{if(focusElement){focusElement.focus();}else if(lastElementFocusedOutsideModalRef.current){lastElementFocusedOutsideModalRef.current.focus();}});},[closedFocusId,schedule]);const handleCloseModal=React.useCallback(()=>{if(animated){setIsExiting(true);setTimeout(()=>{setIsExiting(false);if(typeof controlledOpened==="boolean"){onClose?.();}else {setUncontrolledOpened(false);onClose?.();}returnFocus();},timingDuration);}else {if(typeof controlledOpened==="boolean"){onClose?.();}else {setUncontrolledOpened(false);onClose?.();}returnFocus();}},[controlledOpened,onClose,returnFocus,animated,timingDuration]);const openModal=React.useCallback(()=>{saveLastElementFocused();setUncontrolledOpened(true);},[saveLastElementFocused]);const drawerDialogProps=React.useMemo(()=>({alignment,animated,isExiting,timingDuration}),[alignment,animated,isExiting,timingDuration]);const renderModal=React.useCallback(()=>{if(typeof modal==="function"){const renderedModal=modal({closeModal:handleCloseModal});if(!renderedModal){return null}return renderedModal}if(!modal){return null}return modal},[modal,handleCloseModal]);const renderedChildren=children?children({openModal}):null;const body=document.body;if(!body){return null}return jsxs(ModalContext.Provider,{value:{closeModal:handleCloseModal},children:[renderedChildren,opened&&!isExiting||opened&&isExiting&&animated?ReactDOM.createPortal(jsx(DrawerContext.Provider,{value:drawerDialogProps,children:jsx(FocusTrap,{style:styles?.container,children:jsx(DrawerBackdrop,{initialFocusId:initialFocusId,testId:testId,onCloseModal:backdropDismissEnabled?handleCloseModal:()=>{},children:renderModal()})})}),body):null,opened&&!isExiting&&jsxs(Fragment,{children:[jsx(DrawerLauncherKeypressListener,{onClose:handleCloseModal}),jsx(ScrollDisabler,{})]})]})};function DrawerLauncherKeypressListener({onClose}){React.useEffect(()=>{const handleKeyup=e=>{if(e.key==="Escape"){e.preventDefault();e.stopPropagation();onClose();}};window.addEventListener("keyup",handleKeyup);return ()=>window.removeEventListener("keyup",handleKeyup)},[onClose]);return null}DrawerLauncher.displayName="DrawerLauncher";var drawerLauncher = withActionScheduler(DrawerLauncher);
55
+
56
+ function useDirectionDetection(elementRef,options={}){const{direction:explicitDirection,defaultDirection="ltr"}=options;if(explicitDirection){return explicitDirection}if(elementRef?.current){const elementWithDir=elementRef.current.closest("[dir]");if(elementWithDir){const dirValue=elementWithDir.getAttribute("dir");return dirValue==="rtl"||dirValue==="ltr"?dirValue:defaultDirection}}const documentDir=document.documentElement.getAttribute("dir")||document.body.getAttribute("dir");if(documentDir==="rtl"||documentDir==="ltr"){return documentDir}return defaultDirection}
57
+
58
+ const DrawerDialog=React.forwardRef(function DrawerDialog(props,ref){const contextProps=useDrawerContext();const alignment=contextProps.alignment;const animated=contextProps.animated??DEFAULT_DRAWER_ANIMATED;const isExiting=contextProps.isExiting??DEFAULT_DRAWER_IS_EXITING;const timingDuration=contextProps.timingDuration??DEFAULT_DRAWER_TIMING_DURATION_MS;const{styles}=props;const direction=useDirectionDetection();const isRtl=direction==="rtl";const componentStyles=getComponentStyles({alignment,isRtl,animated,isExiting,timingDuration});const alignmentStyles=alignment&&componentStyles[alignment]||componentStyles.inlineEnd;return jsx(FlexibleDialog,{...props,ref:ref,styles:{root:[componentStyles.root,alignmentStyles,styles?.root].filter(Boolean),dialog:[componentStyles.dialog,styles?.dialog].filter(Boolean),panel:styles?.panel,closeButton:styles?.closeButton}})});const getTransformValue=(isRtl,alignment,percentage)=>{if(alignment==="blockEnd"){return `translate3d(0, ${percentage}%, 0)`}const directionMultiplier=isRtl?alignment==="inlineEnd"?-1:1:alignment==="inlineEnd"?1:-1;return `translate3d(${directionMultiplier*percentage}%, 0, 0)`};const createKeyframes=(isRtl,alignment)=>({slideIn:{"0%":{transform:getTransformValue(isRtl,alignment,100),opacity:0},"100%":{transform:getTransformValue(isRtl,alignment,0),opacity:1}},slideOut:{"0%":{transform:getTransformValue(isRtl,alignment,0),opacity:1},"100%":{transform:getTransformValue(isRtl,alignment,100),opacity:0}}});const getComponentStyles=({alignment,isRtl,animated,isExiting,timingDuration})=>{const alignmentKeyframes=alignment?createKeyframes(isRtl,alignment):null;return StyleSheet.create({root:{boxShadow:theme.dialog.shadow.default,color:semanticColor.core.foreground.neutral.strong,overflow:"auto",position:"relative",willChange:"transform, opacity",height:"100%",minHeight:"100vh",minWidth:breakpoint.width.xsMax,maxWidth:breakpoint.width.smMax,width:"100%",[breakpoint.mediaQuery.smOrSmaller]:{minWidth:"unset",maxWidth:"unset"}},dialog:{minHeight:alignment==="blockEnd"?"unset":"100vh",minWidth:"unset"},inlineStart:{animationName:animated&&alignmentKeyframes&&(isExiting?alignmentKeyframes.slideOut:alignmentKeyframes.slideIn),animationDuration:`${timingDuration}ms`,animationTimingFunction:"linear",animationFillMode:"forwards"},inlineEnd:{animationName:animated&&alignmentKeyframes&&(isExiting?alignmentKeyframes.slideOut:alignmentKeyframes.slideIn),animationDuration:`${timingDuration}ms`,animationTimingFunction:"linear",animationFillMode:"forwards"},blockEnd:{animationName:animated&&alignmentKeyframes&&(isExiting?alignmentKeyframes.slideOut:alignmentKeyframes.slideIn),animationDuration:`${timingDuration}ms`,animationTimingFunction:"linear",animationFillMode:"forwards",height:"auto",minHeight:"unset",maxWidth:"unset",[breakpoint.mediaQuery.smOrSmaller]:{height:"auto"}}})};DrawerDialog.displayName="DrawerDialog";
49
59
 
50
60
  function maybeGetNextAncestorModalLauncherPortal(element){let candidateElement=element&&element.parentElement;while(candidateElement&&!candidateElement.hasAttribute(ModalLauncherPortalAttributeName)){candidateElement=candidateElement.parentElement;}return candidateElement}function maybeGetPortalMountedModalHostElement(element){return maybeGetNextAncestorModalLauncherPortal(element)}
51
61
 
52
- export { FlexibleDialog, ModalDialog, ModalFooter, ModalHeader, modalLauncher as ModalLauncher, ModalPanel, OnePaneDialog, maybeGetPortalMountedModalHostElement };
62
+ export { DrawerDialog, drawerLauncher as DrawerLauncher, FlexibleDialog, ModalDialog, ModalFooter, ModalHeader, modalLauncher as ModalLauncher, ModalPanel, OnePaneDialog, maybeGetPortalMountedModalHostElement };
@@ -0,0 +1,58 @@
1
+ import * as React from "react";
2
+ type Direction = "ltr" | "rtl";
3
+ interface DirectionDetectionOptions {
4
+ /**
5
+ * Explicit direction to use, bypassing DOM detection.
6
+ * Useful when direction is known from external sources like RequestInfo.
7
+ */
8
+ direction?: Direction;
9
+ /**
10
+ * The default direction to use if no dir attribute is found.
11
+ * Defaults to "ltr".
12
+ */
13
+ defaultDirection?: Direction;
14
+ }
15
+ /**
16
+ * Hook for detecting the text direction (LTR/RTL). Supports both element-specific
17
+ * and document-level direction detection.
18
+ *
19
+ * This hook performs DOM queries on each render and does not use state or
20
+ * observers, making it stable and predictable.
21
+ *
22
+ * @param elementRef - Optional ref to the element to start searching from.
23
+ * If provided, searches up the DOM tree from this element.
24
+ * If not provided, uses document-level detection.
25
+ * @param options - Configuration options for direction detection
26
+ * @returns The detected direction ("ltr" or "rtl")
27
+ *
28
+ * @example
29
+ * ```tsx
30
+ * // Element-specific detection - searches up DOM tree from the element
31
+ * const MyComponent = () => {
32
+ * const ref = React.useRef<HTMLDivElement>(null);
33
+ * const direction = useDirectionDetection(ref);
34
+ *
35
+ * return (
36
+ * <div ref={ref}>
37
+ * Direction is: {direction}
38
+ * </div>
39
+ * );
40
+ * };
41
+ *
42
+ * // Document-level detection - uses document.documentElement.dir
43
+ * const MyOtherComponent = () => {
44
+ * const direction = useDirectionDetection();
45
+ * return <div>Page direction is: {direction}</div>;
46
+ * };
47
+ *
48
+ * // With explicit direction (e.g., from RequestInfo)
49
+ * const MyRTLComponent = ({requestInfo}) => {
50
+ * const direction = useDirectionDetection(undefined, {
51
+ * direction: requestInfo.isRTL ? "rtl" : "ltr"
52
+ * });
53
+ * return <div>Explicit direction: {direction}</div>;
54
+ * };
55
+ * ```
56
+ */
57
+ export declare function useDirectionDetection(elementRef?: React.RefObject<HTMLElement | null>, options?: DirectionDetectionOptions): Direction;
58
+ export {};
package/dist/index.d.ts CHANGED
@@ -5,5 +5,9 @@ import ModalLauncher from "./components/modal-launcher";
5
5
  import ModalPanel from "./components/modal-panel";
6
6
  import OnePaneDialog from "./components/one-pane-dialog";
7
7
  import FlexibleDialog from "./components/flexible-dialog";
8
+ import DrawerLauncher from "./components/drawer-launcher";
9
+ import DrawerDialog, { type DrawerDialogStyles } from "./components/drawer-dialog";
8
10
  import maybeGetPortalMountedModalHostElement from "./util/maybe-get-portal-mounted-modal-host-element";
9
- export { ModalHeader, ModalFooter, ModalDialog, ModalPanel, ModalLauncher, OnePaneDialog, FlexibleDialog, maybeGetPortalMountedModalHostElement, };
11
+ import type { DrawerAlignment } from "./util/types";
12
+ export { ModalHeader, ModalFooter, ModalDialog, ModalPanel, ModalLauncher, OnePaneDialog, FlexibleDialog, DrawerLauncher, DrawerDialog, maybeGetPortalMountedModalHostElement, };
13
+ export type { DrawerAlignment, DrawerDialogStyles };
package/dist/index.js CHANGED
@@ -44,11 +44,11 @@ const theme$1={root:{border:{radius:wonderBlocksTokens.border.radius.radius_040}
44
44
 
45
45
  var theme = wonderBlocksTokens.mapValuesToCssVars(theme$1,"--wb-c-modal-");
46
46
 
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$6.wrapper,style],children:[below&&jsxRuntime.jsx(wonderBlocksCore.View,{style:styles$6.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$6.dialog,testId:testId,children:children}),above&&jsxRuntime.jsx(wonderBlocksCore.View,{style:styles$6.above,children:above})]})});const small$2="@media (max-width: 767px)";const styles$6=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";
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
48
 
49
- function ModalFooter({children}){return jsxRuntime.jsx(wonderBlocksCore.View,{style:styles$5.footer,children:children})}ModalFooter.__IS_MODAL_FOOTER__=true;ModalFooter.isComponentOf=instance=>{return instance&&instance.type&&instance.type.__IS_MODAL_FOOTER__};const styles$5=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}`}});
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}`}});
50
50
 
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$4.header],testId:testId,children:[breadcrumbs&&jsxRuntime.jsx(wonderBlocksCore.View,{style:styles$4.breadcrumbs,children:breadcrumbs}),jsxRuntime.jsx(wonderBlocksTypography.Heading,{size:"large",tag:"h2",style:styles$4.title,id:titleId,testId:testId&&`${testId}-title`,children:title}),subtitle&&jsxRuntime.jsx(wonderBlocksTypography.BodyText,{size:"small",style:styles$4.subtitle,testId:testId&&`${testId}-subtitle`,children:subtitle})]})}const small$1="@media (max-width: 767px)";const styles$4=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}});
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}});
52
52
 
53
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);};}}
54
54
 
@@ -56,28 +56,40 @@ const ModalLauncherPortalAttributeName="data-modal-launcher-portal";
56
56
 
57
57
  const FOCUSABLE_ELEMENTS="a[href], details, input, textarea, select, button";function findFocusableNodes(root){return Array.from(root.querySelectorAll(FOCUSABLE_ELEMENTS))}
58
58
 
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$3.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$3=aphrodite.StyleSheet.create({modalPositioner:{position:"fixed",left:0,top:0,width:"100%",height:"100%",alignItems:"center",justifyContent:"center",overflow:"auto",background:wonderBlocksTokens.semanticColor.surface.overlay}});
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.surface.overlay}});
60
60
 
61
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;
62
62
 
63
63
  const defaultContext={closeModal:undefined};const ModalContext=React__namespace.createContext(defaultContext);ModalContext.displayName="ModalContext";
64
64
 
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$2.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$2=aphrodite.StyleSheet.create({container:{zIndex:1080}});var modalLauncher = wonderBlocksTiming.withActionScheduler(ModalLauncher);
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);
66
66
 
67
- function ModalContent(props){const{scrollOverflow,style,children}=props;return jsxRuntime.jsx(wonderBlocksCore.View,{style:[styles$1.wrapper,scrollOverflow&&styles$1.scrollOverflow],children:jsxRuntime.jsx(wonderBlocksCore.View,{style:[styles$1.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$1=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};
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};
68
68
 
69
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})}})}}
70
70
 
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.hasFooter,mainContent.props.style]})},[content,footer,scrollOverflow]);const mainContent=renderMainContent();return jsxRuntime.jsxs(wonderBlocksCore.View,{style:[styles.wrapper,style],testId:testId&&`${testId}-panel`,children:[closeButtonVisible&&jsxRuntime.jsx(CloseButton,{onClick:onClose,style:[styles.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=aphrodite.StyleSheet.create({wrapper:{flex:"1 1 auto",flexDirection:"column",background:wonderBlocksTokens.semanticColor.surface.primary,boxSizing:"border-box",overflow:"hidden",height:"100%",width:"100%"},closeButton:{position:"absolute",right:theme.closeButton.layout.gapRight,top:theme.closeButton.layout.gapTop,zIndex:1,":focus":wonderBlocksStyles.focusStyles.focus[":focus-visible"]},hasFooter:{paddingBlockEnd:theme.panel.layout.gap.default}});
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.surface.primary,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}});
72
72
 
73
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}})};
74
74
 
75
- function FlexiblePanel({closeButtonVisible=true,content,title,onClose,styles,testId}){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,{children:contentNode});if(!mainContent){return mainContent}return React__namespace.cloneElement(mainContent,{style:[mainContent.props.style]})},[title,content]);const mainContent=renderMainContent();const defaultBackgroundStyle={backgroundColor:wonderBlocksTokens.semanticColor.surface.primary};const combinedBackgroundStyles={...defaultBackgroundStyle,...styles?.root};return jsxRuntime.jsxs(wonderBlocksCore.View,{style:[componentStyles$1.wrapper,combinedBackgroundStyles],testId:testId&&`${testId}-panel`,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",right:theme.closeButton.layout.gapRight,top:theme.closeButton.layout.gapTop,zIndex:1,":focus":wonderBlocksStyles.focusStyles.focus[":focus-visible"]}});
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,{children:contentNode});if(!mainContent){return mainContent}return React__namespace.cloneElement(mainContent,{style:[mainContent.props.style]})},[title,content]);const mainContent=renderMainContent();const defaultBackgroundStyle={backgroundColor:wonderBlocksTokens.semanticColor.surface.primary};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"]}});
76
76
 
77
- const FlexibleDialog=({onClose,title,content,styles,closeButtonVisible=true,testId,titleId,role,...accessibilityProps})=>{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(ModalDialog,{style:[componentStyles.dialog,styles?.root],testId:testId,"aria-label":accessibilityProps["aria-label"],"aria-labelledby":headingId,"aria-describedby":accessibilityProps["aria-describedby"],role:role,children:jsxRuntime.jsx(FlexiblePanel,{styles:{root:styles?.panel},onClose:onClose,title:renderedTitle,content:content,closeButtonVisible:closeButtonVisible,testId:testId})})};const componentStyles=aphrodite.StyleSheet.create({dialog:{width:"93.75%",maxWidth:576,height:"auto",maxHeight:"100vh",position:"relative",overflow:"auto",[wonderBlocksTokens.breakpoint.mediaQuery.sm]:{width:"100%",height:"100vh",maxHeight:"100vh"}}});
77
+ 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,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%",[wonderBlocksTokens.breakpoint.mediaQuery.sm]:{height:"100vh",maxHeight:"100vh",width:"100%"}}});
78
+
79
+ const DEFAULT_DRAWER_TIMING_DURATION_MS=400;const DEFAULT_DRAWER_ANIMATED=true;const DEFAULT_DRAWER_BACKDROP_DISMISS_ENABLED=true;const DEFAULT_DRAWER_IS_EXITING=false;const defaultDrawerContextValue={animated:DEFAULT_DRAWER_ANIMATED,isExiting:DEFAULT_DRAWER_IS_EXITING,timingDuration:DEFAULT_DRAWER_TIMING_DURATION_MS};const DrawerContext=React__namespace.createContext(defaultDrawerContextValue);const useDrawerContext=()=>{return React__namespace.useContext(DrawerContext)};
80
+
81
+ const DrawerBackdrop=({children,testId,initialFocusId,onCloseModal})=>{const{alignment,animated,timingDuration,isExiting}=useDrawerContext();const[mousePressedOutside,setMousePressedOutside]=React__namespace.useState(false);const backdropRef=React__namespace.useRef(null);const computedTimingDuration=animated?timingDuration:0;const getInitialFocusElement=React__namespace.useCallback(container=>{if(!initialFocusId){return null}return container.querySelector(`#${initialFocusId}`)},[initialFocusId]);const getFirstFocusableElement=React__namespace.useCallback(container=>{const focusableElements=findFocusableNodes(container);if(!focusableElements){return null}return focusableElements[0]},[]);const getDialogElement=React__namespace.useCallback(container=>{const dialogElement=container.querySelector('[role="dialog"]');if(dialogElement){dialogElement.tabIndex=-1;}return dialogElement},[]);React__namespace.useEffect(()=>{const container=backdropRef.current;if(!container){return}const firstFocusableElement=getInitialFocusElement(container)||getFirstFocusableElement(container)||getDialogElement(container);if(firstFocusableElement){setTimeout(()=>{firstFocusableElement.focus();},computedTimingDuration);}},[getInitialFocusElement,getFirstFocusableElement,getDialogElement,computedTimingDuration]);const handleMouseDown=e=>{if(e.target===e.currentTarget){setMousePressedOutside(true);}};const handleMouseUp=e=>{if(e.target===e.currentTarget&&mousePressedOutside){onCloseModal();}setMousePressedOutside(false);};const backdropProps={[ModalLauncherPortalAttributeName]:true};return jsxRuntime.jsx(wonderBlocksCore.View,{ref:backdropRef,style:[styles.drawerPositioner,alignment&&styles[alignment],animated&&(isExiting?styles.fadeOut:styles.fadeIn)].filter(Boolean),onMouseDown:handleMouseDown,onMouseUp:handleMouseUp,testId:testId,...backdropProps,children:children})};const keyframes={fadeIn:{"0%":{opacity:0},"100%":{opacity:1}},fadeOut:{"0%":{opacity:1},"100%":{opacity:0}}};const styles=aphrodite.StyleSheet.create({drawerPositioner:{position:"fixed",left:0,top:0,width:"100%",height:"100%",display:"flex",overflow:"hidden",background:wonderBlocksTokens.semanticColor.surface.overlay},inlineStart:{alignItems:"flex-start",justifyContent:"flex-start"},inlineEnd:{alignItems:"flex-end",justifyContent:"flex-start"},blockEnd:{alignItems:"center",justifyContent:"flex-end"},fadeIn:{animationName:keyframes.fadeIn,animationDuration:"400ms",animationTimingFunction:"linear",animationFillMode:"forwards"},fadeOut:{animationName:keyframes.fadeOut,animationDuration:"400ms",animationTimingFunction:"linear",animationFillMode:"forwards"}});
82
+
83
+ const defaultProps={backdropDismissEnabled:DEFAULT_DRAWER_BACKDROP_DISMISS_ENABLED,defaultTimingDuration:DEFAULT_DRAWER_TIMING_DURATION_MS};const DrawerLauncher=props=>{const{modal,backdropDismissEnabled=defaultProps.backdropDismissEnabled,initialFocusId,closedFocusId,testId,opened:controlledOpened,onClose,children,schedule,alignment,styles,animated=DEFAULT_DRAWER_ANIMATED,timingDuration=defaultProps.defaultTimingDuration}=props;const[uncontrolledOpened,setUncontrolledOpened]=React__namespace.useState(false);const[isExiting,setIsExiting]=React__namespace.useState(false);const lastElementFocusedOutsideModalRef=React__namespace.useRef(null);const opened=typeof controlledOpened==="boolean"?controlledOpened:uncontrolledOpened;const saveLastElementFocused=React__namespace.useCallback(()=>{lastElementFocusedOutsideModalRef.current=document.activeElement;},[]);React__namespace.useEffect(()=>{if(controlledOpened&&!prevControlledOpened.current){saveLastElementFocused();}prevControlledOpened.current=controlledOpened;},[controlledOpened,saveLastElementFocused]);const prevControlledOpened=React__namespace.useRef(controlledOpened);const returnFocus=React__namespace.useCallback(()=>{const focusElement=closedFocusId?document.getElementById(closedFocusId):null;schedule.animationFrame(()=>{if(focusElement){focusElement.focus();}else if(lastElementFocusedOutsideModalRef.current){lastElementFocusedOutsideModalRef.current.focus();}});},[closedFocusId,schedule]);const handleCloseModal=React__namespace.useCallback(()=>{if(animated){setIsExiting(true);setTimeout(()=>{setIsExiting(false);if(typeof controlledOpened==="boolean"){onClose?.();}else {setUncontrolledOpened(false);onClose?.();}returnFocus();},timingDuration);}else {if(typeof controlledOpened==="boolean"){onClose?.();}else {setUncontrolledOpened(false);onClose?.();}returnFocus();}},[controlledOpened,onClose,returnFocus,animated,timingDuration]);const openModal=React__namespace.useCallback(()=>{saveLastElementFocused();setUncontrolledOpened(true);},[saveLastElementFocused]);const drawerDialogProps=React__namespace.useMemo(()=>({alignment,animated,isExiting,timingDuration}),[alignment,animated,isExiting,timingDuration]);const renderModal=React__namespace.useCallback(()=>{if(typeof modal==="function"){const renderedModal=modal({closeModal:handleCloseModal});if(!renderedModal){return null}return renderedModal}if(!modal){return null}return modal},[modal,handleCloseModal]);const renderedChildren=children?children({openModal}):null;const body=document.body;if(!body){return null}return jsxRuntime.jsxs(ModalContext.Provider,{value:{closeModal:handleCloseModal},children:[renderedChildren,opened&&!isExiting||opened&&isExiting&&animated?ReactDOM__namespace.createPortal(jsxRuntime.jsx(DrawerContext.Provider,{value:drawerDialogProps,children:jsxRuntime.jsx(FocusTrap,{style:styles?.container,children:jsxRuntime.jsx(DrawerBackdrop,{initialFocusId:initialFocusId,testId:testId,onCloseModal:backdropDismissEnabled?handleCloseModal:()=>{},children:renderModal()})})}),body):null,opened&&!isExiting&&jsxRuntime.jsxs(jsxRuntime.Fragment,{children:[jsxRuntime.jsx(DrawerLauncherKeypressListener,{onClose:handleCloseModal}),jsxRuntime.jsx(ScrollDisabler,{})]})]})};function DrawerLauncherKeypressListener({onClose}){React__namespace.useEffect(()=>{const handleKeyup=e=>{if(e.key==="Escape"){e.preventDefault();e.stopPropagation();onClose();}};window.addEventListener("keyup",handleKeyup);return ()=>window.removeEventListener("keyup",handleKeyup)},[onClose]);return null}DrawerLauncher.displayName="DrawerLauncher";var drawerLauncher = wonderBlocksTiming.withActionScheduler(DrawerLauncher);
84
+
85
+ function useDirectionDetection(elementRef,options={}){const{direction:explicitDirection,defaultDirection="ltr"}=options;if(explicitDirection){return explicitDirection}if(elementRef?.current){const elementWithDir=elementRef.current.closest("[dir]");if(elementWithDir){const dirValue=elementWithDir.getAttribute("dir");return dirValue==="rtl"||dirValue==="ltr"?dirValue:defaultDirection}}const documentDir=document.documentElement.getAttribute("dir")||document.body.getAttribute("dir");if(documentDir==="rtl"||documentDir==="ltr"){return documentDir}return defaultDirection}
86
+
87
+ const DrawerDialog=React__namespace.forwardRef(function DrawerDialog(props,ref){const contextProps=useDrawerContext();const alignment=contextProps.alignment;const animated=contextProps.animated??DEFAULT_DRAWER_ANIMATED;const isExiting=contextProps.isExiting??DEFAULT_DRAWER_IS_EXITING;const timingDuration=contextProps.timingDuration??DEFAULT_DRAWER_TIMING_DURATION_MS;const{styles}=props;const direction=useDirectionDetection();const isRtl=direction==="rtl";const componentStyles=getComponentStyles({alignment,isRtl,animated,isExiting,timingDuration});const alignmentStyles=alignment&&componentStyles[alignment]||componentStyles.inlineEnd;return jsxRuntime.jsx(FlexibleDialog,{...props,ref:ref,styles:{root:[componentStyles.root,alignmentStyles,styles?.root].filter(Boolean),dialog:[componentStyles.dialog,styles?.dialog].filter(Boolean),panel:styles?.panel,closeButton:styles?.closeButton}})});const getTransformValue=(isRtl,alignment,percentage)=>{if(alignment==="blockEnd"){return `translate3d(0, ${percentage}%, 0)`}const directionMultiplier=isRtl?alignment==="inlineEnd"?-1:1:alignment==="inlineEnd"?1:-1;return `translate3d(${directionMultiplier*percentage}%, 0, 0)`};const createKeyframes=(isRtl,alignment)=>({slideIn:{"0%":{transform:getTransformValue(isRtl,alignment,100),opacity:0},"100%":{transform:getTransformValue(isRtl,alignment,0),opacity:1}},slideOut:{"0%":{transform:getTransformValue(isRtl,alignment,0),opacity:1},"100%":{transform:getTransformValue(isRtl,alignment,100),opacity:0}}});const getComponentStyles=({alignment,isRtl,animated,isExiting,timingDuration})=>{const alignmentKeyframes=alignment?createKeyframes(isRtl,alignment):null;return aphrodite.StyleSheet.create({root:{boxShadow:theme.dialog.shadow.default,color:wonderBlocksTokens.semanticColor.core.foreground.neutral.strong,overflow:"auto",position:"relative",willChange:"transform, opacity",height:"100%",minHeight:"100vh",minWidth:wonderBlocksTokens.breakpoint.width.xsMax,maxWidth:wonderBlocksTokens.breakpoint.width.smMax,width:"100%",[wonderBlocksTokens.breakpoint.mediaQuery.smOrSmaller]:{minWidth:"unset",maxWidth:"unset"}},dialog:{minHeight:alignment==="blockEnd"?"unset":"100vh",minWidth:"unset"},inlineStart:{animationName:animated&&alignmentKeyframes&&(isExiting?alignmentKeyframes.slideOut:alignmentKeyframes.slideIn),animationDuration:`${timingDuration}ms`,animationTimingFunction:"linear",animationFillMode:"forwards"},inlineEnd:{animationName:animated&&alignmentKeyframes&&(isExiting?alignmentKeyframes.slideOut:alignmentKeyframes.slideIn),animationDuration:`${timingDuration}ms`,animationTimingFunction:"linear",animationFillMode:"forwards"},blockEnd:{animationName:animated&&alignmentKeyframes&&(isExiting?alignmentKeyframes.slideOut:alignmentKeyframes.slideIn),animationDuration:`${timingDuration}ms`,animationTimingFunction:"linear",animationFillMode:"forwards",height:"auto",minHeight:"unset",maxWidth:"unset",[wonderBlocksTokens.breakpoint.mediaQuery.smOrSmaller]:{height:"auto"}}})};DrawerDialog.displayName="DrawerDialog";
78
88
 
79
89
  function maybeGetNextAncestorModalLauncherPortal(element){let candidateElement=element&&element.parentElement;while(candidateElement&&!candidateElement.hasAttribute(ModalLauncherPortalAttributeName)){candidateElement=candidateElement.parentElement;}return candidateElement}function maybeGetPortalMountedModalHostElement(element){return maybeGetNextAncestorModalLauncherPortal(element)}
80
90
 
91
+ exports.DrawerDialog = DrawerDialog;
92
+ exports.DrawerLauncher = drawerLauncher;
81
93
  exports.FlexibleDialog = FlexibleDialog;
82
94
  exports.ModalDialog = ModalDialog;
83
95
  exports.ModalFooter = ModalFooter;
@@ -0,0 +1,24 @@
1
+ import * as React from "react";
2
+ import type { DrawerAlignment } from "./types";
3
+ /**
4
+ * Centralized default values for the drawer system.
5
+ *
6
+ * These constants provide the default behavior for all drawer components
7
+ * and can be imported by consumers who need to reference or override defaults.
8
+ */
9
+ /** Default duration in milliseconds for drawer slide animations and focus timing. */
10
+ export declare const DEFAULT_DRAWER_TIMING_DURATION_MS = 400;
11
+ /** Default setting for whether drawer animations are enabled. */
12
+ export declare const DEFAULT_DRAWER_ANIMATED = true;
13
+ /** Default setting for whether clicking the backdrop dismisses the drawer. */
14
+ export declare const DEFAULT_DRAWER_BACKDROP_DISMISS_ENABLED = true;
15
+ /** Default value for internal exit animation state. */
16
+ export declare const DEFAULT_DRAWER_IS_EXITING = false;
17
+ export interface DrawerContextProps {
18
+ alignment?: DrawerAlignment;
19
+ animated?: boolean;
20
+ isExiting?: boolean;
21
+ timingDuration?: number;
22
+ }
23
+ export declare const DrawerContext: React.Context<DrawerContextProps>;
24
+ export declare const useDrawerContext: () => DrawerContextProps;
@@ -10,3 +10,10 @@ import * as React from "react";
10
10
  * NOTE(kevinb): we include `| null` here because that's what React.FC<> returns.
11
11
  */
12
12
  export type ModalElement = React.ReactElement | null;
13
+ /**
14
+ * The position of the modal, with logical support for RTL (Right-to-Left).
15
+ * `inlineStart` is left-aligned in LTR,
16
+ * `inlineEnd` is right-aligned in LTR,
17
+ * `blockEnd` is bottom-aligned.
18
+ */
19
+ export type DrawerAlignment = "inlineStart" | "inlineEnd" | "blockEnd";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanacademy/wonder-blocks-modal",
3
- "version": "8.2.3",
3
+ "version": "8.3.0",
4
4
  "design": "v2",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -20,14 +20,14 @@
20
20
  "author": "",
21
21
  "license": "MIT",
22
22
  "dependencies": {
23
+ "@khanacademy/wonder-blocks-breadcrumbs": "3.2.1",
23
24
  "@khanacademy/wonder-blocks-core": "12.4.0",
24
- "@khanacademy/wonder-blocks-icon-button": "10.3.18",
25
+ "@khanacademy/wonder-blocks-icon-button": "10.4.0",
25
26
  "@khanacademy/wonder-blocks-layout": "3.1.34",
26
27
  "@khanacademy/wonder-blocks-styles": "0.2.29",
27
28
  "@khanacademy/wonder-blocks-timing": "7.0.2",
28
29
  "@khanacademy/wonder-blocks-tokens": "12.2.0",
29
- "@khanacademy/wonder-blocks-typography": "4.2.19",
30
- "@khanacademy/wonder-blocks-breadcrumbs": "3.2.1"
30
+ "@khanacademy/wonder-blocks-typography": "4.2.19"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "@phosphor-icons/core": "^2.0.2",