tailwindlabs / headlessui

Completely unstyled, fully accessible UI components, designed to integrate beautifully with Tailwind CSS.
https://headlessui.com
MIT License
25.93k stars 1.07k forks source link

[Dialog] Leave transition not working when using `:unmount="false"` (vue) #2987

Open benjamincanac opened 8 months ago

benjamincanac commented 8 months ago

What package within Headless UI are you using?

@headlessui/vue

What version of that package are you using?

v1.7.19

What browser are you using?

Chrome

Reproduction URL

https://stackblitz.com/edit/vitejs-vite-jh8put?file=src/components/Dialog.vue,src/App.vue&terminal=dev

Describe your issue

I've made a minimal reproduction following the example on https://headlessui.com/vue/dialog with :unmount="false" on the Dialog, TransitionRoot and TransitionChild components.

The leave transition is not working unless the ref is true when first loading the page. You can see on reload it will work, but it won't when closing it the second time.

joshdavies14 commented 1 month ago

Are there any updates on this being fixed?

We have multiple cases where we use Dialog components either with forms inside them or as part of a larger form and need to run validation on the form contents. unmount is required to be false here to ensure that all fields of the form are correctly checked before submitting in the parent component, but applying that fix means we now regress on the leave transitions due to this bug. Essentially we now have to choose between working validation and JS-side manipulation of fields (as refs are null if they aren't mounted), or regressing in transitions.

joshdavies14 commented 1 month ago

I ended up deciding to take a quick look at this...

https://github.com/tailwindlabs/headlessui/blob/49c081d199a315d924b31c2ecec49196c10f7206/packages/%40headlessui-vue/src/components/transitions/transition.ts#L446-L450

In the linked code above, we see an if statement run on effect on the TransitionRoot component, that changes the visible/hidden state of the tree depending on the show value passed to the component.

From adding some console log statements into the playground locally, I can see that upon setting the open value to false (e.g. after closing a Dialog component), we see that the show value is correctly set to false, but the value of hasChildren(nestingBag) is originally false. We also see that the transitions on the child components don't activate instantly. Because of this, the state of the TransitionRoot is set to hidden before the leave transitions even become visible.

image

I think we don't see this behaviour when not setting unmount to false because, while the same code in TransitionRoot executes fine, the watchEffect in TransitionChild is bypassed with unmount = true, and so the delayed code isn't required to be triggered at all.

https://github.com/tailwindlabs/headlessui/blob/49c081d199a315d924b31c2ecec49196c10f7206/packages/%40headlessui-vue/src/components/transitions/transition.ts#L198

https://github.com/tailwindlabs/headlessui/blob/49c081d199a315d924b31c2ecec49196c10f7206/packages/%40headlessui-vue/src/components/transitions/transition.ts#L229

I think what is happening is the watchEffect in the TransitionChild is running at the same time as the watchEffect in the TransitionRoot, and the check for children with active transitions in Root completes before the child has time to activate it's transition, so its getting caught out and everything is being set to hidden. I could very well be wrong here so please don't take this as gospel, but it might help with some investigation.

Archetipo95 commented 1 day ago

Any update on this?

Archetipo95 commented 19 hours ago

I think that my issue https://github.com/tailwindlabs/headlessui/issues/3535 is linked to this one