@@ -23,6 +23,7 @@ export interface InternalDialogProps {
2323 show : boolean ;
2424 onClose : ( success : boolean ) => void ;
2525 onClosed : ( ) => void ;
26+ disableLightDismiss ?: boolean ;
2627 width ?: CSS . Property . Width ;
2728}
2829
@@ -57,7 +58,7 @@ type DialogSlotComponent = React.FC<
5758 * return (
5859 * <button onClick={show}>Open</button>
5960 * <Dialog {...props}>
60- * <DialogTitle >Title</DialogTitle >
61+ * <Dialog.Title >Title</Dialog.Title >
6162 * ...
6263 * </Dialog>
6364 * );
@@ -82,6 +83,7 @@ const InnerDialog: React.FC<React.PropsWithChildren<InternalDialogProps>> = ({
8283 children,
8384 show,
8485 width,
86+ disableLightDismiss = false ,
8587 onClose,
8688 onClosed,
8789} ) => {
@@ -100,6 +102,10 @@ const InnerDialog: React.FC<React.PropsWithChildren<InternalDialogProps>> = ({
100102 React . MouseEventHandler < HTMLDialogElement >
101103 > (
102104 e => {
105+ if ( disableLightDismiss ) {
106+ return ;
107+ }
108+
103109 if ( ! isTopLevel ) {
104110 // Don't react to closing events if the dialog is not on top.
105111
@@ -113,16 +119,49 @@ const InnerDialog: React.FC<React.PropsWithChildren<InternalDialogProps>> = ({
113119 cancelDialog ( ) ;
114120 }
115121 } ,
116- [ cancelDialog , isTopLevel ] ,
122+ [ cancelDialog , isTopLevel , disableLightDismiss ] ,
117123 ) ;
118124
125+ // Prevent native dialog cancel event when disableLightDismiss is true
126+ // This must be set up before the dialog is shown
127+ // Only needed for safary right now because it doesn't support the closedby attribute.
128+ // https://caniuse.com/wf-dialog-closedby
129+ useEffect ( ( ) => {
130+ const dialog = dialogRef . current ;
131+
132+ if ( ! dialog ) {
133+ return ;
134+ }
135+
136+ const handleCancel = ( e : Event ) => {
137+ if ( disableLightDismiss ) {
138+ e . preventDefault ( ) ;
139+ e . stopPropagation ( ) ;
140+ } else if ( isTopLevel && ! hasOpenInnerPopup ) {
141+ // Only handle cancel if we're the top level dialog
142+ // The useHotkeys below will call cancelDialog
143+ }
144+ } ;
145+
146+ // Use capture phase to ensure we get the event first
147+ dialog . addEventListener ( 'cancel' , handleCancel , true ) ;
148+
149+ return ( ) => {
150+ dialog . removeEventListener ( 'cancel' , handleCancel , true ) ;
151+ } ;
152+ } , [ disableLightDismiss , isTopLevel , hasOpenInnerPopup ] ) ;
153+
119154 // Close the dialog when the escape key is pressed
120155 useHotkeys (
121156 'esc' ,
122157 ( ) => {
123- cancelDialog ( ) ;
158+ if ( ! disableLightDismiss ) {
159+ cancelDialog ( ) ;
160+ }
161+ } ,
162+ {
163+ enabled : show && ! hasOpenInnerPopup && isTopLevel ,
124164 } ,
125- { enabled : show && ! hasOpenInnerPopup && isTopLevel } ,
126165 ) ;
127166
128167 // When closing the `data-closing` attribute must be set before rendering so the animation has started when the regular useEffect is called.
@@ -158,15 +197,18 @@ const InnerDialog: React.FC<React.PropsWithChildren<InternalDialogProps>> = ({
158197 onMouseDown = { handleOutSideClick }
159198 $width = { width }
160199 data-top-level = { isTopLevel }
200+ closedby = { disableLightDismiss ? 'none' : 'closerequest' }
161201 >
162202 < StyledInnerDialog ref = { innerDialogRef } >
163203 < PopoverContainer >
164204 < DropdownContainer >
165- < CloseButtonSlot slot = 'close' >
166- < Button icon onClick = { cancelDialog } aria-label = 'close' >
167- < FaTimes />
168- </ Button >
169- </ CloseButtonSlot >
205+ { ! disableLightDismiss && (
206+ < CloseButtonSlot slot = 'close' >
207+ < Button icon onClick = { cancelDialog } aria-label = 'close' >
208+ < FaTimes />
209+ </ Button >
210+ </ CloseButtonSlot >
211+ ) }
170212 { children }
171213 </ DropdownContainer >
172214 </ PopoverContainer >
0 commit comments