Reputation: 3530
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
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
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.
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