BATMAN_2008
BATMAN_2008

Reputation: 3530

Using the multiple Dialog in same page throws error: There are no focusable elements inside the <FocusTrap />

I am creating a reusable Dialog modal in my application and it works fine when I use it once in my page but when I use more than once then I get the error: There are no focusable elements inside the <FocusTrap />.

Following is my reusable Dialog component from HeadlessUI in my Vue3/Nuxt 3 application: ExtensionModal.vue:

<template>
  <div class="flex-col items-center">
    <div class="mb-2">
      <button
        type="button"
        @click="openModal"
        class="block rounded-full text-blue-700 hover:text-white border border-blue-700 hover:bg-blue-800 focus:ring-0 focus:outline-none focus:ring-blue-300 font-medium text-sm px-5 py-2.5 text-center mr-2 mb-2 dark:border-blue-500 dark:text-blue-500 dark:hover:text-white dark:hover:bg-blue-600 dark:focus:ring-blue-800"
      >
        {{ buttonLabel }}
      </button>
    </div>
  </div>

  <TransitionRoot appear :show="showExtensionModal" as="template">
    <Dialog as="div" @close="closeModal" class="relative z-10">
      <TransitionChild
        as="template"
        enter="duration-300 ease-out"
        enter-from="opacity-0"
        enter-to="opacity-100"
        leave="duration-200 ease-in"
        leave-from="opacity-100"
        leave-to="opacity-0"
      >
        <div class="fixed inset-0 bg-black bg-opacity-25" />
      </TransitionChild>
      <div
        class="fixed h-fit inset-0 overflow-y-auto flex items-center justify-center"
      >
        <div
          class="fixed inset-0 overflow-y-auto flex items-center justify-center"
        >
          <TransitionChild
            as="template"
            enter="duration-300 ease-out"
            enter-from="opacity-0 scale-95"
            enter-to="opacity-100 scale-100"
            leave="duration-200 ease-in"
            leave-from="opacity-100 scale-100"
            leave-to="opacity-0 scale-95"
          >
            <DialogPanel
              class="flex-grow w-full h-fit transform overflow-visible rounded-2xl bg-gray-200 dark:bg-slate-800 p-6 text-left align-middle shadow-xl transition-all max-w-[90vw] sm:max-w-[80vw] md:max-w-[70vw] lg:max-w-[60vw] xl:max-w-[50vw] 2xl:max-w-[40vw]"
            >
              <DialogTitle
                as="h3"
                class="flex text-lg font-medium leading-6 text-gray-900 justify-center dark:text-white"
              >
                {{ buttonTitle }}
              </DialogTitle>

              <form @submit="onSubmit($event)">
                  <div class="flex mt-5">
                    <div class="w-full z-40">
                      <select
                        v-model="element"
                        class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
                      >
                        <option
                          v-for="option in extensionTypes"
                          :value="option.value"
                          :key="option.value"
                        >
                          {{ option.text }}
                        </option>
                      </select>
                    </div>
                  </div>

                <!-- Submit/Cancel button for the modal -->
                <FormSubmit @submit="onSubmit" @close="closeModal" />
              </form>
            </DialogPanel>
          </TransitionChild>
        </div>
      </div>
    </Dialog>
  </TransitionRoot>
</template>

<script setup>
import {
  TransitionRoot,
  TransitionChild,
  Dialog,
  DialogPanel,
  DialogTitle,
  Switch,
} from "@headlessui/vue";

const props = defineProps({
  buttonTitle: {
    type: String, // title for the field
    required: false,
  },
  buttonLabel: {
    type: String, // label for the button
    required: false,
  },
});

const emits = defineEmits(["onSubmit"]);

const extension = ref({});
const showExtensionModal = useState("showExtensionModal", () => false);
const extensionTypes = useState("extensionTypes", () => [
  { value: "extensionElement", text: "Element" },
]);
const dataTypes = useState("dataTypes", () => [
  { value: "string", text: "String" },
  { value: "complex", text: "Complex" },
]);

//Open the modal on click of the button
function openModal() {
  showExtensionModal.value = true;
}

//Close the modal on click of the button
function closeModal() {
  showExtensionModal.value = false;
}

//Function to update the value of the object based on DropDown value change
function onItemChange(fieldName, value) {
  extension.value = {};
  extension.value[fieldName] = value.value;
}

//Function to store and return the value on submission
function onSubmit(event) {
  event.preventDefault();
  console.log("Sumitted : " + JSON.stringify(extension.value, null, 4));
  emits("onSubmit", extension.value);
  closeModal();
}
</script>

<style>
</style>

I am using this component twice in my application: pages/view.vue:

<template>
    <div>
  
      <div class="my-2 mx-2 px-2 pb-0">
        <div class="flex gap-2 h-16 float-right">
          <ExtensionModal
            :buttonTitle="$t('pages.modal.userExtension-btt-title')"
            :buttonLabel="$t('pages.modal.userExtension-btt-label')"
          />
  
          <ExtensionModal
            :buttonTitle="$t('pages.modal.ilmd-btt-title')"
            :buttonLabel="$t('pages.modal.ilmd-btt-label')"
          />

        </div>
      </div>
    </div>
  </template>
  
  <script setup>
  import { Icon } from "@iconify/vue";
  </script>
  
  <style>
  </style>

When I click on the button to open modal then I get the error:

There are no focusable elements inside the <FocusTrap />`

The installation and everything else is correct because it works fine when I use only one instance of the child component in my view.vue page:

<ExtensionModal
            :buttonTitle="$t('pages.modal.userExtension-btt-title')"
            :buttonLabel="$t('pages.modal.userExtension-btt-label')"
          />

I am getting the error only when i use it more than once in my application. Not sure what am I doing wrong here. Do I need to pass any additional parameter or add any missing information?

Upvotes: 2

Views: 2036

Answers (2)

Kasturi
Kasturi

Reputation: 41

I also had similar issue. You can solve by adding the initialFocus in the Dialog tag.

 <Dialog as="div" 
   @close="closeModal" 
   class="relative z-10"  
   :initialFocus="modalFocus">

and Make sure to initialize the modalFocus variable in the script section:

<script setup>
import { ref } from 'vue';
    
const modalFocus = ref(null);
</script>

you can also see these mentioned in the headlessUI documentation. here is the link.

Upvotes: 0

weekent
weekent

Reputation: 11

I also having this issues with "There are no focusable elements inside the <FocusTrap //> " in dev console. And I succeed to solve it.

  1. This message is showed because of there is no element can be focused.
  2. Headless UI Dialog should contain at least one focusable element.

By searching for the documentation provided by the headless ui website. Headless UI Dialog

You can manage to initial focus by using Vue ref. Please try your best to figure it out.

Upvotes: 1

Related Questions