Closed paulwongx closed 1 year ago
You can make it static
and decide when to show the component yourself by using the close method
a react example could be something like this
const foo = () => {
const [isOpen, setIsOpen] = useState(false);
return (
<>
{!isOpen && (
<button onClick={() => setIsOpen(true)}>Open dialog</button>
)}
{isOpen && (
<Dialog static open={isOpen} onClose={() => setIsOpen(false)}>
// ...
</Dialog>
)}
</>
);
}
The
open
prop is still used for manage scroll locking and focus trapping, but as long asstatic
is present, the actual element will always be rendered regardless of theopen
value, which allows you to control it yourself externally.
You can read more about it under Transitions (See the last code example, right before Accessibility notes)
Hi there, I forgot to post my solution for this problem. The trick is not the static
property. Even you provide the static
property, the onClose
is still triggered when user clicks outside. (The ideal solution will be a property to disable the listener for clicking outside)
To solve the problem, we can provide an empty function as the onClose
function and use static
property with custom function to handle open and close for the modal.
<Dialog
// ...
static
onClose={() => null}>
</Dialog>
I only needed to set the onClose to null using the solution from @wengtytt for this to work. Adding the static property kept the custom close events from working.
<Dialog
// ...
onClose={() => null}>
</Dialog>
Hi there, I forgot to post my solution for this problem. The trick is not the
static
property. Even you provide thestatic
property, theonClose
is still triggered when user clicks outside. (The ideal solution will be a property to disable the listener for clicking outside)To solve the problem, we can provide an empty function as the
onClose
function and usestatic
property with custom function to handle open and close for the modal.<Dialog // ... static onClose={() => null}> </Dialog>
Amazing. This works. Thank you! Edit: @iambryanhaney's answer is even better as it allows esc key to work too.
Hi there, I forgot to post my solution for this problem. The trick is not the
static
property. Even you provide thestatic
property, theonClose
is still triggered when user clicks outside. (The ideal solution will be a property to disable the listener for clicking outside)To solve the problem, we can provide an empty function as the
onClose
function and usestatic
property with custom function to handle open and close for the modal.<Dialog // ... static onClose={() => null}> </Dialog>
Oh 🤦♂️ I might've forgotten what i did to solve this.
Maybe i solved this myself by exchanging the overlay component with my own div, setting aria-hidden="true" and using the same classes.
That way you don't loose out on the close on esc etc. However, not sure if this has other drawbacks (by not using the provided overlay component)
Bypassing onClose
will require you to set up your own keyboard listener to capture the esc
key.
If we look in dialog.tsx
, we can see that there is a mouseDown event listener on the window itself, which closes the Dialog if the event emitter is not a descendant of it.
useWindowEvent('mousedown', event => {
let target = event.target as HTMLElement
if (dialogState !== DialogStates.Open) return
if (hasNestedDialogs) return
if (internalDialogRef.current?.contains(target)) return
close()
})
Simple solution: add pointer-events: none
to the Dialog.Overlay
...
Inline: style={{ pointerEvents: 'none' }}
Tailwind: className="pointer-events-none"
...which will effectively disable click events outside of the Dialog's body.
If you have other interactive elements higher in the z-index, such as a toast notification, you can capture the mouseDown
event in that element (while still listening for onClick) and prevent it from bubbling to the Dialog's window listener:
// In your toast notification component...
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
<div onMouseDown={(e) => e.stopPropagation()}>
Notes on the eslint disable: https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-static-element-interactions.md#case-the-event-handler-is-only-being-used-to-capture-bubbled-events
Bypassing
onClose
will require you to set up your own keyboard listener to capture theesc
key.If we look in
dialog.tsx
, we can see that there is a mouseDown event listener on the window itself, which closes the Dialog if the event emitter is not a descendant of it.useWindowEvent('mousedown', event => { let target = event.target as HTMLElement if (dialogState !== DialogStates.Open) return if (hasNestedDialogs) return if (internalDialogRef.current?.contains(target)) return close() })
Simple solution: add
pointer-events: none
to theDialog.Overlay
...Inline:
style={{ pointerEvents: 'none' }}
Tailwind:className="pointer-events-none"
...which will effectively disable click events outside of the Dialog's body.
If you have other interactive elements higher in the z-index, such as a toast notification, you can capture the
mouseDown
event in that element (while still listening for onClick) and prevent it from bubbling to the Dialog's window listener:// In your toast notification component... {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */} <div onMouseDown={(e) => e.stopPropagation()}>
Notes on the eslint disable: https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-static-element-interactions.md#case-the-event-handler-is-only-being-used-to-capture-bubbled-events
Wow you're absolutely right - esc key not working is an issue with the onclick={()=>null} solution. This should be the accepted answer! Many thanks!
static onClose={() => null}>
this solution doesn't work for me , my problem is if i click outside the dialog it never goes away but i can click the outside buttons and it is working when the modal is not even not closed.
anyone help?
you can also return the empty obj, it prevents the outside click ! onClose={() => {} }>
Bypassing
onClose
will require you to set up your own keyboard listener to capture theesc
key.If we look in
dialog.tsx
, we can see that there is a mouseDown event listener on the window itself, which closes the Dialog if the event emitter is not a descendant of it.useWindowEvent('mousedown', event => { let target = event.target as HTMLElement if (dialogState !== DialogStates.Open) return if (hasNestedDialogs) return if (internalDialogRef.current?.contains(target)) return close() })
Simple solution: add
pointer-events: none
to theDialog.Overlay
...Inline:
style={{ pointerEvents: 'none' }}
Tailwind:className="pointer-events-none"
...which will effectively disable click events outside of the Dialog's body.
If you have other interactive elements higher in the z-index, such as a toast notification, you can capture the
mouseDown
event in that element (while still listening for onClick) and prevent it from bubbling to the Dialog's window listener:// In your toast notification component... {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */} <div onMouseDown={(e) => e.stopPropagation()}>
Notes on the eslint disable: https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-static-element-interactions.md#case-the-event-handler-is-only-being-used-to-capture-bubbled-events
This is exactly what i was looking for, many thanks !!!
I tried to set static and create a dummy @close function but its not working i was still able to close the dialog as i click outside
<Dialog
ref={containerRef}
as='div'
className='relative z-10'
onClose={() => {}}
onClick={(event) => {
/**
* disable Close modal when click outside of modal
*/
if (
isElementChild(
containerRef.current!,
event.target as HTMLElement,
)
) {
closeModal()
}
}}
>
I want to close the modal on click outside, but I use this method to solve the nested modal situation
<Dialog ref={containerRef} as='div' className='relative z-10' onClose={() => {}} onClick={(event) => { /** * disable Close modal when click outside of modal */ if ( isElementChild( containerRef.current!, event.target as HTMLElement, ) ) { closeModal() } }} >
I want to close the modal on click outside, but I use this method to solve the nested modal situation
That's the solution that worked for me i mentioned above
i had same error so i tried to solve my way and this actually work i did this by removing the style of pointer events like so:
` onClose: () => { set({ type: null, isOpen: false });
// Delay restoring pointer events for 2 seconds
setTimeout(() => {
document.body.style.pointerEvents = "auto";
}, 1000);
}, }));`
Note, i did it with small timeout to make sure that first all modal scripts are completed and then im manually removing that. Hope it helps!
Bypassing
onClose
will require you to set up your own keyboard listener to capture theesc
key.If we look in
dialog.tsx
, we can see that there is a mouseDown event listener on the window itself, which closes the Dialog if the event emitter is not a descendant of it.useWindowEvent('mousedown', event => { let target = event.target as HTMLElement if (dialogState !== DialogStates.Open) return if (hasNestedDialogs) return if (internalDialogRef.current?.contains(target)) return close() })
Simple solution: add
pointer-events: none
to theDialog.Overlay
...Inline:
style={{ pointerEvents: 'none' }}
Tailwind:className="pointer-events-none"
...which will effectively disable click events outside of the Dialog's body.
If you have other interactive elements higher in the z-index, such as a toast notification, you can capture the
mouseDown
event in that element (while still listening for onClick) and prevent it from bubbling to the Dialog's window listener:// In your toast notification component... {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */} <div onMouseDown={(e) => e.stopPropagation()}>
Notes on the eslint disable: https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-static-element-interactions.md#case-the-event-handler-is-only-being-used-to-capture-bubbled-events
The version UI 1.6 have been depreacted the Dialog.Overlay. https://headlessui.com/react/dialog#dialog-overlay
At least with version 1.7.17 the issue it not happen.
The pointer-events-none
did not work for me on 1.7, so I'm just moving the backdrop to be part of the DialogPanel
. This way, there are no clicks outside :).
<template>
<TransitionRoot as="template" :show="open">
<Dialog class="relative z-10" @close="onClose">
<DialogPanel>
<TransitionChild as="template" enter="ease-out duration-300" enter-from="opacity-0" enter-to="opacity-100" leave="ease-in duration-200" leave-from="opacity-100" leave-to="opacity-0">
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</TransitionChild>
<-- Fixed backdrop here -->
<div id="backdrop" class="fixed inset-0 z-10 w-screen overflow-y-auto">
...rest of your dialog content
</div>
</DialogPanel>
</Dialog>
</TransitionRoot>
</template>
If you want to also prevent the ESC key handler, you can add a key listener with capture mode:
With Vue, it'd be something like this.
function keyPress(e) {
if (e.key === "Escape") {
console.log("Escape key pressed");
e.stopImmediatePropagation();
}
}
onMounted(() => {
console.log("mounted");
window.addEventListener('keydown', keyPress, true); // 'true' enables capture mode
});
onUnmounted(() => {
console.log("unmounted");
window.removeEventListener('keydown', keyPress, true);
});
Just make DialogPanel fullscreen, then add dialog content div as a direct child of DialogPanel and style that as though it is DialogPanel. This way we don't lose built in functionality such as close on ESC key.
Is there an option to disable "outside click" behaviour? For example, I'd like to keep the dialog opened when click outside. It would be great if we can have the condition inside the useWindowEvent function.
Originally posted by @wengtytt in https://github.com/tailwindlabs/headlessui/pull/212#issuecomment-841353081
There also seems to be multiple feature requests regarding this. Is there a way to override this default behavior currently?