vue.jsvuejs3nuxt.jsheadless-ui

Opening Healdess UI Dialog (Modal) via another Dialog (Modal) closes the previous Dialog (Modal) automatically? (Added CodeSandBox reproduction link)


I am using the HeadlessUI/Vue component in my Nuxt 3 application to build the Dialog/Modals. I am using the latest version "@headlessui/vue": "^1.7.22" and developing in Chrome latest browser.

I am creating a DialogPanel1.vue using the headlessui/vue. I have a button in DialogPanel1.vue on clicking it I am opening the DialogPanel2.vue but within DialogPanel2.vue when I click anywhere then it automatically closes the DialogPanel1.vue which is behind the DialogPanel2.vue.

Minimal reproduction repo in CodeSandBox

Why is it closing DialogPanel1 automatically? I want to ensure that DialogPanel1 is closed only when I click the close button in DialogPanel1.vue not for any click on DialogPanel2. I tried adding the @click.stop or @mousedown.stop to prevent the propagation of the event but it's still not working. Can anyone please let me know the issue?

Following is my complete code /pages/index.vue:

<template>
  <DialogPanel1 />
</template>
  
<script setup>
</script>

Following are my components /component/DialogPanel1.vue:

<template>
  <div class="mb-2">
    <button
      type="button"
      @click="openModal1"
      class="flex secondary-button text-secondary dark:bg-transparent dark:hover:text-secondary dark:hover:bg-slate-700 mx-auto justify-center items-center rounded border focus:ring-0 focus:outline-none font-medium px-5 py-2.5 text-center min-w-[8rem] sm:min-w-[10rem] lg:min-w-[12rem]"
    >
      <span class="pr-1"> Open Modal-1 </span>
    </button>
  </div>

  <TransitionRoot appear :show="modal1" as="template">
    <Dialog as="div" @close="closeModal" class="relative z-30">
      <div class="fixed inset-0 overflow-y-auto">
        <div
          class="flex min-h-full items-center justify-center p-4 text-center"
        >
          <DialogPanel
            class="dark:bg-slate-800 w-full transform overflow-auto rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all z-20"
          >
            THIS IS MODAL-1
            <button
              class="flex secondary-button text-secondary dark:bg-transparent dark:hover:text-secondary dark:hover:bg-slate-700 mx-auto justify-center items-center rounded border focus:ring-0 focus:outline-none font-medium px-5 py-2.5 text-center min-w-[8rem] sm:min-w-[10rem] lg:min-w-[12rem]"
              @click="openModal2"
            >
              MODAL-2
            </button>
          </DialogPanel>
        </div>
      </div>
    </Dialog>
  </TransitionRoot>

  <DialogPanel2 :modal2="modal2" @closeModal="hideModal2" />
</template>
    
<script setup>
import { TransitionRoot, Dialog, DialogPanel } from "@headlessui/vue";

const modal1 = ref(false);
const modal2 = ref(false);

//Function to open Modal-1
const openModal1 = () => {
  console.log("Opening Modal-1");
  modal1.value = true;
};

//Close the modal on click of the button
const closeModal = () => {
  console.log("CLOSE MODAL-1");
  modal1.value = false;
};

//Function to open Modal-2
const openModal2 = () => {
  console.log("Open MODAL-2");
  modal2.value = true;
};

const hideModal2 = () => {
  console.log("Closing MODAL-2");
  modal2.value = false;
};
</script>

Following is my /components/DialogPanel2.vue:

<template>
  <TransitionRoot
    appear
    :show="modal2"
    as="template"
    @close="closeModal"
    @click.native.stop.prevent
  >
    <Dialog
      :initialFocus="focusRef"
      as="div"
      @click.native.stop.prevent
      class="relative z-50"
    >
      <div class="fixed inset-0 overflow-y-auto">
        <div
          class="flex min-h-full items-center justify-center p-4 text-center"
        >
          <DialogPanel
            @click.native.stop.prevent
            class="pt-5 w-full max-w-xl overflow-y-auto transform overflow-visible rounded-2xl bg-white dark:bg-slate-800 p-6 align-middle shadow-xl transition-all"
          >
            THIS IS MODAL-2

            <span class="text-black dark:text-white">Field-1</span>
            <input
              ref="focusRef"
              v-model="test.one"
              type="text"
              class="relative w-full h-full bg-gray-50 dark:text-black dark:bg-gray-700 dark:border-gray-600 border-gray-300 border rounded text-center block overflow-hidden"
            />

            <span class="text-black dark:text-white">Field-2</span>
            <input
              v-model="test.two"
              type="text"
              class="relative w-full h-full bg-gray-50 dark:text-white dark:bg-gray-700 dark:border-gray-600 border-gray-300 border rounded text-center block overflow-hidden"
            />

            <button class="mt-4" @click="closeModal">Close</button>
          </DialogPanel>
        </div>
      </div>
    </Dialog>
  </TransitionRoot>
</template>
  
  <script setup>
import { TransitionRoot, Dialog, DialogPanel } from "@headlessui/vue";

const props = defineProps({
  modal2: {
    type: Boolean, //Show/hide the modal based on the flag
    required: false,
  },
});

const emits = defineEmits(["closeModal"]);
const focusRef = ref(null);

//Reference variables
const modal2 = ref(props.modal2);
const test = ref({});

watch(
  () => props.modal2,
  async (newValue) => {
    modal2.value = newValue;
  }
);

const closeModal = () => {
  console.log("CLOSE MODAL-2");
  modal2.value = false;
  emits("closeModal");
};
</script>

Why clicking anywhere on DialogPanel2.vue automatically closes the previous Dialog DialogPanel1.vue? I tried removing the @close="closeModal" from the DialogPanel1.vue that would prevent the closing of the DialogPanel1.vue but Its also making that I am unable to edit any of the fields in DialogPanel2.vue meaning the focus kind of still remains on the DialogPanel1.vue due to which I am unable to get the access or focus in DialogPanel2.vue. Following changes to DialogPanel1.vue: <TransitionRoot appear :show="modal1" as="template"> but after this when I am unable to access the Input fields in DialogPanel2.vue.

I am still unable to identify the root cause and fix it, any suggestions would be really helpful.

Issue: Codesandbox

Following issue my issue GIF: ModalCosure


Solution

  • I can't read the full code, but it seems like the dialog uses something like "outside click" which will consider any click that is not inside the element or its child as "outside click" and it will trigger the close function. In your case, your <DialogPanel2> is placed outside of <DialogPanel1>, so when the <DialogPanel1> is opened and the <DialogPanel2> is also opened, anything that is not children of <DialogPanel1> will be considered as outside of it (in this case, <DialogPanel2> is considered as outside of <DialogPanel1>), thus the click will trigger the close of <DialogPanel1>.

    The easiest fix (depending on your use case) is to move the <DialogPanel2> to the inside of <DialogPanel1> so that it's not considered as outside click, like this:

      <TransitionRoot appear :show="modal1" as="template" @close="closeModal">
        <Dialog as="div" class="relative z-30">
          <div class="fixed inset-0 overflow-y-auto">
            <div
              class="flex min-h-full items-center justify-center p-4 text-center"
            >
              <DialogPanel
                class="dark:bg-slate-800 w-full transform overflow-auto rounded-2xl bg-gray-200 p-6 text-left align-middle shadow-xl transition-all z-20"
              >
                THIS IS MODAL-1
                <button
                  class="flex secondary-button text-secondary dark:bg-transparent dark:hover:text-secondary dark:hover:bg-slate-700 mx-auto justify-center items-center rounded border focus:ring-0 focus:outline-none font-medium px-5 py-2.5 text-center min-w-[8rem] sm:min-w-[10rem] lg:min-w-[12rem]"
                  @click="openModal2"
                >
                  MODAL-2
                </button>
                <DialogPanel2 :modal2="modal2" @closeModal="hideModal2" /><!-- move it here-->
              </DialogPanel>
            </div>
          </div>
        </Dialog>
      </TransitionRoot>