@khanacademy/wonder-blocks-modal 8.7.11 → 8.8.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 +13 -0
- package/dist/components/drawer-backdrop.d.ts +7 -1
- package/dist/components/drawer-launcher.d.ts +4 -2
- package/dist/es/index.js +9 -7
- package/dist/index.js +9 -7
- package/dist/util/drawer-animation.d.ts +23 -0
- package/dist/util/drawer-context.d.ts +10 -3
- package/dist/util/types.d.ts +11 -0
- package/package.json +2 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @khanacademy/wonder-blocks-modal@8.
|
|
2
|
+
> @khanacademy/wonder-blocks-modal@8.8.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,18 @@
|
|
|
1
1
|
# @khanacademy/wonder-blocks-modal
|
|
2
2
|
|
|
3
|
+
## 8.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e11b241: DrawerLauncher: update the default animation timing, easing, and slide distance, and add an `easing` prop and a `styles.backdrop` key
|
|
8
|
+
|
|
9
|
+
## 8.7.12
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- @khanacademy/wonder-blocks-icon-button@11.4.6
|
|
14
|
+
- @khanacademy/wonder-blocks-announcer@1.1.1
|
|
15
|
+
|
|
3
16
|
## 8.7.11
|
|
4
17
|
|
|
5
18
|
### Patch Changes
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
import type { StyleType } from "@khanacademy/wonder-blocks-core";
|
|
2
3
|
import type { ModalElement } from "../util/types";
|
|
3
4
|
type Props = {
|
|
4
5
|
children: ModalElement;
|
|
@@ -13,6 +14,11 @@ type Props = {
|
|
|
13
14
|
* Test ID used for e2e testing.
|
|
14
15
|
*/
|
|
15
16
|
testId?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Optional custom styles, applied last so they override the backdrop's own
|
|
19
|
+
* styles — including its opacity animation.
|
|
20
|
+
*/
|
|
21
|
+
style?: StyleType;
|
|
16
22
|
};
|
|
17
23
|
/**
|
|
18
24
|
* A private component used by DrawerLauncher. This is the fixed-position
|
|
@@ -24,5 +30,5 @@ type Props = {
|
|
|
24
30
|
* and adding an `onClose` prop that will call `onCloseModal`. If an
|
|
25
31
|
* `onClose` prop is already provided, the two are merged.
|
|
26
32
|
*/
|
|
27
|
-
declare const DrawerBackdrop: ({ children, testId, initialFocusId, onCloseModal, }: Props) => React.JSX.Element;
|
|
33
|
+
declare const DrawerBackdrop: ({ children, testId, initialFocusId, onCloseModal, style, }: Props) => React.JSX.Element;
|
|
28
34
|
export default DrawerBackdrop;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import type { StyleType } from "@khanacademy/wonder-blocks-core";
|
|
3
|
-
import type { DrawerAlignment, ModalElement } from "../util/types";
|
|
3
|
+
import type { DrawerAlignment, DrawerEasing, ModalElement } from "../util/types";
|
|
4
4
|
import { DEFAULT_DRAWER_TIMING_DURATION_MS, DEFAULT_DRAWER_ANIMATED, DEFAULT_DRAWER_BACKDROP_DISMISS_ENABLED, DEFAULT_DRAWER_IS_EXITING } from "../util/drawer-context";
|
|
5
5
|
import type { DrawerDialogStyles } from "./drawer-dialog";
|
|
6
6
|
/**
|
|
@@ -11,7 +11,7 @@ import type { DrawerDialogStyles } from "./drawer-dialog";
|
|
|
11
11
|
*/
|
|
12
12
|
export { DEFAULT_DRAWER_TIMING_DURATION_MS, DEFAULT_DRAWER_ANIMATED, DEFAULT_DRAWER_BACKDROP_DISMISS_ENABLED, DEFAULT_DRAWER_IS_EXITING, };
|
|
13
13
|
/** @deprecated Use DEFAULT_DRAWER_TIMING_DURATION_MS instead. */
|
|
14
|
-
export declare const DEFAULT_TIMING_DURATION_MS =
|
|
14
|
+
export declare const DEFAULT_TIMING_DURATION_MS = 300;
|
|
15
15
|
/**
|
|
16
16
|
* A more restrictive type for DrawerLauncher that encourages the use of DrawerDialog.
|
|
17
17
|
*/
|
|
@@ -29,8 +29,10 @@ declare const _default: {
|
|
|
29
29
|
readonly alignment: DrawerAlignment;
|
|
30
30
|
readonly styles?: {
|
|
31
31
|
container?: StyleType;
|
|
32
|
+
backdrop?: StyleType;
|
|
32
33
|
};
|
|
33
34
|
readonly timingDuration?: number;
|
|
35
|
+
readonly easing?: DrawerEasing;
|
|
34
36
|
readonly animated?: boolean;
|
|
35
37
|
readonly backdropDismissEnabled?: boolean;
|
|
36
38
|
readonly initialFocusId?: string;
|
package/dist/es/index.js
CHANGED
|
@@ -19,7 +19,7 @@ const theme$1={root:{border:{radius:border.radius.radius_040}},dialog:{layout:{p
|
|
|
19
19
|
|
|
20
20
|
var theme = mapValuesToCssVars(theme$1,"--wb-c-modal-");
|
|
21
21
|
|
|
22
|
-
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;const{ariaModalRef}=useModalAnnouncer(ref);return jsx(View,{style:componentStyles$
|
|
22
|
+
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;const{ariaModalRef}=useModalAnnouncer(ref);return jsx(View,{style:componentStyles$3.paddingLayer,"data-modal-padding-layer":true,children:jsxs(View,{style:[componentStyles$3.wrapper,style],children:[below&&jsx(View,{style:componentStyles$3.below,children:below}),jsx(View,{role:role,"aria-modal":"true","aria-label":ariaLabel,"aria-labelledby":ariaLabelledBy,"aria-describedby":ariaDescribedBy,ref:ariaModalRef,style:componentStyles$3.dialog,testId:testId,children:children}),above&&jsx(View,{style:componentStyles$3.above,children:above})]})})});const componentStyles$3=StyleSheet.create({paddingLayer:{alignItems:"center",height:"100%",justifyContent:"center",width:"100%",[modalMediaQuery.midOrSmaller]:{padding:theme.dialog.layout.padding}},wrapper:{alignItems:"stretch",borderRadius:theme.root.border.radius,boxShadow:theme.dialog.shadow.default,color:semanticColor.core.foreground.neutral.strong,flexDirection:"row",height:"100%",position:"relative",width:"100%",[modalMediaQuery.midOrSmaller]:{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",insetBlockStart:0,insetInlineStart:0,insetBlockEnd:0,insetInlineEnd:0,zIndex:1},below:{pointerEvents:"none",position:"absolute",insetBlockStart:0,insetInlineStart:0,insetBlockEnd:0,insetInlineEnd:0,zIndex:-1}});ModalDialog.displayName="ModalDialog";
|
|
23
23
|
|
|
24
24
|
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",marginBlockStart:"auto",minBlockSize: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}`}});
|
|
25
25
|
|
|
@@ -45,19 +45,21 @@ function ModalPanel({closeButtonVisible=true,scrollOverflow=true,content,footer,
|
|
|
45
45
|
|
|
46
46
|
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,width:"93.75%",height:"81.25%",maxBlockSize:624,[modalMediaQuery.midOrSmaller]:{width:"100%",height:"100%",overflow:"hidden"}}});
|
|
47
47
|
|
|
48
|
-
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,applySmallHeightStyles:false,children:contentNode});if(!mainContent){return mainContent}return React.cloneElement(mainContent,{applySmallHeightStyles:false,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$
|
|
48
|
+
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,applySmallHeightStyles:false,children:contentNode});if(!mainContent){return mainContent}return React.cloneElement(mainContent,{applySmallHeightStyles:false,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$2.wrapper,combinedBackgroundStyles],testId:testId&&`${testId}-panel`,ref:panelRef,children:[closeButtonVisible&&jsx(CloseButton,{onClick:onClose,style:[componentStyles$2.closeButton,styles?.closeButton],testId:testId&&`${testId}-close`}),mainContent]})}FlexiblePanel.defaultProps={closeButtonVisible:true};const componentStyles$2=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,insetBlockStart:theme.closeButton.layout.gapTop,zIndex:1,":focus":focusStyles.focus[":focus-visible"]}});
|
|
49
49
|
|
|
50
|
-
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{ariaModalRef}=useModalAnnouncer(ref);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:ariaModalRef,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",maxBlockSize:"100vh",maxInlineSize:576,width:"93.75%"}});
|
|
50
|
+
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{ariaModalRef}=useModalAnnouncer(ref);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$1.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:ariaModalRef,testId:testId,style:[componentStyles$1.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$1=StyleSheet.create({root:{boxShadow:theme.dialog.shadow.default,color:semanticColor.core.foreground.neutral.strong,overflow:"auto",position:"relative",willChange:"transform, opacity",height:"auto",maxBlockSize:"100vh",maxInlineSize:576,width:"93.75%"}});
|
|
51
51
|
|
|
52
|
-
const
|
|
52
|
+
const DRAWER_ENTER_DURATION_MS=300;const DRAWER_EXIT_DURATION_MS=150;const DRAWER_ENTER_EASING="cubic-bezier(0.05, 0.7, 0.1, 1)";const DRAWER_EXIT_EASING="cubic-bezier(0.3, 0, 0.8, 0.15)";const DRAWER_SLIDE_OFFSET="42%";
|
|
53
53
|
|
|
54
|
-
const
|
|
54
|
+
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};const DrawerContext=React.createContext(defaultDrawerContextValue);const useDrawerContext=()=>{return React.useContext(DrawerContext)};
|
|
55
55
|
|
|
56
|
-
const
|
|
56
|
+
const DrawerBackdrop=({children,testId,initialFocusId,onCloseModal,style})=>{const{alignment,animated,timingDuration,isExiting}=useDrawerContext();const[mousePressedOutside,setMousePressedOutside]=React.useState(false);const backdropRef=React.useRef(null);const computedTimingDuration=animated?timingDuration??DRAWER_ENTER_DURATION_MS:0;const fadeStyle=isExiting?styles.fadeOut:styles.fadeIn;const fadeDurationOverride=timingDuration!=null?{animationDuration:`${timingDuration}ms`}:undefined;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&&fadeStyle,animated&&fadeDurationOverride,style].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",insetInlineStart:0,insetBlockStart:0,width:"100%",height:"100%",display:"flex",overflow:"hidden",background:semanticColor.core.background.overlay.default},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:`${DRAWER_ENTER_DURATION_MS}ms`,animationTimingFunction:"linear",animationFillMode:"forwards"},fadeOut:{animationName:keyframes.fadeOut,animationDuration:`${DRAWER_EXIT_DURATION_MS}ms`,animationTimingFunction:"linear",animationFillMode:"forwards"}});
|
|
57
|
+
|
|
58
|
+
const defaultProps={backdropDismissEnabled:DEFAULT_DRAWER_BACKDROP_DISMISS_ENABLED};const DrawerLauncher=props=>{const{modal,backdropDismissEnabled=defaultProps.backdropDismissEnabled,initialFocusId,closedFocusId,testId,opened:controlledOpened,onClose,children,schedule,alignment,styles,animated=DEFAULT_DRAWER_ANIMATED,timingDuration,easing}=props;const easingEnter=easing?.enter;const easingExit=easing?.exit;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??DRAWER_EXIT_DURATION_MS);}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,easing:{enter:easingEnter,exit:easingExit}}),[alignment,animated,isExiting,timingDuration,easingEnter,easingExit]);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,style:styles?.backdrop,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);
|
|
57
59
|
|
|
58
60
|
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}
|
|
59
61
|
|
|
60
|
-
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
|
|
62
|
+
const DrawerDialog=React.forwardRef(function DrawerDialog(props,ref){const contextProps=useDrawerContext();const alignment=contextProps.alignment??"inlineEnd";const animated=contextProps.animated??DEFAULT_DRAWER_ANIMATED;const isExiting=contextProps.isExiting??DEFAULT_DRAWER_IS_EXITING;const timingDuration=contextProps.timingDuration;const easing=contextProps.easing;const{styles}=props;const direction=useDirectionDetection();const isRtl=direction==="rtl";const phase=isExiting?"exit":"enter";const defaultAnimationStyle=DEFAULT_ANIMATION_STYLES[alignment][isRtl?"rtl":"ltr"][phase];const phaseEasing=easing?.[phase];const animationStyle=!animated?undefined:timingDuration===undefined&&phaseEasing===undefined?defaultAnimationStyle:{...defaultAnimationStyle,...timingDuration!==undefined&&{animationDuration:`${timingDuration}ms`},...phaseEasing!==undefined&&{animationTimingFunction:phaseEasing}};return jsx(FlexibleDialog,{...props,ref:ref,styles:{root:[componentStyles.root,componentStyles[alignment],animationStyle,styles?.root].filter(Boolean),dialog:[alignment==="blockEnd"?componentStyles.dialogBlockEnd:componentStyles.dialogDefault,styles?.dialog].filter(Boolean),panel:styles?.panel,content:styles?.content,closeButton:styles?.closeButton}})});const REST_TRANSFORM="translate3d(0, 0, 0)";const getTransformValue=(isRtl,alignment,offset)=>{if(alignment==="blockEnd"){return `translate3d(0, ${offset}, 0)`}const isNegative=isRtl?alignment==="inlineEnd":alignment==="inlineStart";const signedOffset=isNegative?`calc(-1 * ${offset})`:offset;return `translate3d(${signedOffset}, 0, 0)`};const createKeyframes=(isRtl,alignment)=>({slideIn:{"0%":{transform:getTransformValue(isRtl,alignment,DRAWER_SLIDE_OFFSET),opacity:0},"100%":{transform:REST_TRANSFORM,opacity:1}},slideOut:{"0%":{transform:REST_TRANSFORM,opacity:1},"100%":{transform:getTransformValue(isRtl,alignment,DRAWER_SLIDE_OFFSET),opacity:0}}});const buildDefaultStyle=(isExiting,alignmentKeyframes)=>({animationName:isExiting?alignmentKeyframes.slideOut:alignmentKeyframes.slideIn,animationDuration:`${isExiting?DRAWER_EXIT_DURATION_MS:DRAWER_ENTER_DURATION_MS}ms`,animationTimingFunction:isExiting?DRAWER_EXIT_EASING:DRAWER_ENTER_EASING,animationFillMode:isExiting?"forwards":"backwards"});const buildDirectionStyles=alignment=>{const ltrKeyframes=createKeyframes(false,alignment);const rtlKeyframes=alignment==="blockEnd"?ltrKeyframes:createKeyframes(true,alignment);return {ltr:{enter:buildDefaultStyle(false,ltrKeyframes),exit:buildDefaultStyle(true,ltrKeyframes)},rtl:{enter:buildDefaultStyle(false,rtlKeyframes),exit:buildDefaultStyle(true,rtlKeyframes)}}};const DEFAULT_ANIMATION_STYLES={inlineStart:buildDirectionStyles("inlineStart"),inlineEnd:buildDirectionStyles("inlineEnd"),blockEnd:buildDirectionStyles("blockEnd")};const componentStyles=StyleSheet.create({root:{boxShadow:theme.dialog.shadow.default,color:semanticColor.core.foreground.neutral.strong,overflow:"auto",position:"relative",willChange:"transform, opacity",height:"100%",minBlockSize:"100vh",minInlineSize:breakpoint.width.xsMax,maxInlineSize:breakpoint.width.smMax,width:"100%",[breakpoint.mediaQuery.smOrSmaller]:{minInlineSize:"unset",maxInlineSize:"unset"}},dialogDefault:{minBlockSize:"100vh",minInlineSize:"unset"},dialogBlockEnd:{minBlockSize:"unset",minInlineSize:"unset"},inlineStart:{},inlineEnd:{},blockEnd:{height:"auto",minBlockSize:"unset",maxInlineSize:"unset",[breakpoint.mediaQuery.smOrSmaller]:{height:"auto"}}});DrawerDialog.displayName="DrawerDialog";
|
|
61
63
|
|
|
62
64
|
function maybeGetNextAncestorModalLauncherPortal(element){let candidateElement=element&&element.parentElement;while(candidateElement&&!candidateElement.hasAttribute(ModalLauncherPortalAttributeName)){candidateElement=candidateElement.parentElement;}return candidateElement}function maybeGetPortalMountedModalHostElement(element){return maybeGetNextAncestorModalLauncherPortal(element)}
|
|
63
65
|
|
package/dist/index.js
CHANGED
|
@@ -48,7 +48,7 @@ const theme$1={root:{border:{radius:wonderBlocksTokens.border.radius.radius_040}
|
|
|
48
48
|
|
|
49
49
|
var theme = wonderBlocksTokens.mapValuesToCssVars(theme$1,"--wb-c-modal-");
|
|
50
50
|
|
|
51
|
-
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;const{ariaModalRef}=useModalAnnouncer(ref);return jsxRuntime.jsx(wonderBlocksCore.View,{style:componentStyles$
|
|
51
|
+
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;const{ariaModalRef}=useModalAnnouncer(ref);return jsxRuntime.jsx(wonderBlocksCore.View,{style:componentStyles$3.paddingLayer,"data-modal-padding-layer":true,children:jsxRuntime.jsxs(wonderBlocksCore.View,{style:[componentStyles$3.wrapper,style],children:[below&&jsxRuntime.jsx(wonderBlocksCore.View,{style:componentStyles$3.below,children:below}),jsxRuntime.jsx(wonderBlocksCore.View,{role:role,"aria-modal":"true","aria-label":ariaLabel,"aria-labelledby":ariaLabelledBy,"aria-describedby":ariaDescribedBy,ref:ariaModalRef,style:componentStyles$3.dialog,testId:testId,children:children}),above&&jsxRuntime.jsx(wonderBlocksCore.View,{style:componentStyles$3.above,children:above})]})})});const componentStyles$3=aphrodite.StyleSheet.create({paddingLayer:{alignItems:"center",height:"100%",justifyContent:"center",width:"100%",[modalMediaQuery.midOrSmaller]:{padding:theme.dialog.layout.padding}},wrapper:{alignItems:"stretch",borderRadius:theme.root.border.radius,boxShadow:theme.dialog.shadow.default,color:wonderBlocksTokens.semanticColor.core.foreground.neutral.strong,flexDirection:"row",height:"100%",position:"relative",width:"100%",[modalMediaQuery.midOrSmaller]:{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",insetBlockStart:0,insetInlineStart:0,insetBlockEnd:0,insetInlineEnd:0,zIndex:1},below:{pointerEvents:"none",position:"absolute",insetBlockStart:0,insetInlineStart:0,insetBlockEnd:0,insetInlineEnd:0,zIndex:-1}});ModalDialog.displayName="ModalDialog";
|
|
52
52
|
|
|
53
53
|
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",marginBlockStart:"auto",minBlockSize: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}`}});
|
|
54
54
|
|
|
@@ -74,19 +74,21 @@ function ModalPanel({closeButtonVisible=true,scrollOverflow=true,content,footer,
|
|
|
74
74
|
|
|
75
75
|
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,width:"93.75%",height:"81.25%",maxBlockSize:624,[modalMediaQuery.midOrSmaller]:{width:"100%",height:"100%",overflow:"hidden"}}});
|
|
76
76
|
|
|
77
|
-
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,applySmallHeightStyles:false,children:contentNode});if(!mainContent){return mainContent}return React__namespace.cloneElement(mainContent,{applySmallHeightStyles:false,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$
|
|
77
|
+
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,applySmallHeightStyles:false,children:contentNode});if(!mainContent){return mainContent}return React__namespace.cloneElement(mainContent,{applySmallHeightStyles:false,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$2.wrapper,combinedBackgroundStyles],testId:testId&&`${testId}-panel`,ref:panelRef,children:[closeButtonVisible&&jsxRuntime.jsx(CloseButton,{onClick:onClose,style:[componentStyles$2.closeButton,styles?.closeButton],testId:testId&&`${testId}-close`}),mainContent]})}FlexiblePanel.defaultProps={closeButtonVisible:true};const componentStyles$2=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,insetBlockStart:theme.closeButton.layout.gapTop,zIndex:1,":focus":wonderBlocksStyles.focusStyles.focus[":focus-visible"]}});
|
|
78
78
|
|
|
79
|
-
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{ariaModalRef}=useModalAnnouncer(ref);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:ariaModalRef,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",maxBlockSize:"100vh",maxInlineSize:576,width:"93.75%"}});
|
|
79
|
+
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{ariaModalRef}=useModalAnnouncer(ref);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$1.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:ariaModalRef,testId:testId,style:[componentStyles$1.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$1=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",maxBlockSize:"100vh",maxInlineSize:576,width:"93.75%"}});
|
|
80
80
|
|
|
81
|
-
const
|
|
81
|
+
const DRAWER_ENTER_DURATION_MS=300;const DRAWER_EXIT_DURATION_MS=150;const DRAWER_ENTER_EASING="cubic-bezier(0.05, 0.7, 0.1, 1)";const DRAWER_EXIT_EASING="cubic-bezier(0.3, 0, 0.8, 0.15)";const DRAWER_SLIDE_OFFSET="42%";
|
|
82
82
|
|
|
83
|
-
const
|
|
83
|
+
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};const DrawerContext=React__namespace.createContext(defaultDrawerContextValue);const useDrawerContext=()=>{return React__namespace.useContext(DrawerContext)};
|
|
84
84
|
|
|
85
|
-
const
|
|
85
|
+
const DrawerBackdrop=({children,testId,initialFocusId,onCloseModal,style})=>{const{alignment,animated,timingDuration,isExiting}=useDrawerContext();const[mousePressedOutside,setMousePressedOutside]=React__namespace.useState(false);const backdropRef=React__namespace.useRef(null);const computedTimingDuration=animated?timingDuration??DRAWER_ENTER_DURATION_MS:0;const fadeStyle=isExiting?styles.fadeOut:styles.fadeIn;const fadeDurationOverride=timingDuration!=null?{animationDuration:`${timingDuration}ms`}:undefined;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&&fadeStyle,animated&&fadeDurationOverride,style].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",insetInlineStart:0,insetBlockStart:0,width:"100%",height:"100%",display:"flex",overflow:"hidden",background:wonderBlocksTokens.semanticColor.core.background.overlay.default},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:`${DRAWER_ENTER_DURATION_MS}ms`,animationTimingFunction:"linear",animationFillMode:"forwards"},fadeOut:{animationName:keyframes.fadeOut,animationDuration:`${DRAWER_EXIT_DURATION_MS}ms`,animationTimingFunction:"linear",animationFillMode:"forwards"}});
|
|
86
|
+
|
|
87
|
+
const defaultProps={backdropDismissEnabled:DEFAULT_DRAWER_BACKDROP_DISMISS_ENABLED};const DrawerLauncher=props=>{const{modal,backdropDismissEnabled=defaultProps.backdropDismissEnabled,initialFocusId,closedFocusId,testId,opened:controlledOpened,onClose,children,schedule,alignment,styles,animated=DEFAULT_DRAWER_ANIMATED,timingDuration,easing}=props;const easingEnter=easing?.enter;const easingExit=easing?.exit;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??DRAWER_EXIT_DURATION_MS);}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,easing:{enter:easingEnter,exit:easingExit}}),[alignment,animated,isExiting,timingDuration,easingEnter,easingExit]);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,style:styles?.backdrop,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);
|
|
86
88
|
|
|
87
89
|
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}
|
|
88
90
|
|
|
89
|
-
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
|
|
91
|
+
const DrawerDialog=React__namespace.forwardRef(function DrawerDialog(props,ref){const contextProps=useDrawerContext();const alignment=contextProps.alignment??"inlineEnd";const animated=contextProps.animated??DEFAULT_DRAWER_ANIMATED;const isExiting=contextProps.isExiting??DEFAULT_DRAWER_IS_EXITING;const timingDuration=contextProps.timingDuration;const easing=contextProps.easing;const{styles}=props;const direction=useDirectionDetection();const isRtl=direction==="rtl";const phase=isExiting?"exit":"enter";const defaultAnimationStyle=DEFAULT_ANIMATION_STYLES[alignment][isRtl?"rtl":"ltr"][phase];const phaseEasing=easing?.[phase];const animationStyle=!animated?undefined:timingDuration===undefined&&phaseEasing===undefined?defaultAnimationStyle:{...defaultAnimationStyle,...timingDuration!==undefined&&{animationDuration:`${timingDuration}ms`},...phaseEasing!==undefined&&{animationTimingFunction:phaseEasing}};return jsxRuntime.jsx(FlexibleDialog,{...props,ref:ref,styles:{root:[componentStyles.root,componentStyles[alignment],animationStyle,styles?.root].filter(Boolean),dialog:[alignment==="blockEnd"?componentStyles.dialogBlockEnd:componentStyles.dialogDefault,styles?.dialog].filter(Boolean),panel:styles?.panel,content:styles?.content,closeButton:styles?.closeButton}})});const REST_TRANSFORM="translate3d(0, 0, 0)";const getTransformValue=(isRtl,alignment,offset)=>{if(alignment==="blockEnd"){return `translate3d(0, ${offset}, 0)`}const isNegative=isRtl?alignment==="inlineEnd":alignment==="inlineStart";const signedOffset=isNegative?`calc(-1 * ${offset})`:offset;return `translate3d(${signedOffset}, 0, 0)`};const createKeyframes=(isRtl,alignment)=>({slideIn:{"0%":{transform:getTransformValue(isRtl,alignment,DRAWER_SLIDE_OFFSET),opacity:0},"100%":{transform:REST_TRANSFORM,opacity:1}},slideOut:{"0%":{transform:REST_TRANSFORM,opacity:1},"100%":{transform:getTransformValue(isRtl,alignment,DRAWER_SLIDE_OFFSET),opacity:0}}});const buildDefaultStyle=(isExiting,alignmentKeyframes)=>({animationName:isExiting?alignmentKeyframes.slideOut:alignmentKeyframes.slideIn,animationDuration:`${isExiting?DRAWER_EXIT_DURATION_MS:DRAWER_ENTER_DURATION_MS}ms`,animationTimingFunction:isExiting?DRAWER_EXIT_EASING:DRAWER_ENTER_EASING,animationFillMode:isExiting?"forwards":"backwards"});const buildDirectionStyles=alignment=>{const ltrKeyframes=createKeyframes(false,alignment);const rtlKeyframes=alignment==="blockEnd"?ltrKeyframes:createKeyframes(true,alignment);return {ltr:{enter:buildDefaultStyle(false,ltrKeyframes),exit:buildDefaultStyle(true,ltrKeyframes)},rtl:{enter:buildDefaultStyle(false,rtlKeyframes),exit:buildDefaultStyle(true,rtlKeyframes)}}};const DEFAULT_ANIMATION_STYLES={inlineStart:buildDirectionStyles("inlineStart"),inlineEnd:buildDirectionStyles("inlineEnd"),blockEnd:buildDirectionStyles("blockEnd")};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:"100%",minBlockSize:"100vh",minInlineSize:wonderBlocksTokens.breakpoint.width.xsMax,maxInlineSize:wonderBlocksTokens.breakpoint.width.smMax,width:"100%",[wonderBlocksTokens.breakpoint.mediaQuery.smOrSmaller]:{minInlineSize:"unset",maxInlineSize:"unset"}},dialogDefault:{minBlockSize:"100vh",minInlineSize:"unset"},dialogBlockEnd:{minBlockSize:"unset",minInlineSize:"unset"},inlineStart:{},inlineEnd:{},blockEnd:{height:"auto",minBlockSize:"unset",maxInlineSize:"unset",[wonderBlocksTokens.breakpoint.mediaQuery.smOrSmaller]:{height:"auto"}}});DrawerDialog.displayName="DrawerDialog";
|
|
90
92
|
|
|
91
93
|
function maybeGetNextAncestorModalLauncherPortal(element){let candidateElement=element&&element.parentElement;while(candidateElement&&!candidateElement.hasAttribute(ModalLauncherPortalAttributeName)){candidateElement=candidateElement.parentElement;}return candidateElement}function maybeGetPortalMountedModalHostElement(element){return maybeGetNextAncestorModalLauncherPortal(element)}
|
|
92
94
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Animation values for the drawer system, shared by `drawer-dialog` (the panel,
|
|
3
|
+
* which slides and fades) and `drawer-backdrop` (which fades).
|
|
4
|
+
*
|
|
5
|
+
* Enter and exit run on an asymmetric clock: the entrance is longer and eases
|
|
6
|
+
* out, the exit is quicker and eases in. Both layers use the same durations, so
|
|
7
|
+
* they finish together.
|
|
8
|
+
*/
|
|
9
|
+
/** Duration in milliseconds of the enter (slide-in) animation. */
|
|
10
|
+
export declare const DRAWER_ENTER_DURATION_MS = 300;
|
|
11
|
+
/** Duration in milliseconds of the exit (slide-out) animation. */
|
|
12
|
+
export declare const DRAWER_EXIT_DURATION_MS = 150;
|
|
13
|
+
/** Easing for the entrance: a decisive arrival that settles gently. */
|
|
14
|
+
export declare const DRAWER_ENTER_EASING = "cubic-bezier(0.05, 0.7, 0.1, 1)";
|
|
15
|
+
/** Easing for the exit: eases in so the panel accelerates away. */
|
|
16
|
+
export declare const DRAWER_EXIT_EASING = "cubic-bezier(0.3, 0, 0.8, 0.15)";
|
|
17
|
+
/**
|
|
18
|
+
* How far the panel sits from its resting position at the far end of the slide,
|
|
19
|
+
* as a percentage of its own size. A partial distance, so the movement is
|
|
20
|
+
* suggested rather than a full traverse of the panel's width; the panel's fade
|
|
21
|
+
* covers the rest.
|
|
22
|
+
*/
|
|
23
|
+
export declare const DRAWER_SLIDE_OFFSET = "42%";
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import type { DrawerAlignment } from "./types";
|
|
2
|
+
import type { DrawerAlignment, DrawerEasing } from "./types";
|
|
3
3
|
/**
|
|
4
4
|
* Centralized default values for the drawer system.
|
|
5
5
|
*
|
|
6
6
|
* These constants provide the default behavior for all drawer components
|
|
7
7
|
* and can be imported by consumers who need to reference or override defaults.
|
|
8
8
|
*/
|
|
9
|
-
/**
|
|
10
|
-
|
|
9
|
+
/**
|
|
10
|
+
* The drawer's enter duration in milliseconds. Each phase has its own duration —
|
|
11
|
+
* see `DRAWER_ENTER_DURATION_MS` and `DRAWER_EXIT_DURATION_MS` in
|
|
12
|
+
* `./drawer-animation`.
|
|
13
|
+
*/
|
|
14
|
+
export declare const DEFAULT_DRAWER_TIMING_DURATION_MS = 300;
|
|
11
15
|
/** Default setting for whether drawer animations are enabled. */
|
|
12
16
|
export declare const DEFAULT_DRAWER_ANIMATED = true;
|
|
13
17
|
/** Default setting for whether clicking the backdrop dismisses the drawer. */
|
|
@@ -18,7 +22,10 @@ export interface DrawerContextProps {
|
|
|
18
22
|
alignment?: DrawerAlignment;
|
|
19
23
|
animated?: boolean;
|
|
20
24
|
isExiting?: boolean;
|
|
25
|
+
/** Overrides the duration of both the enter and exit animations. */
|
|
21
26
|
timingDuration?: number;
|
|
27
|
+
/** Per-phase overrides for the slide easing. */
|
|
28
|
+
easing?: DrawerEasing;
|
|
22
29
|
}
|
|
23
30
|
export declare const DrawerContext: React.Context<DrawerContextProps>;
|
|
24
31
|
export declare const useDrawerContext: () => DrawerContextProps;
|
package/dist/util/types.d.ts
CHANGED
|
@@ -17,3 +17,14 @@ export type ModalElement = React.ReactElement | null;
|
|
|
17
17
|
* `blockEnd` is bottom-aligned.
|
|
18
18
|
*/
|
|
19
19
|
export type DrawerAlignment = "inlineStart" | "inlineEnd" | "blockEnd";
|
|
20
|
+
/**
|
|
21
|
+
* Easing curves for the drawer's slide animation, one per phase. Each value is
|
|
22
|
+
* any CSS `animation-timing-function`; omitting a phase leaves it on its default
|
|
23
|
+
* curve.
|
|
24
|
+
*/
|
|
25
|
+
export type DrawerEasing = {
|
|
26
|
+
/** The curve used while the drawer slides into place. */
|
|
27
|
+
enter?: string;
|
|
28
|
+
/** The curve used while the drawer slides away. */
|
|
29
|
+
exit?: string;
|
|
30
|
+
};
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "",
|
|
4
4
|
"author": "Khan Academy",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "8.
|
|
6
|
+
"version": "8.8.0",
|
|
7
7
|
"publishConfig": {
|
|
8
8
|
"access": "public"
|
|
9
9
|
},
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@khanacademy/wonder-blocks-announcer": "1.1.1",
|
|
32
32
|
"@khanacademy/wonder-blocks-breadcrumbs": "3.2.27",
|
|
33
33
|
"@khanacademy/wonder-blocks-core": "12.4.4",
|
|
34
|
-
"@khanacademy/wonder-blocks-icon-button": "11.4.
|
|
34
|
+
"@khanacademy/wonder-blocks-icon-button": "11.4.6",
|
|
35
35
|
"@khanacademy/wonder-blocks-layout": "3.1.60",
|
|
36
36
|
"@khanacademy/wonder-blocks-styles": "0.2.53",
|
|
37
37
|
"@khanacademy/wonder-blocks-timing": "7.1.0",
|