Vuepic / vue3-date-time-picker

Datepicker component for Vue 3
https://vue3datepicker.com
MIT License
158 stars 13 forks source link

Usage inside modal not working #85

Closed flowbru closed 2 years ago

flowbru commented 2 years ago

Hi there,

thanks alot for your awesome datepicker!

I just tried to use it inside a modal (tailwindui) and the input field is displayed correctly. However when I click on the input field, the date picker menu is not being opened. When I embed the datepicker the exact same way on the normal page, it works as expected.

What am I missing here?

Jasenkoo commented 2 years ago

WIll check what is going on, can you add a snippet of which props you are using?

flowbru commented 2 years ago

Thank you for your response. You can find my whole modal code including the datepicker below.

<template>
    <TransitionRoot as="template" :show="open">
        <Dialog as="div" class="fixed z-10 inset-0 overflow-y-auto" >
            <div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
                <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">
                    <DialogOverlay class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
                </TransitionChild>

                <span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">&#8203;</span>
                <TransitionChild as="template" enter="ease-out duration-300" enter-from="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" enter-to="opacity-100 translate-y-0 sm:scale-100" leave="ease-in duration-200" leave-from="opacity-100 translate-y-0 sm:scale-100" leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95">
                    <div class="inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6">
                        <div>
                            <div class="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-green-100">
                                <CalendarIcon class="h-6 w-6 text-green-600" aria-hidden="true" />
                            </div>
                            <div class="mt-3 text-center sm:mt-5">
                                <DialogTitle as="h3" class="font-bold text-2xl leading-6 text-gray-900">
                                    {{ $t('Create new appointment') }}
                                </DialogTitle>
                                <Datepicker v-model="date"
                                            format="dd.MM.yyyy HH:mm"
                                            locale="de-DE" />

                            </div>
                        </div>
                        <div class="mt-5 sm:mt-6 sm:grid sm:grid-cols-2 sm:gap-3 sm:grid-flow-row-dense">
                            <!-- TODO: Buttons -->
                        </div>
                    </div>
                </TransitionChild>
            </div>
        </Dialog>
    </TransitionRoot>
</template>

<script setup>
import { ref } from 'vue';
import { Dialog, DialogOverlay, DialogTitle, TransitionChild, TransitionRoot } from '@headlessui/vue';
import { CheckIcon, CalendarIcon } from '@heroicons/vue/outline';
import Datepicker from 'vue3-date-time-picker';

let props = defineProps({
    showModal: Boolean,
});

const date = ref(new Date());

const open = ref(props.showModal);

const emit = defineEmits(['close']);

function closeModal() {
    open.value = false;
    emit('close');
}

</script>
<style scoped>
.dp__menu {
    z-index: 9999!important;
}
</style>
Jasenkoo commented 2 years ago

Investigated a little, this is not an issue from the date picker side. The dialog itself focuses first focusable element, causing conflict within a component since the date picker has a prop open on focus. To be able to open the menu I had to add the initial-focus element to a button as stated here.

Also, noticed some strange behavior, when I click on the component from the dialog, it does a multi-click, which is very odd. I guess they do some custom click handling in the Dialog component. This is happening only when I don't specify initial-focus element. This is also the reason why the menu is not opening.

Also when the menu is opened with a portal, causes the dialog to close whenever the menu is clicked. This is happening if you connect the @close event from the dialog. This will be fixed in the upcoming version.

flowbru commented 2 years ago

Thank you for your support!

Jasenkoo commented 2 years ago

Included in the v2.5.0

rinaldihtb commented 2 years ago

hi @Jasenkoo ,

I found strange behavior while using datepicker inside modal from CoreUI Vue here. which it is using bootstrap4

basically it did not prompt any error, but when I was picking a date, the modal automatically closed too.

is there any option how do I resolve this ?

Thanks in advance.

Jasenkoo commented 2 years ago

@rinaldihtb What version of the component are you using?

rinaldihtb commented 2 years ago

@rinaldihtb What version of the component are you using?

@Jasenkoo , thanks for your response,

for datepicker, i'm using the latest one. "vue3-date-time-picker": "^2.5.0",

and for CoreUI i'm using 4.1 version

lgazoni commented 2 years ago

Hello Guys, Recently I'm using this template https://github.com/uilibrary/AatroX-vue.
I tried to use "vue3-date-time-picker": "^2.5.0" in a modal and I couldn't get it to work. Please would you have any help?

The same problem as @flowbru

johndalangin commented 2 years ago

TLDR: Use teleport feature to render the date picker from WITHIN the modal instead of default body element

--

@lgazoni @rinaldihtb We recently encountered this error using Tailwind UI Dialog/Modal wherein the datepicker automatically closes upon any interaction.

We found out that this happens since vue3-date-time-picker renders as a child of the root body element and Headless UI Dialog/Modal prevents any clicks to elements outside of the modal.

To solve, we used the teleport function of vue3-date-time-picker to make the plugin render within the modal instead of at the root body element, which prevents clicks/focus.

See example below:

<template>
  <Dialog :open="isOpen" @close="setIsOpen">
    <DialogOverlay />

    <DialogTitle>Deactivate account</DialogTitle>
    <DialogDescription>
      This will permanently deactivate your account
    </DialogDescription>

    <p>
      Are you sure you want to deactivate your account? All of your data will be
      permanently removed. This action cannot be undone.
    </p>

    <div class="mt-2">
      <!-- Set teleport attribute here! -->
      <Datepicker
        teleport="'#datePickerContainerId" <----------------- this <------------------
        inputClassName="rounded-lg font--h5"
      />
    </div>

      <!-- Provide div for teleport WITHIN the modal -->
    <div id="datePickerContainerId"></div>

    <button @click="setIsOpen(false)">Deactivate</button>
    <button @click="setIsOpen(false)">Cancel</button>
  </Dialog>
</template>

<script>
  import { ref } from "vue";
  import {
    Dialog,
    DialogOverlay,
    DialogTitle,
    DialogDescription,
  } from "@headlessui/vue";

  import Datepicker from "vue3-date-time-picker";
  import "vue3-date-time-picker/dist/main.css";

  export default {
    components: { Dialog, DialogOverlay, DialogTitle, DialogDescription, Datepicker },
    setup() {
      let isOpen = ref(true);

      return {
        isOpen,
        setIsOpen(value) {
          isOpen.value = value;
        },
      };
    },
  };
</script>
johndalangin commented 2 years ago

@Jasenkoo We might need to include this in the package documentation. How do we go about this? I can submit a PR, just let me know šŸ‘

Jasenkoo commented 2 years ago

@johndalangin @lgazoni @rinaldihtb

@lgazoni @rinaldihtb We recently encountered this error using Tailwind UI Dialog/Modal wherein the datepicker automatically closes upon any interaction.

This is partially due to dialog, also, if you are using some form of components, most of them rely on click-outside directive from vueuse library which has an event listener on mousedown which is triggered before I can call stopPropagnation.

We found out that this happens since vue3-date-time-picker renders as a child of the root body element and Headless UI Dialog/Modal prevents any clicks to elements outside of the modal.

Also, this solution works fine, that is the way the teleport target is provided. If it is rendered without teleport, it may cause some issues with relative parents.

We might need to include this in the package documentation. How do we go about this? I can submit a PR, just let me know šŸ‘

For now, I have the docs in a separate repo, I might move to the main in the near future. Where would you like to add it? Under teleport or somewhere on top?

I found strange behavior while using datepicker inside modal from CoreUI Vue here. which it is using bootstrap4

I will take a deeper look at what is happening here.

We recently encountered this error using Tailwind UI Dialog/Modal

Are you using pure CSS or with some component framework?

lgazoni commented 2 years ago

Hello @johndalangin ,

Thank you so much. I tried to put your suggestion in the modal I developed, but unfortunately it didn't work. The form still doesn't open the calendar. another problem I had was to include the css import, it only worked including in the header as I believe it is incompatible with postCSS.

See my example below:

<template>
  <link
    rel="stylesheet"
    href="https://unpkg.com/vue3-date-time-picker@latest/dist/main.css"
  />
  <TransitionRoot appear :show="show" as="template">
    <Dialog as="div">
      <div class="fixed inset-5 z-10 overflow-y-auto">
        <div class="min-h-screen px-4 text-center">
          <TransitionChild
            as="template"
            enter="duration-300 ease-out"
            leave="duration-200 ease-in"
          >
            <DialogOverlay class="fixed inset-0 bg-black opacity-10" />
          </TransitionChild>

          <span class="inline-block h-screen align-middle" aria-hidden="true">
            &#8203;
          </span>

          <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"
          >
            <div
              class="inline-block w-full max-w-4xl p-6 my-8 overflow-hidden text-left align-middle transition-all transform bg-white shadow-xl rounded-2xl"
            >
              <DialogTitle
                as="h3"
                class="text-lg font-medium leading-6 text-gray-900"
              >
                Janela de configuraĆ§Ć£o de tarefa
              </DialogTitle>
              <div class="mt-2 mb-5">
                <p class="text-sm text-gray-500">
                  Realize a configuraĆ§Ć£o da tarefa selecionada como um intervalo
                  de tempo ou monitoramento em tempo real
                </p>
              </div>

              <!-- 
                  OPƇƕES DE SELECAO (MONITORAMENTO ON LINE OU INTERVALO) 
              -->
              <!-- FLEX COM {TOGGLE} : {INICIO E FIM }: {BOTOES RESULTADOS E CANCELAR}  -->
              <div class="grid grid-flow-row-dense mb-auto">
                <!-- INCLUSAO DO TOGGLE PARA ANALISE E MONITORAMENTO  -->
                <div class="flex-initial w-40 mb-4 my-5 mt-3">
                  <Switch
                    v-model="monitoring"
                    v-on:click="SwitchMonitoring()"
                    :class="monitoring ? 'bg-purple-500' : 'bg-purple-500'"
                    class="relative inline-flex items-center h-6 rounded-full w-11 border-black"
                  >
                    <!-- <span class="sr-only">Enable notifications</span> -->
                    <span
                      :class="monitoring ? 'translate-x-6' : 'translate-x-1'"
                      class="inline-block w-4 h-4 transform bg-white rounded-full"
                    />
                  </Switch>
                  <span class="ml-2" v-if="!isActive">Intevalo</span>
                  <span class="ml-2" v-if="isActive">OnLine</span>
                </div>
                <!-- ------------------------ -->

                <!-- FLEX PARA INCLUIR A SELEƇƃO DO INTRVALO DE TEMPO INICIAL E FINAL -->
                <div v-show="!monitoring" class="flex" id="idAnalise">
                  <div class="flex-auto">
                    <spam
                      class="text-sm block tracking-wide text-gray-700 font-bold mb-2"
                      >Inicio:</spam
                    >
                    <Datepicker
                      v-model="date"
                      format="yyyy-MM-dd HH:mm:ss"
                      teleport="'#datePickerContainerId"
                    /><!-- include teleport -->
                  </div>
                  <!-- Provide div for teleport WITHIN the modal -->
                  <div id="datePickerContainerId"></div>

                  <div class="flex w-5"></div>

                  <div class="flex-auto">
                    <spam
                      class="text-sm block tracking-wide text-gray-700 font-bold mb-2"
                      >Fim:</spam
                    ><Datepicker
                      v-model="date"
                      format="yyyy-MM-dd HH:mm:ss"
                      teleport="'#datePickerContainerId" 
                    /> <!-- include teleport -->
                  </div>
                  <!-- Provide div for teleport WITHIN the modal -->
                  <div id="datePickerContainerId"></div>

                  <!-- BOTOES PARA RECUPERAR A ANALISE E CANCELAR -->
                  <div class="flex w-5"></div>
                  <div class="float-right mt-5 flex">
                    <BaseBtn
                      class="border border-primary text-primary rounded-full hover:bg-primary hover:text-white mt-2 mr-2"
                      @click="closeModal(true)"
                      >Resultados</BaseBtn
                    >
                    <BaseBtn
                      class="border border-danger text-danger rounded-full hover:bg-danger hover:text-white mt-2 mr-2"
                      @click="closeModal(false)"
                      >Cancelar</BaseBtn
                    >
                  </div>
                </div>

                <!--  {FREQUENCIA }  : {BOTOES RESULTADOS E CANCELAR}  -->

                <div v-show="monitoring" class="flex">
                  <div class="flex-col" id="idRealTime">
                    <div class="flex-auto">
                      <spam
                        class="text-sm block tracking-wide text-gray-700 font-bold mb-2"
                        >FrequĆŖncia (seg):</spam
                      >
                      <!-- entrada do campo frequencia do Timer -->
                      <input
                        class="number flex-auto appearance-none block bg-gray-50 text-gray-700 border border-gray-200 rounded leading-tight focus:outline-none focus:bg-white focus:border-gray-500"
                        id="freq"
                        type="number"
                        value="50"
                        name="frequencyNumber"
                      />
                    </div>
                  </div>

                  <!-- BOTOES PARA REALTIME E CANCELAR -->

                  <div class="flex w-5"></div>
                  <div class="float-right mt-5 flex">
                    <BaseBtn
                      class="border border-primary text-primary rounded-full hover:bg-primary hover:text-white mt-2 mr-2"
                      @click="closeModal(true)"
                      >Monitoramento</BaseBtn
                    >
                    <BaseBtn
                      class="border border-danger text-danger rounded-full hover:bg-danger hover:text-white mt-2 mr-2"
                      @click="closeModal(false)"
                      >Cancelar</BaseBtn
                    >
                  </div>
                </div>
              </div>
              <!-- ------------------------------------------------------------- -->
            </div>
          </TransitionChild>
        </div>
      </div>
    </Dialog>
  </TransitionRoot>
  <TemplateEditor
    :show="edit"
    :title="title"
    :type="type"
    :subTemplate="selectedGraph"
    @close="edit = false"
    @confirm="addGraph"
  />
</template>

<script>
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */

import BaseBtn from "@/components/Base/BaseBtn.vue";
import { mapActions } from "vuex";

import Datepicker from "vue3-date-time-picker";
//import "vue3-date-time-picker/dist/main.css";

import {
  TransitionRoot,
  TransitionChild,
  Dialog,
  DialogOverlay,
  DialogTitle,
  Switch,
} from "@headlessui/vue";

export default {
  props: {
    show: Boolean, ///< Boolean para exibir a janela
    idTask: Number, ///< Id da task atual
    taskName: String, ///< Nome da Task
  },
  data() {
    let completeButtonRef = null;
    const date = new Date();
    return {
      url: process.env.API_URL, ///< Teste com Env
      monitoring: true, ///< Toogle de selecao (false == Intervals, true == Realtime)
      date, ///< Data Atual
      startDateTime: Date, ///< Data inicial com hora
      endDateTime: Date, ///< Data Final com hora
      frequency: Number, ///< Numero da frequencia em ms para monitoramento em real time
      completeButtonRef,
    };
  },

  //mounted() {
  //
  //},

  components: {
    BaseBtn,
    TransitionRoot,
    TransitionChild,
    Dialog,
    DialogOverlay,
    DialogTitle,
    Switch,
    Datepicker,
  },

  methods: {
    ...mapActions({ updateTemplate: "userSession/updateTemplate" }),

    /**
     * @brief recarrega template original
     *
     */
    refresh() {
      this.editedTemplate = { ...this.template };
    },

    /**
     * @brief fecha o modal de lista dos grƔficos
     *
     * @param confirm indica se o usuƔrio confirmou as alteraƧƵes feitas no template
     */
    closeModal(confirm) {
      if (confirm) {
        //this.updateTemplate({ ...this.editedTemplate });
      }
      this.$emit("closeModal");
    },
  },

  /**
   * Funcao para Exibir ou ocultar os parametros de monitoramento conforme selecionado
   * Realtime ==>
   * Intervals ==> Start and Time
   */
  SwitchMonitoring() {
    console.log(this.mode);
    // if (mode == "visible") {
    //   this.isRealtime = "visible";
    // } else {
    //   this.isRealtime = "invisible";
    // }
  },
  computed: {
    /** funcao para controlar o estado do toggle (analise ou realtime) */
    isActive() {
      return this.monitoring;
    },
  },
};
</script>
jordcodes commented 2 years ago

@johndalangin I have another issue following the teleport example posted.

Every time styles of transform, left and top are added into the teleport causing it datepicker to be rendered offscreen. The id given is modified based on the id of the teleport.

johndalangin commented 2 years ago

Hi @lgazoni kindly check your syntax you may have an unnecessary ' here:

teleport="'#datePickerContainerId"
          ^
          ^
          ^

Sorry about that, that's my bad.

@jadpdm Could you provide a codesandbox.io link to an example of your concern? Thank you.