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