nathanreyes / v-calendar

An elegant calendar and datepicker plugin for Vue.
https://vcalendar.io
MIT License
4.32k stars 840 forks source link

How to prevent popover to close on dayclick #1358

Open akorajac opened 11 months ago

akorajac commented 11 months ago

Using the following example, with the mode="dateTime": https://vcalendar.io/datepicker/slot-content.html#manual-control How can I prevent the popover being closed on day click.

I want to be able to select the time manually rather than it being selected automatically on dayclick before closing the popover.

<script setup>
import { ref } from 'vue'

const date = ref()
</script>

<template>
  <VDatePicker v-model="date" :popover="false" mode="dateTime">
    <template #default="{ togglePopover, inputValue, inputEvents }">
      <div class="flex overflow-hidden rounded-lg border border-gray-300 dark:border-gray-600">
        <button
          class="bg-accent-100 hover:bg-accent-200 text-accent-700 dark:text-accent-300 flex items-center justify-center border-r border-gray-300 px-2 dark:border-gray-600 dark:bg-gray-700 dark:hover:bg-gray-600"
          @click="() => togglePopover()"
        >
          <IconCalendar class="h-5 w-5" />
        </button>
        <input :value="inputValue" v-on="inputEvents" class="flex-grow bg-white px-2 py-1 dark:bg-gray-700" />
      </div>
    </template>
  </VDatePicker>
</template>
WeiShen68 commented 5 months ago

Is there any other greater solution to prevent popover to close on dayclick 🥹

yuriymarad commented 5 months ago
<div class="relative">
    <span @click="datePickerVisible = true">
        {{ range }} <!-- your syled input here -->
    </span>
    <div v-if="datePickerVisible" class="absolute z-10 mt-1">
        <div class="bg-white p-4 rounded-lg">
            <DatePicker  v-model.range="range" mode="dateTime" is24hr />
        </div>
    </div>
    <div v-if="datePickerVisible" class="fixed inset-0" @click="datePickerVisible = false"></div>
</div>

Is there any other greater solution to prevent popover to close on dayclick 🥹

guilhermeagirardi commented 5 months ago

@yuriymarad you can simply stop using the v-calendar popover and control it manually.

Here's an example:

<template>
    <div class="w-44 relative date-range-input">
        <input
            type="text"
            class="border-gray-300 w-full py-0.5 px-2 focus:border-green-300 focus:ring focus:ring-green-200 focus:ring-opacity-50 rounded-md shadow-sm text-sm"
            placeholder="Start"
            v-model="startDate"
            @click="open = true"
            @keydown.enter="apply"
        />

        <input
            type="text"
            class="border-gray-300 w-full mt-1 py-0.5 px-2 focus:border-green-300 focus:ring focus:ring-green-200 focus:ring-opacity-50 rounded-md shadow-sm text-sm"
            placeholder="End"
            v-model="endDate"
            @click="open = true"
            @keydown.enter="apply"
        />

        <div class="absolute left-0" v-show="open">
            <DatePicker
                v-model="range"
                mode="dateTime"
                form
                color="green"
                is-range
                :is24hr="false"
                hide-time-header
                trim-weeks
                :key="fix"
            >
                <template v-slot:footer>
                    <div class="bg-gray-100 text-center p-2 border-t rounded-b-lg">
                        <Button @click="apply" :disabled="!range.start || !range.end">
                            Apply
                        </Button>
                    </div>
                </template>
            </DatePicker>
        </div>
    </div>
</template>

<script>
    //Components
    import Button from '../Button.vue';
    import { Calendar, DatePicker } from 'v-calendar';
    import 'v-calendar/dist/style.css';

    //Aux
    import moment from "moment";

    export default {
        name: "DateRangeInput",

        data() {
            return {
                open: false,
                range: {
                    start: null,
                    end: null,
                },
                fix: 0
            }
        },

        computed: {
            startDate: {
                get() {
                    return this.range.start
                        ? moment(this.range.start).format(this.mask)
                        : '';
                },
                set(value) {
                    const newStart = moment(value, this.mask);
                    if (newStart.isValid()) {
                        this.range.start = newStart.toDate();
                        this.fix++;
                    }
                }
            },

            endDate: {
                get() {
                    return this.range.end
                        ? moment(this.range.end).format(this.mask)
                        : '';
                },
                set(value) {
                    const newEnd = moment(value, this.mask);
                    if (newEnd.isValid()) {
                        this.range.end = newEnd.toDate();
                        this.fix++;
                    }
                }
            },
        },

        props: {
            modelValue: {
                required: true
            },
            mask: {
                type: String,
                default: () => 'YYYY-MM-DD, hh:mm A'
            }
        },

        methods: {
            handleClickOutside(event) {
                const clickedOnInput = event.target.closest('.date-range-input');

                // The click was outside the input and the calendar?
                if (!clickedOnInput)
                    this.open = false;
            },

            apply() {
                //your logic here
            }
        },

        mounted() {
            document.addEventListener('click', this.handleClickOutside);
        },

        beforeUnmount() {
            document.removeEventListener('click', this.handleClickOutside);
        },

        created() {
            if (this.modelValue) {
                const datetimes = this.modelValue.split(',');
                this.range = {
                    start: moment(datetimes[0]).toISOString(),
                    end: moment(datetimes[1]).toISOString(),
                };
            }
        },

        components: {
            DatePicker,
            Button,
            Calendar
        }
    }
</script>

This code is adapted to my needs, obviously for you the reality is different. Delete, modify or add as needed.

This code saved me.