ソースを参照

feat: 日期插件本地化

rindy 2 年 前
コミット
fa62d8eccf

+ 13 - 0
src/assets/css/_settings.scss

@@ -0,0 +1,13 @@
+/* Color system */
+$white: #ffffff;
+$black: #2f2f2f;
+$green: #40b983;
+$darkGreen: #2f9668;
+$darkestGreen: #217751;
+$blue: #00b9f5;
+$dark-blue: #012b72;
+$grey-600: #6c757d;
+$grey-800: #485e7f;
+$hovered: #f8f8f8;
+$highlight: $green;
+

+ 267 - 0
src/components/datepicker/DateInput.vue

@@ -0,0 +1,267 @@
+<template>
+    <div :class="[addBootstrapClass ? 'input-group' : '']">
+        <!-- Calendar Button -->
+        <span
+            v-if="calendarButton"
+            class="vuejs3-datepicker__calendar-button"
+            :class="{ 'input-group-prepend': addBootstrapClass }"
+            @click="showCalendar"
+            v-bind:style="{ 'cursor:not-allowed;': disabled }"
+        >
+            <span :class="{ 'input-group-text': addBootstrapClass }">
+                <i :class="calendarButtonIcon">
+                    {{ calendarButtonIconContent }}
+                    <span v-if="!calendarButtonIcon">&hellip;</span>
+                </i>
+            </span>
+        </span>
+        <div v-if="typeable || !hideInput" style="position: relative">
+            <span v-if="!inline">
+                <IconView customClass="vuejs3-datepicker__typeablecalendar" :color="iconColor" :width="iconWidth" :height="iconHeight" />
+            </span>
+            <input
+                :type="inline ? 'hidden' : 'text'"
+                :class="computedInputClass"
+                :name="name"
+                ref="inputRef"
+                :id="id"
+                class="vuejs3-datepicker__inputvalue"
+                :value="formattedValue"
+                :open-date="openDate"
+                :placeholder="placeholder"
+                :clear-button="clearButton"
+                :disabled="disabled"
+                :required="required"
+                :readonly="!typeable"
+                @click="showCalendar"
+                @keyup="parseTypedDate"
+                @blur="inputBlurred"
+                autocomplete="off"
+            />
+        </div>
+        <div v-else @click="showCalendar" id="calendar-div">
+            <div class="vuejs3-datepicker__value" v-if="!inline">
+                <span class="vuejs3-datepicker__icon">
+                    <IconView :color="iconColor" :width="iconWidth" :height="iconHeight" />
+                </span>
+                <div class="vuejs3-datepicker__content" v-if="formattedValue">{{ formattedValue }}</div>
+                <div v-else class="vuejs3-datepicker__content">{{ placeholder }}</div>
+            </div>
+        </div>
+        <span v-if="clearButton && selectedDate" class="vuejs3-datepicker__clear-button" :class="{ 'input-group-append': addBootstrapClass }" @click="clearDate()">
+            <span :class="{ 'input-group-text': addBootstrapClass }">
+                <i :class="clearButtonIcon">
+                    <span v-if="!clearButtonIcon">&times;</span>
+                </i>
+            </span>
+        </span>
+        <slot name="belowDate"></slot>
+    </div>
+</template>
+
+<script>
+import { computed, defineComponent, ref, watch } from 'vue'
+import IconView from '../iconview/IconView.vue'
+import { formatDate, stringToDate } from './utils/DateUtils'
+
+export default defineComponent({
+    name: 'DateInput',
+    components: {
+        IconView,
+    },
+    props: {
+        selectedDate: {
+            type: [Date, String],
+        },
+        resetTypedDate: {
+            type: [Date],
+        },
+        format: {
+            type: [String, Function],
+        },
+        translation: {
+            type: Object,
+        },
+        inline: {
+            type: Boolean,
+        },
+        id: {
+            type: String,
+        },
+        name: {
+            type: String,
+        },
+        openDate: {
+            type: Date,
+        },
+        placeholder: {
+            type: String,
+        },
+        inputClass: {
+            type: String,
+        },
+        clearButton: {
+            type: Boolean,
+        },
+        clearButtonIcon: {
+            type: String,
+        },
+        calendarButton: {
+            type: Boolean,
+        },
+        calendarButtonIcon: {
+            type: String,
+        },
+        calendarButtonIconContent: {
+            type: String,
+        },
+        disabled: {
+            type: Boolean,
+        },
+        required: {
+            type: Boolean,
+        },
+        typeable: {
+            type: Boolean,
+        },
+        addBootstrapClass: {
+            type: Boolean,
+        },
+        useUtc: {
+            type: Boolean,
+        },
+        minimumView: {
+            type: String,
+            default: 'day',
+        },
+        maximumView: {
+            type: String,
+            default: 'year',
+        },
+        hideInput: {
+            type: Boolean,
+            default: true,
+        },
+        fullMonthName: {
+            type: Boolean,
+            default: false,
+        },
+        iconColor: {
+            default: 'black',
+            type: String,
+        },
+        iconHeight: {
+            default: 16,
+            type: [String, Number],
+        },
+        iconWidth: {
+            default: 16,
+            type: [String, Number],
+        },
+        theme: {
+            default: 'green',
+            type: String,
+        },
+    },
+    emits: ['show-calendar', 'typed-date', 'clear-date', 'close-calendar'],
+    setup(props, { emit }) {
+        const typedDate = ref()
+        const inputRef = ref(null)
+        const computedInputClass = computed(() => {
+            if (props.addBootstrapClass) {
+                if (typeof props.inputClass === 'string') {
+                    return [props.inputClass, 'form-control'].join(' ')
+                }
+                return Object.assign({ 'form-control': true }, props.inputClass)
+            }
+            return props.inputClass
+        })
+        const formattedValue = computed(() => {
+            var _a, _b
+            if (!props.selectedDate) {
+                return null
+            }
+            if (typedDate.value) {
+                return typedDate.value
+            }
+            const propDate = stringToDate(props.selectedDate)
+            let date = typeof props.format === 'function' ? props.format(propDate) : formatDate(propDate, props.format, props.translation)
+            if (props.minimumView === props.maximumView) {
+                const [, y, z] = date.split(' ')
+                if (props.maximumView === 'month') {
+                    if (props.fullMonthName) {
+                        const i = (_a = props.translation) === null || _a === void 0 ? void 0 : _a.monthsAbbr.indexOf(y)
+                        return (_b = props.translation) === null || _b === void 0 ? void 0 : _b.months[i]
+                    }
+                    date = y
+                } else if (props.maximumView === 'year') {
+                    date = z
+                }
+            }
+            return date
+        })
+        // watchers
+        watch(
+            () => props.resetTypedDate,
+            () => {
+                typedDate.value = ''
+            }
+        )
+        /**
+         * open Calendar
+         */
+        function showCalendar() {
+            emit('show-calendar')
+        }
+        /**
+         * Attempt to parse a typed date
+         * @param {Event} event
+         */
+        function parseTypedDate(event) {
+            if (
+                [
+                    27,
+                    13, // enter
+                ].includes(event.keyCode)
+            ) {
+                inputRef.value.blur()
+            }
+            if (props.typeable) {
+                const { value } = inputRef.value
+                const temptypedDate = Date.parse(value)
+                if (!Number.isNaN(temptypedDate)) {
+                    typedDate.value = value
+                    emit('typed-date', new Date(temptypedDate))
+                }
+            }
+        }
+        /**
+         * emit a clearDate event
+         */
+        function clearDate() {
+            emit('clear-date')
+        }
+        /**
+         * nullify the typed date to defer to regular formatting
+         * called once the input is blurred
+         */
+        function inputBlurred() {
+            if (props.typeable && Number.isNaN(Date.parse(inputRef.value.value))) {
+                clearDate()
+                inputRef.value.value = null
+                typedDate.value = ''
+            }
+            emit('close-calendar', true)
+        }
+        return {
+            typedDate,
+            computedInputClass,
+            formattedValue,
+            showCalendar,
+            parseTypedDate,
+            inputBlurred,
+            inputRef,
+        }
+    },
+})
+</script>

+ 617 - 0
src/components/datepicker/Datepicker.vue

@@ -0,0 +1,617 @@
+<template>
+  <div
+    class="vuejs3-datepicker"
+    :class="[isRtl ? 'rtl' : '', `vuejs3-${theme}`, wrapperClass]"
+    v-clickoutside="{
+      handler: inline ? null : closeOnClickOutside,
+    }"
+  >
+    <date-input
+      :selectedDate="selectedDate"
+      :resetTypedDate="resetTypedDate"
+      :format="format"
+      :translation="translation"
+      :inline="inline"
+      :id="id"
+      :name="name"
+      :fullMonthName="fullMonthName"
+      :openDate="openDate"
+      :placeholder="placeholder"
+      :inputClass="inputClass"
+      :typeable="typeable"
+      :clearButton="clearButton"
+      :clearButtonIcon="clearButtonIcon"
+      :calendarButton="calendarButton"
+      :calendarButtonIcon="calendarButtonIcon"
+      :calendarButtonIconContent="calendarButtonIconContent"
+      :disabled="disabled"
+      :required="required"
+      :addBootstrapClass="addBootstrapClass"
+      :use-utc="useUtc"
+      @showCalendar="showCalendar"
+      @closeCalendar="close"
+      @typedDate="setTypedDate"
+      @clearDate="clearDate"
+      :minimumView="minimumView"
+      :maximumView="maximumView"
+      :hideInput="hideInput"
+      :iconWidth="iconWidth"
+      :iconHeight="iconHeight"
+      :iconColor="iconColor"
+      :theme="theme"
+    >
+      <template v-slot:belowDate>
+        <slot name="belowDate"></slot>
+      </template>
+    </date-input>
+    <!--Day View  -->
+    <picker-day
+      v-if="allowedToShowView('day')"
+      :pageDate="pageDate"
+      :selectedDate="selectedDate"
+      :showDayView="showDayView"
+      :fullMonthName="fullMonthName"
+      :allowedToShowView="allowedToShowView"
+      :disabledDates="disabledDates"
+      :highlighted="highlighted"
+      :calendarClass="calendarClass"
+      :calendarStyle="calendarStyle"
+      :translation="translation"
+      :pageTimestamp="pageTimestamp"
+      :isRtl="isRtl"
+      :mondayFirst="mondayFirst"
+      :dayCellContent="dayCellContent"
+      @changedMonth="handleChangedMonthFromDayPicker"
+      @selectDate="selectDate"
+      @showMonthCalendar="showMonthCalendar"
+      @selectedDisabled="selectDisabledDate"
+      @showYearCalendar="showYearCalendar"
+      :minimumView="minimumView"
+      :maximumView="maximumView"
+      :preventDisableDateSelection="preventDisableDateSelection"
+      :theme="theme"
+    >
+      <template v-slot:customCalendarHeader>
+        <slot name="customCalendarHeader"></slot>
+      </template>
+    </picker-day>
+
+    <!--Month View -->
+    <picker-month
+      v-if="allowedToShowView('month')"
+      :pageDate="pageDate"
+      :selectedDate="selectedDate"
+      :showMonthView="showMonthView"
+      :allowedToShowView="allowedToShowView"
+      :disabledDates="disabledDates"
+      :calendarClass="calendarClass"
+      :calendarStyle="calendarStyle"
+      :translation="translation"
+      :isRtl="isRtl"
+      :use-utc="useUtc"
+      :fullMonthName="fullMonthName"
+      @selectMonth="selectMonth"
+      @showYearCalendar="showYearCalendar"
+      @changedYear="setPageDate"
+      :minimumView="minimumView"
+      :maximumView="maximumView"
+      :theme="theme"
+    >
+      <template v-slot:customCalendarHeader>
+        <slot name="customCalendarHeader"></slot>
+      </template>
+    </picker-month>
+
+    <!-- Year View -->
+    <picker-year
+      v-if="allowedToShowView('year')"
+      :pageDate="pageDate"
+      :selectedDate="selectedDate"
+      :showYearView="showYearView"
+      :allowedToShowView="allowedToShowView"
+      :disabledDates="disabledDates"
+      :calendarClass="calendarClass"
+      :calendarStyle="calendarStyle"
+      :translation="translation"
+      :isRtl="isRtl"
+      :use-utc="useUtc"
+      @selectYear="selectYear"
+      @changedDecade="setPageDate"
+      :fullMonthName="fullMonthName"
+      :minimumView="minimumView"
+      :maximumView="maximumView"
+      :theme="theme"
+    >
+      <template v-slot:customCalendarHeader>
+        <slot name="customCalendarHeader"></slot>
+      </template>
+    </picker-year>
+  </div>
+</template>
+
+<script>
+import './datepicker.scss';
+import { defineComponent, computed, watch, ref } from 'vue';
+import clickOutside from '../../directives/click-outside';
+import DateInput from './DateInput.vue';
+import PickerDay from './PickerDay.vue';
+import PickerMonth from './PickerMonth.vue';
+import PickerYear from './PickerYear.vue';
+import * as Langlist from './locale/index';
+import { isValidDate, setDate, validateDateInput } from './utils/DateUtils';
+
+export default defineComponent({
+    name: 'Datepicker',
+    components: {
+        DateInput,
+        PickerDay,
+        PickerMonth,
+        PickerYear,
+    },
+    directives: {
+        clickoutside: clickOutside,
+    },
+    props: {
+        modelValue: {
+            type: [Date, String, Number],
+        },
+        value: {
+            type: [Date, String, Number],
+        },
+        format: {
+            type: [String, Function],
+            default: 'dd MMM yyyy',
+        },
+        language: {
+            type: String,
+            default: 'en',
+        },
+        openDate: {
+            validator: (val) => validateDateInput(val),
+            type: Date,
+            default: new Date(),
+        },
+        minimumView: {
+            type: String,
+            default: 'day',
+        },
+        maximumView: {
+            type: String,
+            default: 'year',
+        },
+        name: {
+            type: String,
+        },
+        id: {
+            type: String,
+        },
+        dayCellContent: {
+            type: Function,
+        },
+        fullMonthName: {
+            type: Boolean,
+        },
+        disabledDates: {
+            type: Object,
+        },
+        highlighted: {
+            type: Object,
+        },
+        placeholder: {
+            type: String,
+        },
+        inline: {
+            type: Boolean,
+        },
+        calendarClass: {
+            type: [String, Object, Array],
+        },
+        inputClass: {
+            type: [String, Object, Array],
+        },
+        wrapperClass: {
+            type: [String, Object, Array],
+        },
+        mondayFirst: {
+            type: Boolean,
+        },
+        clearButton: {
+            type: Boolean,
+        },
+        clearButtonIcon: {
+            type: String,
+        },
+        calendarButton: {
+            type: Boolean,
+        },
+        calendarButtonIcon: {
+            type: String,
+        },
+        calendarButtonIconContent: {
+            type: String,
+        },
+        addBootstrapClass: {
+            type: Boolean,
+        },
+        initialView: {
+            type: String,
+        },
+        disabled: {
+            type: Boolean,
+        },
+        required: {
+            type: Boolean,
+        },
+        typeable: {
+            type: Boolean,
+        },
+        useUtc: {
+            type: Boolean,
+        },
+        hideInput: {
+            type: Boolean,
+            default: true,
+        },
+        preventDisableDateSelection: {
+            type: Boolean,
+            default: true,
+        },
+        iconColor: {
+            default: 'black',
+            type: String,
+        },
+        iconHeight: {
+            default: 16,
+            type: [String, Number],
+        },
+        iconWidth: {
+            default: 16,
+            type: [String, Number],
+        },
+        theme: {
+            default: 'green',
+            type: String,
+        },
+    },
+    emits: [
+        'input',
+        'cleared',
+        'update:modelValue',
+        'closed',
+        'changed-month',
+        'changed-year',
+        'changed-day',
+        'selected',
+        'selected-disabled',
+    ],
+    setup(props, { emit }) {
+        const initmodelvalue = new Date(props.modelValue);
+        const pageTimestamp = ref(0);
+        const selectedDate = ref(null);
+        if (props.modelValue && isValidDate(initmodelvalue)) {
+            pageTimestamp.value = initmodelvalue.getTime();
+            selectedDate.value = initmodelvalue;
+        }
+        if (props.openDate) {
+            pageTimestamp.value = setDate(new Date(props.openDate), 1);
+        }
+        const showDayView = ref(false);
+        const showMonthView = ref(false);
+        const showYearView = ref(false);
+        const calendarHeight = ref(0);
+        const resetTypedDate = ref(new Date());
+        /** ********************************** Computed  *********************************** */
+        const computedInitialView = computed(() => {
+            if (!props.initialView) {
+                return props.minimumView;
+            }
+            return props.initialView;
+        });
+        const pageDate = computed(() => {
+            return new Date(pageTimestamp.value);
+        });
+        const translation = computed(() => {
+            const temp = Langlist.data;
+            return temp[props.language];
+        });
+        const isInline = computed(() => {
+            return !!props.inline;
+        });
+        const calendarStyle = computed(() => {
+            return {
+                position: isInline.value ? 'static' : undefined,
+            };
+        });
+        const isOpen = computed(() => {
+            return showDayView.value || showMonthView.value || showYearView.value;
+        });
+        const isRtl = computed(() => {
+            return translation.value && translation.value.rtl === true;
+        });
+        /** ********************************** Methods  *********************************** */
+        /**
+         * Sets the date that the calendar should open on
+         */
+        function setPageDate(date) {
+            if (!date) {
+                if (props.openDate) {
+                    date = new Date(props.openDate);
+                }
+                else {
+                    date = new Date();
+                }
+            }
+            pageTimestamp.value = setDate(new Date(date), 1);
+        }
+        /**
+         * Are we allowed to show a specific picker view?
+         * @param {String} view
+         * @return {Boolean}
+         */
+        function allowedToShowView(view) {
+            const views = ['day', 'month', 'year'];
+            const minimumViewIndex = views.indexOf(props.minimumView);
+            const maximumViewIndex = views.indexOf(props.maximumView);
+            const viewIndex = views.indexOf(view);
+            return viewIndex >= minimumViewIndex && viewIndex <= maximumViewIndex;
+        }
+        /**
+         * Close all calendar layers
+         * @param {Boolean} emitEvent - emit close event
+         */
+        function close(emitEvent) {
+            showDayView.value = false;
+            showMonthView.value = false;
+            showYearView.value = false;
+            if (!isInline.value) {
+                if (emitEvent) {
+                    emit('closed');
+                }
+            }
+        }
+        /**
+         * Show the day picker
+         * @return {Boolean}
+         */
+        function showDayCalendar() {
+            if (!allowedToShowView('day')) {
+                return false;
+            }
+            close();
+            showDayView.value = true;
+            return true;
+        }
+        /**
+         * Show the month picker
+         * @return {Boolean}
+         */
+        function showMonthCalendar() {
+            if (!allowedToShowView('month')) {
+                return false;
+            }
+            close();
+            showMonthView.value = true;
+            return true;
+        }
+        /**
+         * Show the year picker
+         * @return {Boolean}
+         */
+        function showYearCalendar() {
+            if (!allowedToShowView('year')) {
+                return false;
+            }
+            close();
+            showYearView.value = true;
+            return true;
+        }
+        /**
+         * Sets the initial picker page view: day, month or year
+         */
+        function setInitialView() {
+            const initialView = computedInitialView.value;
+            if (!allowedToShowView(initialView)) {
+                throw new Error(`initialView '${initialView}' cannot be rendered based on minimum '${props.minimumView}' and maximum '${props.maximumView}'`);
+            }
+            switch (initialView) {
+                case 'year':
+                    showYearCalendar();
+                    break;
+                case 'month':
+                    showMonthCalendar();
+                    break;
+                default:
+                    showDayCalendar();
+                    break;
+            }
+        }
+        /**
+         * Effectively a toggle to show/hide the calendar
+         * @return {mixed}
+         */
+        function showCalendar() {
+            if (props.disabled || isInline.value) {
+                return false;
+            }
+            if (isOpen.value) {
+                return close(true);
+            }
+            setInitialView();
+            return true;
+        }
+        /**
+         * Set the selected date
+         * @param {Number} timestamp
+         */
+        function setDate1(timestamp) {
+            const date = new Date(timestamp);
+            selectedDate.value = date;
+            setPageDate(date);
+            emit('selected', date);
+            if (props.modelValue) {
+                emit('update:modelValue', date);
+            }
+            else {
+                emit('input', date);
+            }
+        }
+        /**
+         * Clear the selected date
+         */
+        function clearDate() {
+            selectedDate.value = null;
+            setPageDate();
+            emit('selected', null);
+            if (props.modelValue) {
+                emit('update:modelValue', null);
+            }
+            else {
+                emit('input', null);
+            }
+            emit('cleared');
+        }
+        /**
+         * @param {Object} date
+         */
+        function selectDate(date) {
+            setDate1(date.timestamp);
+            if (!isInline.value) {
+                close(true);
+            }
+            resetTypedDate.value = new Date();
+        }
+        /**
+         * @param {Object} date
+         */
+        function selectDisabledDate(date) {
+            emit('selected-disabled', date);
+        }
+        /**
+         * @param {Object} month
+         */
+        function selectMonth(month) {
+            const date = new Date(month.timestamp);
+            if (allowedToShowView('day')) {
+                setPageDate(date);
+                showDayCalendar();
+            }
+            else {
+                selectDate(month);
+            }
+            emit('changed-month', month);
+        }
+        /**
+         * @param {Object} year
+         */
+        function selectYear(year) {
+            const date = new Date(year.timestamp);
+            if (allowedToShowView('month')) {
+                setPageDate(date);
+                showMonthCalendar();
+            }
+            else {
+                selectDate(year);
+            }
+            emit('changed-year', year);
+        }
+        /**
+         * Set the datepicker value
+         * @param {Date|String|Number|null} date
+         */
+        function setValue(date) {
+            let tempDate = date;
+            if (typeof date === 'string' || typeof date === 'number') {
+                const parsed = new Date(date);
+                tempDate = Number.isNaN(parsed.valueOf()) ? '' : parsed;
+            }
+            if (!tempDate) {
+                setPageDate();
+                selectedDate.value = null;
+                return;
+            }
+            selectedDate.value = tempDate;
+            setPageDate(date);
+        }
+        /**
+         * Handles a month change from the day picker
+         */
+        function handleChangedMonthFromDayPicker(date) {
+            setPageDate(date);
+            emit('changed-month', date);
+        }
+        /**
+         * Set the date from a typedDate event
+         */
+        function setTypedDate(date) {
+            setDate1(date.getTime());
+        }
+        /**
+         * Initiate the component
+         */
+        function init() {
+            if (props.value) {
+                setValue(props.value);
+            }
+            if (isInline.value) {
+                setInitialView();
+            }
+        }
+        /**
+         * Click Outside handler
+         */
+        function closeOnClickOutside() {
+            close();
+        }
+        /** ********************************** Watchers  *********************************** */
+        watch(() => props.modelValue, (curr) => {
+            setValue(curr);
+        });
+        watch(() => props.value, (curr) => {
+            setValue(curr);
+        });
+        watch(() => props.openDate, () => {
+            setPageDate();
+        });
+        watch(() => props.initialView, () => {
+            setInitialView();
+        });
+        init();
+        return {
+            pageTimestamp,
+            selectedDate,
+            showDayView,
+            showMonthView,
+            showYearView,
+            calendarHeight,
+            resetTypedDate,
+            // computed
+            pageDate,
+            translation,
+            calendarStyle,
+            isOpen,
+            isInline,
+            isRtl,
+            // methods
+            setTypedDate,
+            handleChangedMonthFromDayPicker,
+            selectYear,
+            selectMonth,
+            selectDisabledDate,
+            clearDate,
+            showCalendar,
+            close,
+            allowedToShowView,
+            showYearCalendar,
+            showMonthCalendar,
+            setPageDate,
+            selectDate,
+            closeOnClickOutside,
+            showDayCalendar,
+            computedInitialView,
+            setDate,
+            setDate1,
+            setValue,
+        };
+    },
+});
+</script>

+ 495 - 0
src/components/datepicker/PickerDay.vue

@@ -0,0 +1,495 @@
+<template>
+  <div
+    :class="['vuejs3-datepicker__calendar', `vuejs3-${theme}`, calendarClass]"
+    v-show="showDayView"
+    :style="calendarStyle"
+    @mousedown.prevent
+  >
+    <slot name="customCalendarHeader"></slot>
+    <section v-if="ifDifferentViews && selectedDate" class="vuejs3-datepicker__calendar-topbar">
+      <p class="vuejs3-datepicker__calendar-topbar-year" @click="showYearCalendar">{{ currYearName }}</p>
+      <p class="vuejs3-datepicker__calendar-topbar-day">{{ getDayName }} {{ getDisplayDate }} {{ monthName }}</p>
+    </section>
+    <div class="vuejs3-datepicker__calendar-actionarea">
+      <header>
+        <span @click="isRtl ? nextMonth() : previousMonth()" class="prev" :class="{ disabled: isLeftNavDisabled }"
+          >&lt;</span
+        >
+        <span class="day__month_btn" @click="showMonthCalendar" :class="allowedToShowView('month') ? 'up' : ''"
+          >{{ isYmd ? currYearName : currMonthName }} {{ isYmd ? currMonthName : currYearName }}</span
+        >
+        <span @click="isRtl ? previousMonth() : nextMonth()" class="next" :class="{ disabled: isRightNavDisabled }"
+          >&gt;</span
+        >
+      </header>
+      <div :class="isRtl ? 'flex-rtl' : ''">
+        <span class="cell day-header" v-for="d in daysOfWeek" :key="d.timestamp">{{ d }}</span>
+        <template v-if="blankDays > 0">
+          <span class="cell day blank" v-for="d in blankDays" :key="d.timestamp"></span>
+        </template>
+        <span
+          class="cell day"
+          v-for="day in days"
+          :key="day.timestamp"
+          :class="dayClasses(day)"
+          v-html="dayCellContent(day)"
+          @click="selectDate(day)"
+        ></span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { defineComponent, computed, PropType } from 'vue';
+import {
+  getDay,
+  daysInMonth,
+  setDate,
+  getMonthNameAbbr,
+  compareDates,
+  getFullYear,
+  getMonth,
+  getDate,
+  setMonth,
+  getMonthName,
+  getDayNameAbbr,
+  stringToDate,
+} from './utils/DateUtils';
+
+export default defineComponent({
+    name: 'PickerDay',
+    props: {
+        showDayView: {
+            type: Boolean,
+        },
+        selectedDate: {
+            type: [String, Date],
+            default: new Date(),
+        },
+        pageDate: {
+            type: Date,
+            default: new Date(),
+        },
+        pageTimestamp: {
+            type: Number,
+        },
+        fullMonthName: {
+            type: Boolean,
+        },
+        allowedToShowView: {
+            type: Function,
+        },
+        dayCellContent: {
+            type: Function,
+            default: (day) => day.date,
+        },
+        disabledDates: {
+            type: Object,
+        },
+        highlighted: {
+            type: Object,
+        },
+        calendarClass: {
+            type: [String, Object, Array],
+        },
+        calendarStyle: {
+            type: Object,
+        },
+        translation: {
+            type: Object,
+        },
+        isRtl: {
+            type: Boolean,
+        },
+        mondayFirst: {
+            type: Boolean,
+        },
+        useUtc: {
+            type: Boolean,
+        },
+        minimumView: {
+            type: String,
+            default: 'day',
+        },
+        maximumView: {
+            type: String,
+            default: 'year',
+        },
+        preventDisableDateSelection: {
+            type: Boolean,
+            default: true,
+        },
+        theme: {
+            default: 'green',
+            type: String,
+        },
+    },
+    emits: ['show-year-calendar', 'changed-month', 'show-month-calendar', 'selected-disabled', 'select-date'],
+    setup(props, { emit }) {
+        /** ********************************** Methods *********************************** */
+        /**
+         * Whether a day is highlighted and it is the first date
+         * in the highlighted range of dates
+         * @param {string | Date}
+         */
+        function selectDate(date) {
+            if (date.isDisabled) {
+                emit('selected-disabled', date);
+                if (!props.preventDisableDateSelection) {
+                    emit('select-date', date);
+                }
+            }
+            else {
+                emit('select-date', date);
+            }
+        }
+        /**
+         * Emit an event to show the month picker
+         */
+        function showMonthCalendar() {
+            emit('show-month-calendar');
+        }
+        /**
+         * Emit an event to show the year picker
+         */
+        function showYearCalendar() {
+            emit('show-year-calendar');
+        }
+        /**
+         * Change the page month
+         * @param {Number} incrementBy
+         */
+        function changeMonth(incrementBy) {
+            const date = props.pageDate;
+            setMonth(date, getMonth(date) + incrementBy);
+            emit('changed-month', date);
+        }
+        /**
+         * Is the previous month disabled?
+         * @return {Boolean}
+         */
+        function isPreviousMonthDisabled() {
+            const d = props.disabledDates;
+            if (!d || !d.to) {
+                return false;
+            }
+            const t = props.pageDate;
+            return getMonth(d.to) >= getMonth(t) && getFullYear(d.to) >= getFullYear(t);
+        }
+        /**
+         * Decrement the page month
+         */
+        function previousMonth() {
+            if (!isPreviousMonthDisabled()) {
+                changeMonth(-1);
+            }
+        }
+        /**
+         * Is the next month disabled?
+         * @return {Boolean}
+         */
+        function isNextMonthDisabled() {
+            const d = props.disabledDates;
+            if (!d || !d.from) {
+                return false;
+            }
+            const t = props.pageDate;
+            return getMonth(d.from) <= getMonth(t) && getFullYear(d.from) <= getFullYear(t);
+        }
+        /**
+         * Increment the current page month
+         */
+        function nextMonth() {
+            if (!isNextMonthDisabled()) {
+                changeMonth(+1);
+            }
+        }
+        /**
+         * Whether a day is selected
+         * @param {Date}
+         * @return {Boolean}
+         */
+        function isSelectedDate(dObj) {
+            const propDate = stringToDate(props.selectedDate);
+            return props.selectedDate ? compareDates(propDate, dObj) : false;
+        }
+        /**
+         * Whether a date is disabled
+         * @return {Boolean}
+         */
+        function isDisabledDate(date) {
+            let disabledDates = false;
+            const t = props.disabledDates;
+            if (!t)
+                return disabledDates;
+            if (typeof t === 'undefined') {
+                return false;
+            }
+            if (typeof t.dates !== 'undefined') {
+                t.dates.forEach((d) => {
+                    const isEqual = compareDates(date, d);
+                    if (isEqual) {
+                        disabledDates = true;
+                        // return true;
+                    }
+                });
+            }
+            if (typeof t.to !== 'undefined' && t.to && date < t.to) {
+                disabledDates = true;
+            }
+            if (typeof t.from !== 'undefined' && t.from && date > t.from) {
+                disabledDates = true;
+            }
+            if (typeof t.ranges !== 'undefined') {
+                t.ranges.forEach((range) => {
+                    if (typeof range.from !== 'undefined' && range.from && typeof range.to !== 'undefined' && range.to) {
+                        if (date < range.to && date > range.from) {
+                            disabledDates = true;
+                            // return true;
+                        }
+                    }
+                });
+            }
+            if (typeof t.days !== 'undefined' && t.days.indexOf(getDay(date)) !== -1) {
+                disabledDates = true;
+            }
+            if (typeof t.daysOfMonth !== 'undefined' && t.daysOfMonth.indexOf(getDate(date)) !== -1) {
+                disabledDates = true;
+            }
+            if (typeof t.customPredictor === 'function' && t.customPredictor(date)) {
+                disabledDates = true;
+            }
+            return disabledDates;
+        }
+        /**
+         * Helper
+         * @param  {mixed}  prop
+         * @return {Boolean}
+         */
+        function isDefined(prop) {
+            return typeof prop !== 'undefined' && prop;
+        }
+        /**
+         * Whether a date is highlighted or not
+         * @return {Boolean}
+         */
+        function isHighlightedDate(date) {
+            const h = props.highlighted;
+            if (!(h && h.includeDisabled) && isDisabledDate(date)) {
+                return false;
+            }
+            let highlighted = false;
+            if (typeof h === 'undefined') {
+                return false;
+            }
+            if (typeof h.dates !== 'undefined') {
+                h.dates.forEach((d) => {
+                    if (compareDates(date, d)) {
+                        highlighted = true;
+                        // return true;
+                    }
+                });
+            }
+            if (isDefined(h.from) && isDefined(h.to)) {
+                highlighted = date >= h.from && date <= h.to;
+            }
+            if (typeof h.days !== 'undefined' && h.days.indexOf(getDay(date)) !== -1) {
+                highlighted = true;
+            }
+            if (typeof h.daysOfMonth !== 'undefined' && h.daysOfMonth.indexOf(getDate(date)) !== -1) {
+                highlighted = true;
+            }
+            if (typeof h.customPredictor === 'function' && h.customPredictor(date)) {
+                highlighted = true;
+            }
+            return highlighted;
+        }
+        /**
+         * Returns Css Classes for a day element
+         */
+        function dayClasses(day) {
+            return {
+                selected: day.isSelected,
+                disabled: day.isDisabled,
+                highlighted: day.isHighlighted,
+                today: day.isToday,
+                weekend: day.isWeekend,
+                sat: day.isSaturday,
+                sun: day.isSunday,
+                'highlight-start': day.isHighlightStart,
+                'highlight-end': day.isHighlightEnd,
+            };
+        }
+        /**
+         * Whether a day is highlighted and it is the first date
+         * in the highlighted range of dates
+         * @param {Date}
+         * @return {Boolean}
+         */
+        function isHighlightStart(date) {
+            const h = props.highlighted;
+            if (!h)
+                return false;
+            return (isHighlightedDate(date) &&
+                h.from instanceof Date &&
+                getFullYear(h.from) === getFullYear(date) &&
+                getMonth(h.from) === getMonth(date) &&
+                getDate(h.from) === getDate(date));
+        }
+        /**
+         * Whether a day is highlighted and it is the first date
+         * in the highlighted range of dates
+         * @param {Date}
+         * @return {Boolean}
+         */
+        function isHighlightEnd(date) {
+            const h = props.highlighted;
+            if (!h)
+                return false;
+            return (isHighlightedDate(date) &&
+                h.to instanceof Date &&
+                getFullYear(h.to) === getFullYear(date) &&
+                getMonth(h.to) === getMonth(date) &&
+                getDate(h.to) === getDate(date));
+        }
+        /** ********************************** Computed  *********************************** */
+        /**
+         * Returns an array of day names
+         * @return {String[]}
+         */
+        const daysOfWeek = computed(() => {
+            if (props.mondayFirst) {
+                const tempDays = props.translation && props.translation.days && props.translation.days.slice();
+                tempDays.push(tempDays.shift());
+                return tempDays;
+            }
+            return props.translation && props.translation.days;
+        });
+        const blankDays = computed(() => {
+            const d = props.pageDate;
+            const dObj = props.useUtc
+                ? new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), 1))
+                : new Date(d.getFullYear(), d.getMonth(), 1, d.getHours(), d.getMinutes());
+            if (props.mondayFirst) {
+                return getDay(dObj) > 0 ? getDay(dObj) - 1 : 6;
+            }
+            return getDay(dObj);
+        });
+        /**
+         * @return {Object[]}
+         */
+        const days = computed(() => {
+            const d = props.pageDate;
+            const tdays = [];
+            // set up a new date object to the beginning of the current 'page'
+            const dObj = props.useUtc
+                ? new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), 1))
+                : new Date(d.getFullYear(), d.getMonth(), 1, d.getHours(), d.getMinutes());
+            const t = daysInMonth(getFullYear(dObj), getMonth(dObj));
+            for (let i = 0; i < t; i += 1) {
+                tdays.push({
+                    date: getDate(dObj),
+                    timestamp: dObj.getTime(),
+                    isSelected: isSelectedDate(dObj),
+                    isDisabled: isDisabledDate(dObj),
+                    isHighlighted: isHighlightedDate(dObj),
+                    isHighlightStart: isHighlightStart(dObj),
+                    isHighlightEnd: isHighlightEnd(dObj),
+                    isToday: compareDates(dObj, new Date()),
+                    isWeekend: getDay(dObj) === 0 || getDay(dObj) === 6,
+                    isSaturday: getDay(dObj) === 6,
+                    isSunday: getDay(dObj) === 0,
+                });
+                setDate(dObj, getDate(dObj) + 1);
+            }
+            return tdays;
+        });
+        /**
+         * Gets the name of the month the current page is on
+         * @return {String}
+         */
+        const currMonthName = computed(() => {
+            const monthName = props.fullMonthName
+                ? props.translation && props.translation.months
+                : props.translation && props.translation.monthsAbbr;
+            return getMonthNameAbbr(getMonth(props.pageDate), monthName);
+        });
+        /**
+         * Gets the name of the month the current page is on
+         * @return {String}
+         */
+        const monthName = computed(() => {
+            const tempName = props.translation && props.translation.months;
+            return getMonthName(getMonth(props.pageDate), tempName);
+        });
+        /**
+         * Gets the name of the year that current page is on
+         * @return {Number}
+         */
+        const currYearName = computed(() => {
+            const yearSuffix = props.translation && props.translation.yearSuffix;
+            return `${getFullYear(props.pageDate)}${yearSuffix}`;
+        });
+        /**
+         * Is this translation using year/month/day format?
+         * @return {Boolean}
+         */
+        const isYmd = computed(() => {
+            return (props.translation && props.translation.ymd && props.translation && props.translation.ymd) === true;
+        });
+        /**
+         * Is the left hand navigation button disabled?
+         * @return {Boolean}
+         */
+        const isLeftNavDisabled = computed(() => {
+            return props.isRtl ? isNextMonthDisabled() : isPreviousMonthDisabled();
+        });
+        /**
+         * Is the right hand navigation button disabled?
+         * @return {Boolean}
+         */
+        const isRightNavDisabled = computed(() => {
+            return props.isRtl ? isPreviousMonthDisabled() : isNextMonthDisabled();
+        });
+        const getDayName = computed(() => {
+            const propDate = stringToDate(props.selectedDate);
+            return props.selectedDate ? getDayNameAbbr(propDate, props.translation && props.translation.daysNames) : null;
+        });
+        const getDisplayDate = computed(() => {
+            const propDate = stringToDate(props.selectedDate);
+            return props.selectedDate ? getDate(propDate) : null;
+        });
+        const ifDifferentViews = computed(() => {
+            return !(props.minimumView === props.maximumView && (props.minimumView !== 'day' || props.maximumView !== 'day'));
+        });
+        return {
+            isDefined,
+            showMonthCalendar,
+            daysOfWeek,
+            blankDays,
+            isYmd,
+            days,
+            currMonthName,
+            currYearName,
+            isLeftNavDisabled,
+            isRightNavDisabled,
+            selectDate,
+            previousMonth,
+            nextMonth,
+            dayClasses,
+            monthName,
+            getDayName,
+            getDisplayDate,
+            showYearCalendar,
+            isNextMonthDisabled,
+            ifDifferentViews,
+            isSelectedDate,
+            isDisabledDate,
+            isHighlightedDate,
+            isHighlightStart,
+            isHighlightEnd,
+        };
+    },
+});
+</script>

+ 308 - 0
src/components/datepicker/PickerMonth.vue

@@ -0,0 +1,308 @@
+<template>
+  <div
+    :class="['vuejs3-datepicker__calendar', `vuejs3-${theme}`, calendarClass]"
+    v-show="showMonthView"
+    :style="calendarStyle"
+    @mousedown.prevent
+  >
+    <slot name="customCalendarHeader"></slot>
+    <section v-if="ifDifferentViews" class="vuejs3-datepicker__calendar-topbar">
+      <p class="vuejs3-datepicker__calendar-topbar-year" @click="showYearCalendar">{{ currYearName }}</p>
+      <p class="vuejs3-datepicker__calendar-topbar-day" v-if="selectedDate">
+        {{ getDayName }} {{ getDisplayDate }} {{ monthName }}
+      </p>
+    </section>
+    <div class="vuejs3-datepicker__calendar-actionarea">
+      <header>
+        <span @click="isRtl ? nextYear() : previousYear()" class="prev" :class="{ disabled: isLeftNavDisabled }"
+          >&lt;</span
+        >
+        <span class="month__year_btn" @click="showYearCalendar" :class="allowedToShowView('year') ? 'up' : ''">{{
+          pageYearName
+        }}</span>
+        <span @click="isRtl ? previousYear() : nextYear()" class="next" :class="{ disabled: isRightNavDisabled }"
+          >&gt;</span
+        >
+      </header>
+      <span
+        class="cell month"
+        v-for="month in months"
+        :key="month.timestamp"
+        :class="{ selected: month.isSelected, disabled: month.isDisabled }"
+        @click.stop="selectMonth(month)"
+        >{{ month.month }}</span
+      >
+    </div>
+  </div>
+</template>
+
+<script>
+import { defineComponent, computed, PropType } from 'vue';
+import {
+  getFullYear,
+  getMonth,
+  setMonth,
+  setFullYear,
+  getMonthName,
+  getDate,
+  getMonthNameAbbr,
+  getDayNameAbbr,
+  stringToDate,
+} from './utils/DateUtils';
+
+export default defineComponent({
+    name: 'PickerMonth',
+    props: {
+        showMonthView: {
+            type: Boolean,
+        },
+        selectedDate: {
+            type: [String, Date],
+            default: new Date(),
+        },
+        pageDate: {
+            type: Date,
+            default: new Date(),
+        },
+        pageTimestamp: {
+            type: Number,
+        },
+        disabledDates: {
+            type: Object,
+        },
+        calendarClass: {
+            type: [String, Object, Array],
+        },
+        calendarStyle: {
+            type: Object,
+        },
+        translation: {
+            type: Object,
+        },
+        isRtl: {
+            type: Boolean,
+        },
+        allowedToShowView: {
+            type: Function,
+        },
+        useUtc: {
+            type: Boolean,
+        },
+        fullMonthName: {
+            type: Boolean,
+        },
+        minimumView: {
+            type: String,
+            default: 'day',
+        },
+        maximumView: {
+            type: String,
+            default: 'year',
+        },
+        theme: {
+            default: 'green',
+            type: String,
+        },
+    },
+    setup(props, { emit }) {
+        /** ********************************** Methods  *********************************** */
+        /**
+         * Emits a selectMonth event
+         * @param {Object} month
+         */
+        function selectMonth(month) {
+            if (!month.isDisabled) {
+                emit('select-month', month);
+            }
+        }
+        /**
+         * Changes the year up or down
+         * @param {Number} incrementBy
+         */
+        function changeYear(incrementBy) {
+            const date = props.pageDate;
+            setFullYear(date, getFullYear(date) + incrementBy);
+            emit('changed-year', date);
+        }
+        /**
+         * Checks if the previous year is disabled or not
+         * @return {Boolean}
+         */
+        function isPreviousYearDisabled() {
+            const d = props.disabledDates;
+            if (!d || !d.to) {
+                return false;
+            }
+            return getFullYear(d.to) >= getFullYear(props.pageDate);
+        }
+        /**
+         * Decrements the year
+         */
+        function previousYear() {
+            if (!isPreviousYearDisabled()) {
+                changeYear(-1);
+            }
+        }
+        /**
+         * Checks if the next year is disabled or not
+         * @return {Boolean}
+         */
+        function isNextYearDisabled() {
+            const d = props.disabledDates;
+            if (!d || !d.from) {
+                return false;
+            }
+            return getFullYear(d.from) <= getFullYear(props.pageDate);
+        }
+        /**
+         * Increments the year
+         */
+        function nextYear() {
+            if (!isNextYearDisabled()) {
+                changeYear(1);
+            }
+        }
+        /**
+         * Emits an event that shows the year calendar
+         */
+        function showYearCalendar() {
+            emit('show-year-calendar');
+        }
+        /**
+         * Whether the selected date is in this month
+         * @param {Date}
+         * @return {Boolean}
+         */
+        function isSelectedMonth(date) {
+            const d = stringToDate(props.selectedDate);
+            return d && getFullYear(d) === getFullYear(date) && getMonth(d) === getMonth(date);
+        }
+        /**
+         * Whether a month is disabled
+         * @param {Date}
+         * @return {Boolean}
+         */
+        function isDisabledMonth(date) {
+            let disabledDates = false;
+            const d = props.disabledDates;
+            if (!d)
+                return false;
+            if (typeof d === 'undefined') {
+                return false;
+            }
+            if (typeof d.to !== 'undefined' && d.to) {
+                if ((getMonth(date) < getMonth(d.to) && getFullYear(date) <= getFullYear(d.to)) ||
+                    getFullYear(date) < getFullYear(d.to)) {
+                    disabledDates = true;
+                }
+            }
+            if (typeof d.from !== 'undefined' && d.from) {
+                if ((getMonth(date) > getMonth(d.from) && getFullYear(date) >= getFullYear(d.from)) ||
+                    getFullYear(date) > getFullYear(d.from)) {
+                    disabledDates = true;
+                }
+            }
+            if (typeof d.customPredictor === 'function' && d.customPredictor(date)) {
+                disabledDates = true;
+            }
+            return disabledDates;
+        }
+        /** ********************************** Computed  *********************************** */
+        const months = computed(() => {
+            const d = props.pageDate;
+            const tmonths = [];
+            // set up a new date object to the beginning of the current 'page'
+            const dObj = props.useUtc
+                ? new Date(Date.UTC(d.getUTCFullYear(), 0, d.getUTCDate()))
+                : new Date(d.getFullYear(), 0, d.getDate(), d.getHours(), d.getMinutes());
+            for (let i = 0; i < 12; i += 1) {
+                tmonths.push({
+                    month: getMonthName(i, props.translation && props.translation.months),
+                    timestamp: dObj.getTime(),
+                    isSelected: isSelectedMonth(dObj),
+                    isDisabled: isDisabledMonth(dObj),
+                });
+                setMonth(dObj, getMonth(dObj) + 1);
+            }
+            return tmonths;
+        });
+        /**
+         * Get year name on current page.
+         * @return {String}
+         */
+        const pageYearName = computed(() => {
+            const yearSuffix = props.translation && props.translation.yearSuffix;
+            return `${getFullYear(props.pageDate)}${yearSuffix}`;
+        });
+        /**
+         * Is the left hand navigation disabled
+         * @return {Boolean}
+         */
+        const isLeftNavDisabled = computed(() => {
+            return props.isRtl ? isNextYearDisabled() : isPreviousYearDisabled();
+        });
+        /**
+         * Is the right hand navigation disabled
+         * @return {Boolean}
+         */
+        const isRightNavDisabled = computed(() => {
+            return props.isRtl ? isPreviousYearDisabled() : isNextYearDisabled();
+        });
+        /**
+         * Gets the name of the month the current page is on
+         * @return {String}
+         */
+        const monthName = computed(() => {
+            const tempName = props.translation && props.translation.months;
+            return getMonthName(getMonth(props.pageDate), tempName);
+        });
+        const getDisplayDate = computed(() => {
+            const propDate = stringToDate(props.selectedDate);
+            return props.selectedDate ? getDate(propDate) : null;
+        });
+        const getDayName = computed(() => {
+            const propDate = stringToDate(props.selectedDate);
+            return props.selectedDate ? getDayNameAbbr(propDate, props.translation && props.translation.daysNames) : null;
+        });
+        /**
+         * Gets the name of the year that current page is on
+         * @return {Number}
+         */
+        const currYearName = computed(() => {
+            const yearSuffix = props.translation && props.translation.yearSuffix;
+            return `${getFullYear(props.pageDate)}${yearSuffix}`;
+        });
+        /**
+         * Gets the name of the month the current page is on
+         * @return {String}
+         */
+        const currMonthName = computed(() => {
+            const tempmonthName = props.fullMonthName
+                ? props.translation && props.translation.months
+                : props.translation && props.translation.monthsAbbr;
+            return getMonthNameAbbr(getMonth(props.pageDate), tempmonthName);
+        });
+        const ifDifferentViews = computed(() => {
+            return !(props.minimumView === props.maximumView && (props.minimumView !== 'day' || props.maximumView !== 'day'));
+        });
+        return {
+            isRightNavDisabled,
+            isLeftNavDisabled,
+            pageYearName,
+            months,
+            selectMonth,
+            previousYear,
+            nextYear,
+            currYearName,
+            getDisplayDate,
+            monthName,
+            showYearCalendar,
+            getDayName,
+            currMonthName,
+            ifDifferentViews,
+            isSelectedMonth,
+            isDisabledMonth,
+        };
+    },
+});
+</script>

+ 301 - 0
src/components/datepicker/PickerYear.vue

@@ -0,0 +1,301 @@
+<template>
+  <div
+    :class="['vuejs3-datepicker__calendar', `vuejs3-${theme}`, calendarClass]"
+    v-show="showYearView"
+    :style="calendarStyle"
+    @mousedown.prevent
+  >
+    <slot name="customCalendarHeader"></slot>
+    <section v-if="ifDifferentViews && selectedDate" class="vuejs3-datepicker__calendar-topbar">
+      <p class="vuejs3-datepicker__calendar-topbar-year">{{ currYearName }}</p>
+      <p class="vuejs3-datepicker__calendar-topbar-day" v-if="selectedDate">
+        {{ getDayName }} {{ getDisplayDate }} {{ monthName }}
+      </p>
+    </section>
+    <div class="vuejs3-datepicker__calendar-actionarea">
+      <header>
+        <span @click="isRtl ? nextDecade() : previousDecade()" class="prev" :class="{ disabled: isLeftNavDisabled }"
+          >&lt;</span
+        >
+        <span>{{ getPageDecade }}</span>
+        <span @click="isRtl ? previousDecade() : nextDecade()" class="next" :class="{ disabled: isRightNavDisabled }"
+          >&gt;</span
+        >
+      </header>
+      <span
+        class="cell year"
+        v-for="year in years"
+        :key="year.timestamp"
+        :class="{ selected: year.isSelected, disabled: year.isDisabled }"
+        @click.stop="selectYear(year)"
+        >{{ year.year }}</span
+      >
+    </div>
+  </div>
+</template>
+<script>
+import { defineComponent, computed, PropType } from 'vue';
+import {
+  getDate,
+  getDayNameAbbr,
+  getFullYear,
+  getMonth,
+  getMonthName,
+  getMonthNameAbbr,
+  setFullYear,
+  stringToDate,
+} from './utils/DateUtils';
+
+export default defineComponent({
+    name: 'PickerYear',
+    props: {
+        showYearView: {
+            type: Boolean,
+        },
+        selectedDate: {
+            type: [String, Date],
+            default: new Date(),
+        },
+        pageDate: {
+            type: Date,
+            default: new Date(),
+        },
+        pageTimestamp: {
+            type: Number,
+        },
+        disabledDates: {
+            type: Object,
+        },
+        highlighted: {
+            type: Object,
+        },
+        calendarClass: {
+            type: [String, Object, Array],
+        },
+        calendarStyle: {
+            type: Object,
+        },
+        translation: {
+            type: Object,
+        },
+        isRtl: {
+            type: Boolean,
+        },
+        allowedToShowView: {
+            type: Function,
+        },
+        useUtc: {
+            type: Boolean,
+        },
+        fullMonthName: {
+            type: Boolean,
+        },
+        minimumView: {
+            type: String,
+            default: 'day',
+        },
+        maximumView: {
+            type: String,
+            default: 'year',
+        },
+        theme: {
+            default: 'green',
+            type: String,
+        },
+    },
+    emits: ['select-year', 'changed-decade'],
+    setup(props, { emit }) {
+        /** ********************************** Methods  *********************************** */
+        /**
+         * Select year
+         * @param {year}
+         */
+        function selectYear(year) {
+            if (!year.isDisabled) {
+                emit('select-year', year);
+            }
+        }
+        /**
+         * Change year (increment / decrement)
+         * @param {number}
+         */
+        function changeYear(incrementBy) {
+            const date = props.pageDate;
+            setFullYear(date, getFullYear(date) + incrementBy);
+            emit('changed-decade', date);
+        }
+        /**
+         * checks if previous decade is disabled
+         */
+        function isPreviousDecadeDisabled() {
+            const d = props.disabledDates;
+            if (!d || !d.to) {
+                return false;
+            }
+            const disabledYear = getFullYear(d.to);
+            const lastYearInPreviousPage = Math.floor(getFullYear(props.pageDate) / 10) * 10 - 1;
+            return disabledYear > lastYearInPreviousPage;
+        }
+        /**
+         * changes year to previous decade
+         */
+        function previousDecade() {
+            if (!isPreviousDecadeDisabled()) {
+                changeYear(-10);
+            }
+        }
+        /**
+         * check if next decade is disabled
+         */
+        function isNextDecadeDisabled() {
+            const d = props.disabledDates;
+            if (!d || !d.from) {
+                return false;
+            }
+            const disabledYear = getFullYear(d.from);
+            const firstYearInNextPage = Math.ceil(getFullYear(props.pageDate) / 10) * 10;
+            return disabledYear <= firstYearInNextPage;
+        }
+        /**
+         * moves year to next decade
+         */
+        function nextDecade() {
+            if (!isNextDecadeDisabled()) {
+                changeYear(10);
+            }
+        }
+        /**
+         * Whether the selected date is in this year
+         * @param {Date}
+         * @return {Boolean}
+         */
+        function isSelectedYear(date) {
+            const propDate = stringToDate(props.selectedDate);
+            return props.selectedDate ? getFullYear(propDate) === getFullYear(date) : false;
+        }
+        /**
+         * Whether a year is disabled
+         * @param {Date}
+         * @return {Boolean}
+         */
+        function isDisabledYear(date) {
+            let disabledDates = false;
+            if (typeof props.disabledDates === 'undefined' || !props.disabledDates) {
+                return false;
+            }
+            if (typeof props.disabledDates.to !== 'undefined' && props.disabledDates.to) {
+                if (getFullYear(date) < getFullYear(props.disabledDates.to)) {
+                    disabledDates = true;
+                }
+            }
+            if (typeof props.disabledDates.from !== 'undefined' && props.disabledDates.from) {
+                if (getFullYear(date) > getFullYear(props.disabledDates.from)) {
+                    disabledDates = true;
+                }
+            }
+            if (typeof props.disabledDates.customPredictor === 'function') {
+                disabledDates = props.disabledDates.customPredictor(date);
+            }
+            return disabledDates;
+        }
+        /** ********************************** Computed  *********************************** */
+        const years = computed(() => {
+            const d = props.pageDate;
+            const tyears = [];
+            // set up a new date object to the beginning of the current 'page'7
+            const dObj = props.useUtc
+                ? new Date(Date.UTC(Math.floor(d.getUTCFullYear() / 10) * 10, d.getUTCMonth(), d.getUTCDate()))
+                : new Date(Math.floor(d.getFullYear() / 10) * 10, d.getMonth(), d.getDate(), d.getHours(), d.getMinutes());
+            for (let i = 0; i < 10; i += 1) {
+                tyears.push({
+                    year: getFullYear(dObj),
+                    timestamp: dObj.getTime(),
+                    isSelected: isSelectedYear(dObj),
+                    isDisabled: isDisabledYear(dObj),
+                });
+                setFullYear(dObj, getFullYear(dObj) + 1);
+            }
+            return tyears;
+        });
+        /**
+         * @return {String}
+         */
+        const getPageDecade = computed(() => {
+            const decadeStart = Math.floor(getFullYear(props.pageDate) / 10) * 10;
+            const decadeEnd = decadeStart + 9;
+            const yearSuffix = props.translation && props.translation.yearSuffix;
+            return `${decadeStart} - ${decadeEnd}${yearSuffix}`;
+        });
+        /**
+         * Is the left hand navigation button disabled?
+         * @return {Boolean}
+         */
+        const isLeftNavDisabled = computed(() => {
+            return props.isRtl ? isNextDecadeDisabled() : isPreviousDecadeDisabled();
+        });
+        /**
+         * Is the right hand navigation button disabled?
+         * @return {Boolean}
+         */
+        const isRightNavDisabled = computed(() => {
+            return props.isRtl ? isPreviousDecadeDisabled() : isNextDecadeDisabled();
+        });
+        const getDayName = computed(() => {
+            const propDate = stringToDate(props.selectedDate);
+            return props.selectedDate ? getDayNameAbbr(propDate, props.translation && props.translation.daysNames) : null;
+        });
+        /**
+         * Gets the name of the month the current page is on
+         * @return {String}
+         */
+        const monthName = computed(() => {
+            const tempName = props.translation && props.translation.months;
+            return getMonthName(getMonth(props.pageDate), tempName);
+        });
+        const getDisplayDate = computed(() => {
+            const propDate = stringToDate(props.selectedDate);
+            return props.selectedDate ? getDate(propDate) : null;
+        });
+        /**
+         * Gets the name of the year that current page is on
+         * @return {Number}
+         */
+        const currYearName = computed(() => {
+            const yearSuffix = props.translation && props.translation.yearSuffix;
+            return `${getFullYear(props.pageDate)}${yearSuffix}`;
+        });
+        /**
+         * Gets the name of the month the current page is on
+         * @return {String}
+         */
+        const currMonthName = computed(() => {
+            const tempmonthName = props.fullMonthName
+                ? props.translation && props.translation.months
+                : props.translation && props.translation.monthsAbbr;
+            return getMonthNameAbbr(getMonth(props.pageDate), tempmonthName);
+        });
+        const ifDifferentViews = computed(() => {
+            return !(props.minimumView === props.maximumView && (props.minimumView !== 'day' || props.maximumView !== 'day'));
+        });
+        return {
+            isRightNavDisabled,
+            isLeftNavDisabled,
+            getPageDecade,
+            years,
+            nextDecade,
+            previousDecade,
+            selectYear,
+            getDayName,
+            monthName,
+            getDisplayDate,
+            currYearName,
+            currMonthName,
+            ifDifferentViews,
+            // methods
+            isNextDecadeDisabled,
+            isPreviousDecadeDisabled,
+            isDisabledYear,
+        };
+    },
+});
+</script>

+ 218 - 0
src/components/datepicker/datepicker.scss

@@ -0,0 +1,218 @@
+@import "~@/assets/css/_settings.scss";
+
+.rtl {
+  direction: rtl;
+}
+
+.vuejs3-datepicker {
+  position: relative;
+  display: inline-block;
+  * {
+    box-sizing: border-box;
+  }
+  input {
+    border: 1px solid;
+  }
+  &__icon {
+    display: flex;
+  }
+  &__value {
+    min-width: 200px;
+    display: inline-flex;
+    border-radius: 5px;
+    padding: 13px 15px;
+    cursor: pointer;
+    border: 1px solid ;
+  }
+  &__content{
+    margin-left: 10px;
+    font-size: 15px;
+  }
+
+  &__typeablecalendar{
+    position: absolute;
+    top: 10px;
+    left: 10px;
+  }
+  &__inputvalue{
+    min-width: 200px;
+    display: inline-flex;
+    border-radius: 5px;
+    padding: 12px 10px 13px 35px;
+    cursor: pointer;
+    border: 1px solid ;
+  }
+  &__calendar {
+    position: absolute;
+    z-index: 100;
+    background: $white;
+    width: 300px;
+    box-shadow: 0 0.2rem 1rem rgba(0,0,0,.12);
+    border-radius: 4px;
+    margin-top: 4px;
+    &-topbar{
+        background-color: $green;
+        color: $white;
+        border-radius: 4px 4px 0px 0px;
+        padding: 25px;
+        &-year{
+            font-size: 30px;
+            margin: 0px;
+            padding-bottom: 10px;
+        }
+        &-day{
+            font-size: 20px;
+            margin: 0px;
+        }
+    }
+    &-actionarea{
+        padding: 10px;
+    }
+     header {
+      display: block;
+      line-height: 40px;
+      span {
+        text-align: center;
+        width: 71.42857142857143%;
+        float: left;
+      }
+      .next, .prev {
+        width: 14.285714285714286%;
+        float: left;
+        text-indent: -10000px;
+        position: relative;
+        &:after {
+          content: '';
+          position: absolute;
+          left: 50%;
+          width: 8px;
+          height: 8px;
+          top: 50%;
+          transform: translateX(-50%) translateY(-50%) rotate(45deg);
+        }
+      }
+      .prev:after {
+        border-left: 1px solid $black;
+        border-bottom: 1px solid $black;
+      }
+      .prev.disabled:after,  .next.disabled:after{
+        opacity: 0.5;
+      }
+      .next:after {
+        border-top: 1px solid $black;
+        border-right: 1px solid $black;
+      }
+      .prev:not(.disabled), .next:not(.disabled), .up:not(.disabled){
+        cursor: pointer;
+        font-size: 15px;
+        border-radius: 4px;
+        &:hover{
+          background: $hovered;
+        }
+      }
+    }
+    .disabled {
+      color: #ddd;
+      cursor: default;
+    }
+    .flex-rtl {
+      display: flex;
+      width: inherit;
+      flex-wrap: wrap;
+    }
+    .cell {
+      display: inline-block;
+      padding: 0 5px;
+      width: 14.285714285714286%;
+      height: 40px;
+      line-height: 40px;
+      text-align: center;
+      font-size: 14px;
+      vertical-align: middle;
+      border: 1px solid transparent;
+      &.month, &.year{
+        padding: 10px 5px;
+        height: 50px;
+        line-height: 28px;
+      }
+      &.day-header{
+        text-transform: uppercase;
+      }
+    }
+    .cell:not(.blank):not(.disabled).day, .cell:not(.blank):not(.disabled).month, .cell:not(.blank):not(.disabled).year{
+      cursor: pointer;
+      transition: 0.45s;
+      &:hover{
+        border: 1px solid $green;
+      }
+    }
+    .cell.selected {
+      background: $green;
+      color: $white;
+      &:hover{
+        background: $green;
+      }
+    }
+    .cell.highlighted {
+      background: $green;
+      color: $white;
+      &.selected {
+        background: $green;
+      }
+      &.disabled {
+        color: #a3a3a3;
+      }
+      &.highlight-start, &:last-child {
+        background: $darkGreen;
+      }
+    }
+    .cell.grey {
+      color: #888;
+      &:hover{
+        background: inherit;
+      }
+    }
+    .cell.day-header {
+      font-size: 75%;
+      white-space: nowrap;
+      cursor: inherit;
+      &:hover{
+        background: inherit;
+      }
+    }
+    .month, .year{
+      width: 33.333%;
+    }
+  }
+  &__clear-button, &__calendar-button{
+    cursor: pointer;
+    font-style: normal;
+    &.disabled {
+      color: #999;
+      cursor: default;
+    }
+  }
+  #calendar-div{
+    background-color: white;
+    border-radius:5px;
+  }
+}
+
+.dp-error {
+  color: red;
+  font-size: 12px
+}
+
+.backdrop{
+  position: fixed; /* Sit on top of the page content */
+  display: none; /* Hidden by default */
+  width: 100%; /* Full width (cover the whole page) */
+  height: 100%; /* Full height (cover the whole page) */
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background-color: rgba(0,0,0,0.5); /* Black background with opacity */
+  z-index: 2; /* Specify a stack order in case you're using a different order for other elements */
+  cursor: pointer; /* Add a pointer on hover */
+}

+ 437 - 0
src/components/datepicker/locale/index.js

@@ -0,0 +1,437 @@
+const af = () => {
+  const langName = 'Afrikaans';
+  const monthFullName = [
+      'Januarie',
+      'Februarie',
+      'Maart',
+      'April',
+      'Mei',
+      'Junie',
+      'Julie',
+      'Augustus',
+      'September',
+      'Oktober',
+      'November',
+      'Desember',
+  ];
+  const shortName = ['Jan', 'Feb', 'Mrt', 'Apr', 'Mei', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'];
+  const days = ['So.', 'Ma.', 'Di.', 'Wo.', 'Do.', 'Vr.', 'Sa.'];
+  const rtl = false;
+  const ymd = false;
+  const yearSuffix = '';
+  return {
+      months: monthFullName,
+      monthsAbbr: shortName,
+      days,
+      yearSuffix,
+      ymd,
+      rtl,
+      language: langName,
+      langName,
+      daysNames: days,
+  };
+};
+const de = () => {
+  const langName = 'German';
+  const monthFullName = [
+      'Januar',
+      'Februar',
+      'März',
+      'April',
+      'Mai',
+      'Juni',
+      'Juli',
+      'August',
+      'September',
+      'Oktober',
+      'November',
+      'Dezember',
+  ];
+  const shortName = ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'];
+  const days = ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'];
+  const daysNames = ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'];
+  const rtl = false;
+  const ymd = false;
+  const yearSuffix = '';
+  return {
+      months: monthFullName,
+      monthsAbbr: shortName,
+      days,
+      language: langName,
+      yearSuffix,
+      ymd,
+      rtl,
+      langName,
+      daysNames,
+  };
+};
+const en = () => {
+  const langName = 'English';
+  const monthFullName = [
+      'January',
+      'February',
+      'March',
+      'April',
+      'May',
+      'June',
+      'July',
+      'August',
+      'September',
+      'October',
+      'November',
+      'December',
+  ];
+  const shortName = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+  const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
+  const daysNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
+  const rtl = false;
+  const ymd = false;
+  const yearSuffix = '';
+  return {
+      months: monthFullName,
+      monthsAbbr: shortName,
+      days,
+      language: langName,
+      yearSuffix,
+      ymd,
+      rtl,
+      langName,
+      daysNames,
+  };
+};
+const fr = () => {
+  const langName = 'Français';
+  const monthFullName = [
+      'Janvier',
+      'Février',
+      'Mars',
+      'Avril',
+      'Mai',
+      'Juin',
+      'Juillet',
+      'Août',
+      'Septembre',
+      'Octobre',
+      'Novembre',
+      'Décembre',
+  ];
+  const shortName = ['Jan', 'Fév', 'Mar', 'Avr', 'Mai', 'Jui', 'Juil', 'Aoû', 'Sep', 'Oct', 'Nov', 'Déc'];
+  const days = ['Dim', 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam'];
+  const daysNames = ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi'];
+  const rtl = false;
+  const ymd = false;
+  const yearSuffix = '';
+  return {
+      months: monthFullName,
+      monthsAbbr: shortName,
+      days,
+      language: langName,
+      yearSuffix,
+      ymd,
+      rtl,
+      langName,
+      daysNames,
+  };
+};
+const hi = () => {
+  const langName = 'Hindi';
+  const monthFullName = [
+      'जनवरी',
+      'फ़रवरी',
+      'मार्च',
+      'अप्रैल',
+      'मई',
+      'जून',
+      'जुलाई',
+      'अगस्त',
+      'सितंबर',
+      'अक्टूबर',
+      'नवंबर',
+      'दिसंबर',
+  ];
+  const shortName = ['जन', 'फ़र', 'मार्च', 'अप्रै', 'मई', 'जून', 'जुला', 'अगस्त', 'सितं', 'अक्टू', 'नवं', 'दिसं'];
+  const days = ['रवि', 'सोम', 'मंगल', 'बुध', 'गुरु', 'शुक्र', 'शनि'];
+  const daysNames = ['रविवार', 'सोमवार', 'मंगलवार', 'बुधवार', 'गुरुवार', 'शुक्रवार', 'शनिवार'];
+  const rtl = false;
+  const ymd = false;
+  const yearSuffix = '';
+  return {
+      months: monthFullName,
+      monthsAbbr: shortName,
+      days,
+      language: langName,
+      yearSuffix,
+      ymd,
+      rtl,
+      langName,
+      daysNames,
+  };
+};
+const ja = () => {
+  const langName = 'Japanese';
+  const monthFullName = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'];
+  const shortName = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'];
+  const days = ['日', '月', '火', '水', '木', '金', '土'];
+  const daysNames = ['日曜日', '月曜日', '火曜日', '水曜日', '木曜日', '金曜日', '土曜日'];
+  const rtl = false;
+  const ymd = false;
+  const yearSuffix = '';
+  return {
+      months: monthFullName,
+      monthsAbbr: shortName,
+      days,
+      language: langName,
+      yearSuffix,
+      ymd,
+      rtl,
+      langName,
+      daysNames,
+  };
+};
+const nl = () => {
+  const langName = 'Dutch';
+  const monthFullName = [
+      'januari',
+      'februari',
+      'maart',
+      'april',
+      'mei',
+      'juni',
+      'juli',
+      'augustus',
+      'september',
+      'oktober',
+      'november',
+      'december',
+  ];
+  const shortName = ['jan.', 'feb.', 'mrt.', 'apr.', 'mei', 'juni', 'juli', 'aug.', 'sep.', 'okt.', 'nov.', 'dec.'];
+  const days = ['zo.', 'ma.', 'di.', 'wo.', 'do.', 'vr.', 'za.'];
+  const rtl = false;
+  const ymd = false;
+  const yearSuffix = '';
+  return {
+      months: monthFullName,
+      monthsAbbr: shortName,
+      days,
+      yearSuffix,
+      ymd,
+      rtl,
+      langName,
+      language: langName,
+      daysNames: days,
+  };
+};
+const pt = () => {
+  const langName = 'Português';
+  const monthFullName = [
+      'Janeiro',
+      'Fevereiro',
+      'Março',
+      'Abril',
+      'Maio',
+      'Junho',
+      'Julho',
+      'Agosto',
+      'Setembro',
+      'Outubro',
+      'Novembro',
+      'Dezembro',
+  ];
+  const shortName = ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'];
+  const days = ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sab'];
+  const daysNames = [
+      'Domingo',
+      'Segunda-Feira',
+      'Terça-Feira',
+      'Quarta-Feira',
+      'Quinta-Feira',
+      'Sexta-Feira',
+      'Sábado',
+  ];
+  const rtl = false;
+  const ymd = false;
+  const yearSuffix = '';
+  return {
+      months: monthFullName,
+      monthsAbbr: shortName,
+      days,
+      language: langName,
+      yearSuffix,
+      ymd,
+      rtl,
+      langName,
+      daysNames,
+  };
+};
+const it = () => {
+  const langName = 'Italian';
+  const monthFullName = [
+      'Gennaio',
+      'Febbraio',
+      'Marzo',
+      'Aprile',
+      'Maggio',
+      'Giugno',
+      'Luglio',
+      'Agosto',
+      'Settembre',
+      'Ottobre',
+      'Novembre',
+      'Dicembre',
+  ];
+  const shortName = ['Gen', 'Feb', 'Mar', 'Apr', 'Mag', 'Giu', 'Lug', 'Ago', 'Set', 'Ott', 'Nov', 'Dic'];
+  const days = ['Dom', 'Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab'];
+  const daysNames = ['Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Gioved', 'Venerdì', 'Sabato'];
+  const rtl = false;
+  const ymd = false;
+  const yearSuffix = '';
+  return {
+      months: monthFullName,
+      monthsAbbr: shortName,
+      days,
+      language: langName,
+      yearSuffix,
+      ymd,
+      rtl,
+      langName,
+      daysNames,
+  };
+};
+const pl = () => {
+  const langName = 'Polish';
+  const monthFullName = [
+      'Styczeń',
+      'Luty',
+      'Marzec',
+      'Kwiecień',
+      'Maj',
+      'Czerwiec',
+      'Lipiec',
+      'Sierpień',
+      'Wrzesień',
+      'Październik',
+      'Listopad',
+      'Grudzień',
+  ];
+  const shortName = ['Sty', 'Lut', 'Mar', 'Kwi', 'Maj', 'Cze', 'Lip', 'Sie', 'Wrz', 'Paź', 'Lis', 'Gru'];
+  const days = ['Nd', 'Pn', 'Wt', 'Śr', 'Cz', 'Pt', 'So'];
+  const daysNames = ['Niedziela', 'Poniedziałek', 'Wtorek', 'Środa', 'Czwartek', 'Piątek', 'Sobota'];
+  const rtl = false;
+  const ymd = false;
+  const yearSuffix = '';
+  return {
+      months: monthFullName,
+      monthsAbbr: shortName,
+      days,
+      language: langName,
+      yearSuffix,
+      ymd,
+      rtl,
+      langName,
+      daysNames,
+  };
+};
+const ru = () => {
+  const langName = 'Russian';
+  const monthFullName = [
+      'Январь',
+      'Февраль',
+      'Март',
+      'Апрель',
+      'Май',
+      'Июнь',
+      'Июль',
+      'Август',
+      'Сентябрь',
+      'Октябрь',
+      'Ноябрь',
+      'Декабрь',
+  ];
+  const shortName = ['Янв.', 'Фев.', 'Мар.', 'Апр.', 'Май', 'Июн.', 'Июл.', 'Авг.', 'Сен.', 'Окт.', 'Ноя.', 'Дек.'];
+  const days = ['Вс', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'];
+  const daysNames = ['Воскресенье', 'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота'];
+  const rtl = false;
+  const ymd = false;
+  const yearSuffix = '';
+  return {
+      months: monthFullName,
+      monthsAbbr: shortName,
+      days,
+      language: langName,
+      yearSuffix,
+      ymd,
+      rtl,
+      langName,
+      daysNames,
+  };
+};
+export const tr = () => {
+  const langName = 'Türkçe';
+  const monthFullName = [
+      'Ocak',
+      'Şubat',
+      'Mart',
+      'Nisan',
+      'Mayıs',
+      'Haziran',
+      'Temmuz',
+      'Ağustos',
+      'Eylül',
+      'Ekim',
+      'Kasım',
+      'Aralık',
+  ];
+  const shortName = ['Oca', 'Şub', 'Mar', 'Nis', ' May', 'Haz', 'Tem', 'Ağu', 'Eyl', 'Eki', 'Kas', 'Ara'];
+  const days = ['Pzt', 'Sal', 'Çar', 'Per', 'Cum', 'Cmt', 'Pzr'];
+  const daysNames = ['Pazartesi', 'Salı', 'Çarşamba', 'Perşembe', 'Cuma', 'Cumartesi', 'Pazar'];
+  const rtl = false;
+  const ymd = false;
+  const yearSuffix = '';
+  return {
+      months: monthFullName,
+      monthsAbbr: shortName,
+      days,
+      language: langName,
+      yearSuffix,
+      ymd,
+      rtl,
+      langName,
+      daysNames,
+  };
+};
+const zh = () => {
+  const langName = '中文'
+  const monthFullName = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月']
+  const shortName = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月']
+  const days = ['日', '一', '二', '三', '四', '五', '六']
+  const daysNames = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
+  const rtl = false
+  const ymd = false
+  const yearSuffix = ''
+  return {
+      months: monthFullName,
+      monthsAbbr: shortName,
+      days,
+      language: langName,
+      yearSuffix,
+      ymd,
+      rtl,
+      langName,
+      daysNames
+  }
+}
+export const data = {
+  zh: zh(),
+  af: af(),
+  hi: hi(),
+  ja: ja(),
+  de: de(),
+  en: en(),
+  fr: fr(),
+  nl: nl(),
+  pt: pt(),
+  it: it(),
+  pl: pl(),
+  ru: ru(),
+  tr: tr(),
+};

+ 240 - 0
src/components/datepicker/utils/DateUtils.js

@@ -0,0 +1,240 @@
+// import en from '@/components/datepicker/locale/translations/en';
+/**
+ * Returns the full year, using UTC or not
+ * @param {Date} date
+ */
+ export const getFullYear = (date, useUtc = false) => {
+  return useUtc ? date.getUTCFullYear() : date.getFullYear();
+};
+/**
+* Returns the month, using UTC or not
+* @param {Date} date
+*/
+export const getMonth = (date, useUtc = false) => {
+  return useUtc ? date.getUTCMonth() : date.getMonth();
+};
+/**
+* Returns the date, using UTC or not
+* @param {Date} date
+*/
+export const getDate = (date, useUtc = false) => {
+  return useUtc ? date.getUTCDate() : date.getDate();
+};
+/**
+* Returns the day, using UTC or not
+* @param {Date} date
+*/
+export const getDay = (date, useUtc = false) => {
+  return useUtc ? date.getUTCDay() : date.getDay();
+};
+/**
+* Returns the hours, using UTC or not
+* @param {Date} date
+*/
+export const getHours = (date, useUtc = false) => {
+  return useUtc ? date.getUTCHours() : date.getHours();
+};
+/**
+* Returns the minutes, using UTC or not
+* @param {Date} date
+*/
+export const getMinutes = (date, useUtc = false) => {
+  return useUtc ? date.getUTCMinutes() : date.getMinutes();
+};
+/**
+* Sets the full year, using UTC or not
+* @param {Date} date
+*/
+export const setFullYear = (date, value, useUtc = false) => {
+  return useUtc ? date.setUTCFullYear(value) : date.setFullYear(value);
+};
+/**
+* Sets the month, using UTC or not
+* @param {Date} date
+*/
+export const setMonth = (date, value, useUtc = false) => {
+  return useUtc ? date.setUTCMonth(value) : date.setMonth(value);
+};
+/**
+* Sets the date, using UTC or not
+* @param {Date} date
+* @param {Number} value
+*/
+export const setDate = (date, value, useUtc = false) => {
+  return useUtc ? date.setUTCDate(value) : date.setDate(value);
+};
+/**
+* Check if date1 is equivalent to date2, without comparing the time
+* @see https://stackoverflow.com/a/6202196/4455925
+* @param {Date} date1
+* @param {Date} date2
+*/
+export const compareDates = (date1, date2, useUtc = false) => {
+  const d1 = new Date(date1.getTime());
+  const d2 = new Date(date2.getTime());
+  if (useUtc) {
+      d1.setUTCHours(0, 0, 0, 0);
+      d2.setUTCHours(0, 0, 0, 0);
+  }
+  else {
+      d1.setHours(0, 0, 0, 0);
+      d2.setHours(0, 0, 0, 0);
+  }
+  return d1.getTime() === d2.getTime();
+};
+/**
+* Validates a date object
+* @param {Date} date - an object instantiated with the new Date constructor
+* @return {Boolean}
+*/
+export const isValidDate = (date) => {
+  if (Object.prototype.toString.call(date) !== '[object Date]') {
+      return false;
+  }
+  return !Number.isNaN(date.getTime());
+};
+/**
+* Return abbreviated week day name
+* @param {Date}
+* @param {Array}
+* @return {String}
+*/
+export const getDayNameAbbr = (date, days) => {
+  if (typeof date !== 'object') {
+      throw TypeError('Invalid Type');
+  }
+  return days[getDay(date)];
+};
+/**
+* Return name of the month
+* @param {Number|Date}
+* @param {Array}
+* @return {String}
+*/
+export const getMonthName = (month, months) => {
+  if (!months) {
+      throw Error('missing 2nd parameter Months array');
+  }
+  if (typeof month === 'object') {
+      return months[getMonth(month)];
+  }
+  if (typeof month === 'number') {
+      return months[month];
+  }
+  throw TypeError('Invalid type');
+};
+/**
+* Return an abbreviated version of the month
+* @param {Number|Date}
+* @return {String}
+*/
+export const getMonthNameAbbr = (month, monthsAbbr) => {
+  if (!monthsAbbr) {
+      throw Error('missing 2nd paramter Months array');
+  }
+  if (typeof month === 'object') {
+      return monthsAbbr[getMonth(month)];
+  }
+  if (typeof month === 'number') {
+      return monthsAbbr[month];
+  }
+  throw TypeError('Invalid type');
+};
+/**
+* Alternative get total number of days in month
+* @param {Number} year
+* @param {Number} m
+* @return {Number}
+*/
+export const daysInMonth = (year, month) => {
+  if (/8|3|5|10/.test(month)) {
+      return 30;
+  }
+  if (month === 1) {
+      if ((!(year % 4) && year % 100) || !(year % 400)) {
+          return 29;
+      }
+      return 28;
+  }
+  return 31;
+  // return /8|3|5|10/.test(month as string)
+  //   ? 30
+  //   : month === 1
+  //   ? (!(year % 4) && year % 100) || !(year % 400)
+  //     ? 29
+  //     : 28
+  //   : 31;
+};
+/**
+* Get nth suffix for date
+* @param {Number} day
+* @return {String}
+*/
+export const getNthSuffix = (day) => {
+  switch (day) {
+      case 1:
+      case 21:
+      case 31:
+          return 'st';
+      case 2:
+      case 22:
+          return 'nd';
+      case 3:
+      case 23:
+          return 'rd';
+      default:
+          return 'th';
+  }
+};
+/**
+* Formats date object
+* @param {Date}
+* @param {String}
+* @param {Object}
+* @return {String}
+*/
+export const formatDate = (date, format, translation) => {
+  const year = getFullYear(date);
+  const month = getMonth(date) + 1;
+  const day = getDate(date);
+  const str = format
+      .replace(/dd/, `0${day}`.slice(-2))
+      .replace(/d/, day)
+      .replace(/yyyy/, year)
+      .replace(/yy/, String(year).slice(2))
+      .replace(/MMMM/, getMonthName(getMonth(date), translation.months))
+      .replace(/MMM/, getMonthNameAbbr(getMonth(date), translation.monthsAbbr))
+      .replace(/MM/, `0${month}`.slice(-2))
+      .replace(/M(?!a|ä|e)/, month.toString())
+      .replace(/su/, getNthSuffix(getDate(date)))
+      .replace(/D(?!e|é|i)/, getDayNameAbbr(date, translation.days));
+  return str;
+};
+/**
+* Creates an array of dates for each day in between two dates.
+* @param {Date} start
+* @param {Date} end
+* @return {Array}
+*/
+export const createDateArray = (start, end) => {
+  const dates = [];
+  while (start <= end) {
+      dates.push(new Date(start));
+      start = setDate(new Date(start), getDate(new Date(start)) + 1);
+  }
+  return dates;
+};
+/**
+* method used as a prop validator for input values
+* @param {*} val
+* @return {Boolean}
+*/
+export const validateDateInput = (val) => {
+  return val === null || val instanceof Date || typeof val === 'string' || typeof val === 'number';
+};
+export const stringToDate = (value) => {
+  if (typeof value === 'string') {
+      return new Date(value);
+  }
+  return value;
+};

ファイルの差分が大きいため隠しています
+ 51 - 0
src/components/iconview/IconView.vue


+ 58 - 0
src/directives/click-outside.js

@@ -0,0 +1,58 @@
+/* eslint-disable */
+// https://github.com/simplesmiler/vue-clickaway
+// https://github.com/ndelvalle/v-click-outside/blob/master/lib/v-click-outside.js
+// Mixed both :)
+const EVENTS = ['click'];
+const instances = [];
+const ClickOutside = {
+    instances,
+    beforeMount: bind,
+    update: (el, binding) => {
+        if (JSON.stringify(binding.value) === JSON.stringify(binding.oldValue))
+            return;
+        bind(el, binding);
+    },
+    unmounted: unbind,
+};
+function bind(el, { value }) {
+    unbind(el);
+    const bindingValue = value;
+    const isFunction = typeof bindingValue === 'function';
+    const isObject = typeof bindingValue === 'object';
+    if (!isFunction && !isObject)
+        return;
+    const isActive = !(bindingValue.isActive === false);
+    if (!isActive)
+        return;
+    const handler = isFunction ? bindingValue : bindingValue.handler;
+    const instance = createInstance({ el, handler });
+    instance.eventHandlers.forEach(({ event, handler }) => setTimeout(() => document.addEventListener(event, handler, false), 0));
+    instances.push(instance);
+}
+function unbind(el) {
+    const instanceIndex = instances.findIndex((instance) => instance.el === el);
+    if (instanceIndex === -1)
+        return;
+    const instance = instances[instanceIndex];
+    instance.eventHandlers.forEach(({ event, handler }) => document.removeEventListener(event, handler, false));
+    instances.splice(instanceIndex, 1);
+}
+// --------------------
+// Helpers
+// --------------------
+function createInstance({ el, handler }) {
+    return {
+        el,
+        eventHandlers: EVENTS.map((eventName) => ({
+            event: eventName,
+            handler: (event) => onEvent({ event, el, handler }),
+        })),
+    };
+}
+function onEvent({ event, el, handler }) {
+    const path = event.path || (event.composedPath ? event.composedPath() : undefined);
+    if (path ? path.indexOf(el) < 0 : !el.contains(event.target)) {
+        return handler && handler(event, el);
+    }
+}
+export default ClickOutside;

+ 2 - 2
src/pages/LaserBim.vue

@@ -34,7 +34,7 @@
             <div class="popup" v-if="source && datepickName">
                 <div>
                     <span @click="onPickClose"><i class="iconfont icon-close"></i></span>
-                    <datepicker :inline="true" :value="datepickValue" :highlighted="highlighted" @selected="onSelected"></datepicker>
+                    <datepicker  language="zh"  :inline="true" :value="datepickValue" :highlighted="highlighted" @selected="onSelected"></datepicker>
                 </div>
             </div>
         </main>
@@ -42,7 +42,7 @@
 </template>
 
 <script setup>
-import Datepicker from 'vuejs3-datepicker'
+import Datepicker from '@/components/datepicker/Datepicker'
 import { ref, onActivated, onDeactivated, reactive, onMounted, computed } from 'vue'
 import ConvertViews from '@/utils/ConvertViews'
  

+ 41 - 4
yarn.lock

@@ -6105,7 +6105,7 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
   resolved "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
   integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
 
-pify@^2.0.0:
+pify@^2.0.0, pify@^2.3.0:
   version "2.3.0"
   resolved "https://registry.npmmirror.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
   integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==
@@ -6223,6 +6223,15 @@ postcss-discard-overridden@^4.0.1:
   dependencies:
     postcss "^7.0.0"
 
+postcss-import@^15.0.0:
+  version "15.0.0"
+  resolved "https://registry.npmmirror.com/postcss-import/-/postcss-import-15.0.0.tgz#0b66c25fdd9c0d19576e63c803cf39e4bad08822"
+  integrity sha512-Y20shPQ07RitgBGv2zvkEAu9bqvrD77C9axhj/aA1BQj4czape2MdClCExvB27EwYEJdGgKZBpKanb0t1rK2Kg==
+  dependencies:
+    postcss-value-parser "^4.0.0"
+    read-cache "^1.0.0"
+    resolve "^1.1.7"
+
 postcss-load-config@^2.0.0:
   version "2.1.2"
   resolved "https://registry.npmmirror.com/postcss-load-config/-/postcss-load-config-2.1.2.tgz#c5ea504f2c4aef33c7359a34de3573772ad7502a"
@@ -6336,6 +6345,13 @@ postcss-modules-values@^3.0.0:
     icss-utils "^4.0.0"
     postcss "^7.0.6"
 
+postcss-nested@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.npmmirror.com/postcss-nested/-/postcss-nested-6.0.0.tgz#1572f1984736578f360cffc7eb7dca69e30d1735"
+  integrity sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==
+  dependencies:
+    postcss-selector-parser "^6.0.10"
+
 postcss-normalize-charset@^4.0.1:
   version "4.0.1"
   resolved "https://registry.npmmirror.com/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz#8b35add3aee83a136b0471e0d59be58a50285dd4"
@@ -6455,7 +6471,7 @@ postcss-selector-parser@^3.0.0:
     indexes-of "^1.0.1"
     uniq "^1.0.1"
 
-postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2:
+postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.2:
   version "6.0.10"
   resolved "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d"
   integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==
@@ -6463,6 +6479,11 @@ postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2:
     cssesc "^3.0.0"
     util-deprecate "^1.0.2"
 
+postcss-simple-vars@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.npmmirror.com/postcss-simple-vars/-/postcss-simple-vars-7.0.0.tgz#f1d10a979762aa4e7e3450a35a32885112893d1b"
+  integrity sha512-SPSkKQK7mKjD/tqcTbZkDi3KP+C/cTGXnKQmSt3AisJtnZE6ZxHEUoOGRfpV0B5dW1Y36EETfRHx10WLHpXThA==
+
 postcss-svgo@^4.0.3:
   version "4.0.3"
   resolved "https://registry.npmmirror.com/postcss-svgo/-/postcss-svgo-4.0.3.tgz#343a2cdbac9505d416243d496f724f38894c941e"
@@ -6486,7 +6507,7 @@ postcss-value-parser@^3.0.0:
   resolved "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281"
   integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==
 
-postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0:
+postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0:
   version "4.2.0"
   resolved "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
   integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
@@ -6508,6 +6529,15 @@ postcss@^8.1.10:
     picocolors "^1.0.0"
     source-map-js "^1.0.2"
 
+postcss@^8.4.17:
+  version "8.4.17"
+  resolved "https://registry.npmmirror.com/postcss/-/postcss-8.4.17.tgz#f87863ec7cd353f81f7ab2dec5d67d861bbb1be5"
+  integrity sha512-UNxNOLQydcOFi41yHNMcKRZ39NeXlr8AxGuZJsdub8vIb12fHzcq37DTU/QtbI6WLxNg2gF9Z+8qtRwTj1UI1Q==
+  dependencies:
+    nanoid "^3.3.4"
+    picocolors "^1.0.0"
+    source-map-js "^1.0.2"
+
 prepend-http@^1.0.0:
   version "1.0.4"
   resolved "https://registry.npmmirror.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
@@ -6691,6 +6721,13 @@ raw-body@2.5.1:
     iconv-lite "0.4.24"
     unpipe "1.0.0"
 
+read-cache@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.npmmirror.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774"
+  integrity sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==
+  dependencies:
+    pify "^2.3.0"
+
 read-pkg@^5.1.1:
   version "5.2.0"
   resolved "https://registry.npmmirror.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc"
@@ -6893,7 +6930,7 @@ resolve-url@^0.2.1:
   resolved "https://registry.npmmirror.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
   integrity sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==
 
-resolve@^1.10.0, resolve@^1.14.2:
+resolve@^1.1.7, resolve@^1.10.0, resolve@^1.14.2:
   version "1.22.1"
   resolved "https://registry.npmmirror.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
   integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==