first commit
This commit is contained in:
commit
eb2f504652
32490 changed files with 5731109 additions and 0 deletions
509
node_modules/vue3-datepicker/src/datepicker/Datepicker.vue
generated
vendored
Normal file
509
node_modules/vue3-datepicker/src/datepicker/Datepicker.vue
generated
vendored
Normal file
|
|
@ -0,0 +1,509 @@
|
|||
<template>
|
||||
<div
|
||||
class="v3dp__datepicker"
|
||||
:style="variables($attrs.style as Record<string, string> | undefined)"
|
||||
>
|
||||
<div class="v3dp__input_wrapper">
|
||||
<input
|
||||
type="text"
|
||||
ref="inputRef"
|
||||
:readonly="!typeable"
|
||||
v-model="input"
|
||||
v-bind="$attrs"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
:tabindex="disabled ? -1 : 0"
|
||||
@keyup="keyUp"
|
||||
@blur="blur"
|
||||
@focus="focus"
|
||||
@click="click"
|
||||
/>
|
||||
<div class="v3dp__clearable" v-show="clearable && modelValue">
|
||||
<slot name="clear" :onClear="clearModelValue">
|
||||
<i @click="clearModelValue()">x</i>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
<year-picker
|
||||
v-show="viewShown === 'year'"
|
||||
:pageDate="pageDate"
|
||||
@update:pageDate="(v) => updatePageDate('year', v)"
|
||||
:selected="modelValue"
|
||||
:lowerLimit="lowerLimit"
|
||||
:upperLimit="upperLimit"
|
||||
@select="selectYear"
|
||||
/>
|
||||
<month-picker
|
||||
v-show="viewShown === 'month'"
|
||||
:pageDate="pageDate"
|
||||
@update:pageDate="(v) => updatePageDate('month', v)"
|
||||
:selected="modelValue"
|
||||
@select="selectMonth"
|
||||
:lowerLimit="lowerLimit"
|
||||
:upperLimit="upperLimit"
|
||||
:format="monthListFormat"
|
||||
:locale="locale"
|
||||
@back="viewShown = 'year'"
|
||||
/>
|
||||
<day-picker
|
||||
v-show="viewShown === 'day'"
|
||||
:pageDate="pageDate"
|
||||
@update:pageDate="(v) => updatePageDate('day', v)"
|
||||
:selected="modelValue"
|
||||
:weekStartsOn="weekStartsOn"
|
||||
:lowerLimit="lowerLimit"
|
||||
:upperLimit="upperLimit"
|
||||
:headingFormat="dayPickerHeadingFormat"
|
||||
:disabledDates="disabledDates"
|
||||
:locale="locale"
|
||||
:weekdayFormat="weekdayFormat"
|
||||
:allow-outside-interval="allowOutsideInterval"
|
||||
:format="dayFormat"
|
||||
@select="selectDay"
|
||||
@back="viewShown = 'month'"
|
||||
/>
|
||||
<time-picker
|
||||
v-show="viewShown === 'time'"
|
||||
:pageDate="pageDate"
|
||||
:visible="viewShown === 'time'"
|
||||
:selected="modelValue"
|
||||
:disabledTime="disabledTime"
|
||||
@select="selectTime"
|
||||
@back="goBackFromTimepicker"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed, watchEffect, PropType } from 'vue'
|
||||
import { parse, isValid, format, max, min } from 'date-fns'
|
||||
import YearPicker from './YearPicker.vue'
|
||||
import MonthPicker from './MonthPicker.vue'
|
||||
import DayPicker from './DayPicker.vue'
|
||||
import TimePicker from './Timepicker.vue'
|
||||
|
||||
const TIME_RESOLUTIONS = ['time', 'day', 'month', 'year']
|
||||
|
||||
const boundedDate = (
|
||||
lower: Date | undefined,
|
||||
upper: Date | undefined,
|
||||
target: Date | undefined = undefined
|
||||
) => {
|
||||
let date = target || new Date()
|
||||
|
||||
if (lower) date = max([lower, date])
|
||||
if (upper) date = min([upper, date])
|
||||
|
||||
return date
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
YearPicker,
|
||||
MonthPicker,
|
||||
DayPicker,
|
||||
TimePicker,
|
||||
},
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
/**
|
||||
* `v-model` for selected date
|
||||
*/
|
||||
modelValue: {
|
||||
type: Date as PropType<Date>,
|
||||
required: false,
|
||||
},
|
||||
/**
|
||||
* Dates not available for picking
|
||||
*/
|
||||
disabledDates: {
|
||||
type: Object as PropType<{
|
||||
dates?: Date[]
|
||||
predicate?: (currentDate: Date) => boolean
|
||||
}>,
|
||||
required: false,
|
||||
},
|
||||
allowOutsideInterval: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
/**
|
||||
* Time not available for picking
|
||||
*/
|
||||
disabledTime: {
|
||||
type: Object as PropType<{
|
||||
dates?: Date[]
|
||||
predicate?: (currentDate: Date) => boolean
|
||||
}>,
|
||||
required: false,
|
||||
},
|
||||
/**
|
||||
* Upper limit for available dates for picking
|
||||
*/
|
||||
upperLimit: {
|
||||
type: Date as PropType<Date>,
|
||||
required: false,
|
||||
},
|
||||
/**
|
||||
* Lower limit for available dates for picking
|
||||
*/
|
||||
lowerLimit: {
|
||||
type: Date as PropType<Date>,
|
||||
required: false,
|
||||
},
|
||||
/**
|
||||
* View on which the date picker should open. Can be either `year`, `month`, `day` or `time`
|
||||
*/
|
||||
startingView: {
|
||||
type: String as PropType<'year' | 'month' | 'day' | 'time'>,
|
||||
required: false,
|
||||
default: 'day',
|
||||
validate: (v: unknown) =>
|
||||
typeof v === 'string' && TIME_RESOLUTIONS.includes(v),
|
||||
},
|
||||
/**
|
||||
* Date which should be the "center" of the initial view.
|
||||
* When an empty datepicker opens, it focuses on the month/year
|
||||
* that contains this date
|
||||
*/
|
||||
startingViewDate: {
|
||||
type: Date as PropType<Date>,
|
||||
required: false,
|
||||
default: () => new Date(),
|
||||
},
|
||||
/**
|
||||
* `date-fns`-type formatting for a month view heading
|
||||
*/
|
||||
dayPickerHeadingFormat: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'LLLL yyyy',
|
||||
},
|
||||
/**
|
||||
* `date-fns`-type formatting for the month picker view
|
||||
*/
|
||||
monthListFormat: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'LLL',
|
||||
},
|
||||
/**
|
||||
* `date-fns`-type formatting for a line of weekdays on day view
|
||||
*/
|
||||
weekdayFormat: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'EE',
|
||||
},
|
||||
/**
|
||||
* `date-fns`-type formatting for the day picker view
|
||||
*/
|
||||
dayFormat: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'dd',
|
||||
},
|
||||
/**
|
||||
* `date-fns`-type format in which the string in the input should be both
|
||||
* parsed and displayed
|
||||
*/
|
||||
inputFormat: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'yyyy-MM-dd',
|
||||
},
|
||||
/**
|
||||
* [`date-fns` locale object](https://date-fns.org/v2.16.1/docs/I18n#usage).
|
||||
* Used in string formatting (see default `dayPickerHeadingFormat`)
|
||||
*/
|
||||
locale: {
|
||||
type: Object as PropType<Locale>,
|
||||
required: false,
|
||||
},
|
||||
/**
|
||||
* Day on which the week should start.
|
||||
*
|
||||
* Number from 0 to 6, where 0 is Sunday and 6 is Saturday.
|
||||
* Week starts with a Monday (1) by default
|
||||
*/
|
||||
weekStartsOn: {
|
||||
type: Number as PropType<0 | 1 | 2 | 3 | 4 | 5 | 6>,
|
||||
required: false,
|
||||
default: 1,
|
||||
validator: (value: any) => [0, 1, 2, 3, 4, 5, 6].includes(value),
|
||||
},
|
||||
/**
|
||||
* Disables datepicker and prevents it's opening
|
||||
*/
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
/**
|
||||
* Clears selected date
|
||||
*/
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
/*
|
||||
* Allows user to input date manually
|
||||
*/
|
||||
typeable: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
/**
|
||||
* If set, lower-level views won't show
|
||||
*/
|
||||
minimumView: {
|
||||
type: String as PropType<'year' | 'month' | 'day' | 'time'>,
|
||||
required: false,
|
||||
default: 'day',
|
||||
validate: (v: unknown) =>
|
||||
typeof v === 'string' && TIME_RESOLUTIONS.includes(v),
|
||||
},
|
||||
},
|
||||
emits: {
|
||||
'update:modelValue': (value: Date | null | undefined) =>
|
||||
value === null || value === undefined || isValid(value),
|
||||
decadePageChanged: (pageDate: Date) => true,
|
||||
yearPageChanged: (pageDate: Date) => true,
|
||||
monthPageChanged: (pageDate: Date) => true,
|
||||
opened: () => true,
|
||||
closed: () => true,
|
||||
},
|
||||
setup(props, { emit, attrs }) {
|
||||
const viewShown = ref('none' as 'year' | 'month' | 'day' | 'time' | 'none')
|
||||
const pageDate = ref<Date>(props.startingViewDate)
|
||||
const inputRef = ref(null as HTMLInputElement | null)
|
||||
const isFocused = ref(false)
|
||||
|
||||
const input = ref('')
|
||||
watchEffect(() => {
|
||||
const parsed = parse(input.value, props.inputFormat, new Date(), {
|
||||
locale: props.locale,
|
||||
})
|
||||
if (isValid(parsed)) {
|
||||
pageDate.value = parsed
|
||||
}
|
||||
})
|
||||
|
||||
watchEffect(
|
||||
() =>
|
||||
(input.value =
|
||||
props.modelValue && isValid(props.modelValue)
|
||||
? format(props.modelValue, props.inputFormat, {
|
||||
locale: props.locale,
|
||||
})
|
||||
: '')
|
||||
)
|
||||
|
||||
const renderView = (view: typeof viewShown.value = 'none') => {
|
||||
if (!props.disabled) {
|
||||
if (view !== 'none' && viewShown.value === 'none')
|
||||
pageDate.value =
|
||||
props.modelValue ||
|
||||
boundedDate(props.lowerLimit, props.upperLimit, pageDate.value)
|
||||
viewShown.value = view
|
||||
|
||||
if (view !== 'none') {
|
||||
emit('opened')
|
||||
} else {
|
||||
emit('closed')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.disabled) viewShown.value = 'none'
|
||||
})
|
||||
|
||||
const updatePageDate = (
|
||||
view: 'year' | 'month' | 'day',
|
||||
newPageDate: Date
|
||||
) => {
|
||||
// We need to emit "page changed" event and set the page date
|
||||
pageDate.value = newPageDate
|
||||
|
||||
if (view === 'year') emit('decadePageChanged', newPageDate)
|
||||
else if (view === 'month') emit('yearPageChanged', newPageDate)
|
||||
else if (view === 'day') emit('monthPageChanged', newPageDate)
|
||||
}
|
||||
|
||||
const selectYear = (date: Date) => {
|
||||
pageDate.value = date
|
||||
|
||||
if (props.minimumView === 'year') {
|
||||
renderView('none')
|
||||
emit('update:modelValue', date)
|
||||
} else {
|
||||
viewShown.value = 'month'
|
||||
}
|
||||
}
|
||||
const selectMonth = (date: Date) => {
|
||||
pageDate.value = date
|
||||
|
||||
if (props.minimumView === 'month') {
|
||||
renderView('none')
|
||||
emit('update:modelValue', date)
|
||||
} else {
|
||||
viewShown.value = 'day'
|
||||
}
|
||||
}
|
||||
const selectDay = (date: Date) => {
|
||||
pageDate.value = date
|
||||
|
||||
if (props.minimumView === 'day') {
|
||||
renderView('none')
|
||||
emit('update:modelValue', date)
|
||||
} else {
|
||||
viewShown.value = 'time'
|
||||
}
|
||||
}
|
||||
|
||||
const selectTime = (date: Date) => {
|
||||
renderView('none')
|
||||
emit('update:modelValue', date)
|
||||
}
|
||||
|
||||
const clearModelValue = () => {
|
||||
if (props.clearable) {
|
||||
renderView('none')
|
||||
emit('update:modelValue', null)
|
||||
pageDate.value = props.startingViewDate
|
||||
}
|
||||
}
|
||||
|
||||
const click = () => (isFocused.value = true)
|
||||
|
||||
const focus = () => renderView(initialView.value)
|
||||
|
||||
const blur = () => {
|
||||
isFocused.value = false
|
||||
renderView()
|
||||
}
|
||||
|
||||
const keyUp = (event: KeyboardEvent) => {
|
||||
const code = event.keyCode ? event.keyCode : event.which
|
||||
// close calendar if escape or enter are pressed
|
||||
const closeButton = [
|
||||
27, // escape
|
||||
13, // enter
|
||||
].includes(code)
|
||||
|
||||
if (closeButton) {
|
||||
inputRef.value!.blur()
|
||||
}
|
||||
if (props.typeable) {
|
||||
const parsedDate = parse(
|
||||
inputRef.value!.value,
|
||||
props.inputFormat,
|
||||
new Date(),
|
||||
{ locale: props.locale }
|
||||
)
|
||||
|
||||
// If the date is formatted back same way as it was inputted, then we're not disturbing user input
|
||||
if (
|
||||
isValid(parsedDate) &&
|
||||
input.value ===
|
||||
format(parsedDate, props.inputFormat, { locale: props.locale })
|
||||
) {
|
||||
input.value = inputRef.value!.value
|
||||
emit('update:modelValue', parsedDate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const initialView = computed(() => {
|
||||
const startingViewOrder = TIME_RESOLUTIONS.indexOf(props.startingView)
|
||||
const minimumViewOrder = TIME_RESOLUTIONS.indexOf(props.minimumView)
|
||||
|
||||
return startingViewOrder < minimumViewOrder
|
||||
? props.minimumView
|
||||
: props.startingView
|
||||
})
|
||||
|
||||
const variables = (object: Record<string, string> | undefined) =>
|
||||
Object.fromEntries(
|
||||
Object.entries(object ?? {}).filter(([key, _]) => key.startsWith('--'))
|
||||
)
|
||||
|
||||
const goBackFromTimepicker = () =>
|
||||
props.startingView === 'time' && props.minimumView === 'time'
|
||||
? null
|
||||
: (viewShown.value = 'day')
|
||||
|
||||
return {
|
||||
blur,
|
||||
focus,
|
||||
click,
|
||||
input,
|
||||
inputRef,
|
||||
pageDate,
|
||||
renderView,
|
||||
updatePageDate,
|
||||
selectYear,
|
||||
selectMonth,
|
||||
selectDay,
|
||||
selectTime,
|
||||
keyUp,
|
||||
viewShown,
|
||||
goBackFromTimepicker,
|
||||
clearModelValue,
|
||||
initialView,
|
||||
log: (e: any) => console.log(e),
|
||||
variables,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.v3dp__datepicker {
|
||||
--popout-bg-color: var(--vdp-bg-color, #fff);
|
||||
--box-shadow: var(
|
||||
--vdp-box-shadow,
|
||||
0 4px 10px 0 rgba(128, 144, 160, 0.1),
|
||||
0 0 1px 0 rgba(128, 144, 160, 0.81)
|
||||
);
|
||||
--text-color: var(--vdp-text-color, #000000);
|
||||
--border-radius: var(--vdp-border-radius, 3px);
|
||||
--heading-size: var(--vdp-heading-size, 2.5em); /* 40px for 16px font */
|
||||
--heading-weight: var(--vdp-heading-weight, bold);
|
||||
--heading-hover-color: var(--vdp-heading-hover-color, #eeeeee);
|
||||
--arrow-color: var(--vdp-arrow-color, currentColor);
|
||||
|
||||
--elem-color: var(--vdp-elem-color, currentColor);
|
||||
--elem-disabled-color: var(--vdp-disabled-color, #d5d9e0);
|
||||
--elem-hover-color: var(--vdp-hover-color, #fff);
|
||||
--elem-hover-bg-color: var(--vdp-hover-bg-color, #0baf74);
|
||||
--elem-selected-color: var(--vdp-selected-color, #fff);
|
||||
--elem-selected-bg-color: var(--vdp-selected-bg-color, #0baf74);
|
||||
|
||||
--elem-current-outline-color: var(--vdp-current-date-outline-color, #888);
|
||||
--elem-current-font-weight: var(--vdp-current-date-font-weight, bold);
|
||||
|
||||
--elem-font-size: var(--vdp-elem-font-size, 0.8em);
|
||||
--elem-border-radius: var(--vdp-elem-border-radius, 3px);
|
||||
|
||||
--divider-color: var(--vdp-divider-color, var(--elem-disabled-color));
|
||||
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.v3dp__clearable {
|
||||
display: inline;
|
||||
position: relative;
|
||||
left: -15px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
223
node_modules/vue3-datepicker/src/datepicker/DayPicker.vue
generated
vendored
Normal file
223
node_modules/vue3-datepicker/src/datepicker/DayPicker.vue
generated
vendored
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
<template>
|
||||
<picker-popup
|
||||
headingClickable
|
||||
:leftDisabled="leftDisabled"
|
||||
:rightDisabled="rightDisabled"
|
||||
:items="days"
|
||||
viewMode="day"
|
||||
@left="previousPage"
|
||||
@right="nextPage"
|
||||
@heading="$emit('back')"
|
||||
@elementClick="$emit('select', $event)"
|
||||
>
|
||||
<template #heading>{{ heading }}</template>
|
||||
<template #subheading>
|
||||
<span
|
||||
v-for="(day, index) in weekDays"
|
||||
:key="day"
|
||||
:class="`v3dp__subheading__weekday__${index}`"
|
||||
>
|
||||
{{ day }}
|
||||
</span>
|
||||
</template>
|
||||
</picker-popup>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, ref, watchEffect, PropType } from 'vue'
|
||||
import {
|
||||
startOfMonth,
|
||||
endOfMonth,
|
||||
eachDayOfInterval,
|
||||
subMonths,
|
||||
addMonths,
|
||||
startOfWeek,
|
||||
endOfWeek,
|
||||
isSameDay,
|
||||
setDay,
|
||||
isWithinInterval,
|
||||
isBefore,
|
||||
isAfter,
|
||||
isSameMonth,
|
||||
endOfDay,
|
||||
startOfDay,
|
||||
isValid,
|
||||
format as formatDate,
|
||||
} from 'date-fns'
|
||||
import PickerPopup, { Item } from './PickerPopup.vue'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
PickerPopup,
|
||||
},
|
||||
emits: {
|
||||
'update:pageDate': (date: Date) => isValid(date),
|
||||
select: (date: Date) => isValid(date),
|
||||
back: () => true,
|
||||
},
|
||||
props: {
|
||||
selected: {
|
||||
type: Date as PropType<Date>,
|
||||
required: false,
|
||||
},
|
||||
pageDate: {
|
||||
type: Date as PropType<Date>,
|
||||
required: true,
|
||||
},
|
||||
format: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'dd',
|
||||
},
|
||||
headingFormat: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'LLLL yyyy',
|
||||
},
|
||||
weekdayFormat: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'EE',
|
||||
},
|
||||
locale: {
|
||||
type: Object as PropType<Locale>,
|
||||
required: false,
|
||||
},
|
||||
weekStartsOn: {
|
||||
type: Number as PropType<1 | 2 | 3 | 4 | 5 | 6 | 0>,
|
||||
required: false,
|
||||
default: 1,
|
||||
validator: (i: unknown): boolean =>
|
||||
typeof i === 'number' && Number.isInteger(i) && i >= 0 && i <= 6,
|
||||
},
|
||||
lowerLimit: {
|
||||
type: Date as PropType<Date>,
|
||||
required: false,
|
||||
},
|
||||
upperLimit: {
|
||||
type: Date as PropType<Date>,
|
||||
required: false,
|
||||
},
|
||||
disabledDates: {
|
||||
type: Object as PropType<{
|
||||
dates?: Date[]
|
||||
predicate?: (target: Date) => boolean
|
||||
}>,
|
||||
required: false,
|
||||
},
|
||||
allowOutsideInterval: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const format = computed(
|
||||
() => (format: string) => (value: Date | number) =>
|
||||
formatDate(value, format, {
|
||||
locale: props.locale,
|
||||
weekStartsOn: props.weekStartsOn,
|
||||
})
|
||||
)
|
||||
|
||||
const monthStart = computed(() => startOfMonth(props.pageDate))
|
||||
const monthEnd = computed(() => endOfMonth(props.pageDate))
|
||||
const currentMonth = computed(() => ({
|
||||
start: monthStart.value,
|
||||
end: monthEnd.value,
|
||||
}))
|
||||
|
||||
const displayedInterval = computed(() => ({
|
||||
start: startOfWeek(monthStart.value, {
|
||||
weekStartsOn: props.weekStartsOn,
|
||||
}),
|
||||
end: endOfWeek(monthEnd.value, {
|
||||
weekStartsOn: props.weekStartsOn,
|
||||
}),
|
||||
}))
|
||||
|
||||
const weekDays = computed(() => {
|
||||
const initial = props.weekStartsOn
|
||||
const dayFormat = format.value(props.weekdayFormat)
|
||||
return Array.from(Array(7))
|
||||
.map((_, i) => (initial + i) % 7)
|
||||
.map((v) =>
|
||||
setDay(new Date(), v, {
|
||||
weekStartsOn: props.weekStartsOn,
|
||||
})
|
||||
)
|
||||
.map(dayFormat)
|
||||
})
|
||||
|
||||
const isEnabled = (
|
||||
target: Date,
|
||||
lower?: Date,
|
||||
upper?: Date,
|
||||
disabledDates?: { dates?: Date[]; predicate?: (target: Date) => boolean }
|
||||
): boolean => {
|
||||
if (disabledDates?.dates?.some((date) => isSameDay(target, date)))
|
||||
return false
|
||||
if (disabledDates?.predicate?.(target)) return false
|
||||
if (!lower && !upper) return true
|
||||
if (lower && isBefore(target, startOfDay(lower))) return false
|
||||
if (upper && isAfter(target, endOfDay(upper))) return false
|
||||
return true
|
||||
}
|
||||
|
||||
const days = computed(() => {
|
||||
const today = new Date()
|
||||
const dayFormat = format.value(props.format)
|
||||
return eachDayOfInterval(displayedInterval.value).map(
|
||||
(value): Item => ({
|
||||
value,
|
||||
display: dayFormat(value),
|
||||
selected: !!props.selected && isSameDay(props.selected, value),
|
||||
current: isSameDay(today, value),
|
||||
disabled:
|
||||
(!props.allowOutsideInterval &&
|
||||
!isWithinInterval(value, currentMonth.value)) ||
|
||||
!isEnabled(
|
||||
value,
|
||||
props.lowerLimit,
|
||||
props.upperLimit,
|
||||
props.disabledDates
|
||||
),
|
||||
key: format.value('yyyy-MM-dd')(value),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
const heading = computed(() =>
|
||||
format.value(props.headingFormat)(props.pageDate)
|
||||
)
|
||||
const leftDisabled = computed(
|
||||
() =>
|
||||
props.lowerLimit &&
|
||||
(isSameMonth(props.lowerLimit, props.pageDate) ||
|
||||
isBefore(props.pageDate, props.lowerLimit))
|
||||
)
|
||||
const rightDisabled = computed(
|
||||
() =>
|
||||
props.upperLimit &&
|
||||
(isSameMonth(props.upperLimit, props.pageDate) ||
|
||||
isAfter(props.pageDate, props.upperLimit))
|
||||
)
|
||||
|
||||
const previousPage = () =>
|
||||
emit('update:pageDate', subMonths(props.pageDate, 1))
|
||||
const nextPage = () => emit('update:pageDate', addMonths(props.pageDate, 1))
|
||||
|
||||
return {
|
||||
weekDays,
|
||||
days,
|
||||
heading,
|
||||
leftDisabled,
|
||||
rightDisabled,
|
||||
previousPage,
|
||||
nextPage,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
145
node_modules/vue3-datepicker/src/datepicker/MonthPicker.vue
generated
vendored
Normal file
145
node_modules/vue3-datepicker/src/datepicker/MonthPicker.vue
generated
vendored
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
<template>
|
||||
<picker-popup
|
||||
headingClickable
|
||||
:columnCount="3"
|
||||
:items="months"
|
||||
:leftDisabled="leftDisabled"
|
||||
:rightDisabled="rightDisabled"
|
||||
viewMode="month"
|
||||
@left="previousPage"
|
||||
@right="nextPage"
|
||||
@heading="$emit('back')"
|
||||
@elementClick="$emit('select', $event)"
|
||||
>
|
||||
<template #heading>{{ heading }}</template>
|
||||
</picker-popup>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, ref, watchEffect, PropType } from 'vue'
|
||||
import {
|
||||
startOfYear,
|
||||
endOfYear,
|
||||
eachMonthOfInterval,
|
||||
getMonth,
|
||||
getYear,
|
||||
subYears,
|
||||
addYears,
|
||||
format,
|
||||
isSameMonth,
|
||||
isBefore,
|
||||
isAfter,
|
||||
isSameYear,
|
||||
startOfMonth,
|
||||
endOfMonth,
|
||||
isValid,
|
||||
format as formatDate,
|
||||
} from 'date-fns'
|
||||
import PickerPopup, { Item } from './PickerPopup.vue'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
PickerPopup,
|
||||
},
|
||||
emits: {
|
||||
'update:pageDate': (date: Date) => isValid(date),
|
||||
select: (date: Date) => isValid(date),
|
||||
back: () => true,
|
||||
},
|
||||
props: {
|
||||
/**
|
||||
* Currently selected date, needed for highlighting
|
||||
*/
|
||||
selected: {
|
||||
type: Date as PropType<Date>,
|
||||
required: false,
|
||||
},
|
||||
pageDate: {
|
||||
type: Date as PropType<Date>,
|
||||
required: true,
|
||||
},
|
||||
format: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'LLL',
|
||||
},
|
||||
locale: {
|
||||
type: Object as PropType<Locale>,
|
||||
required: false,
|
||||
},
|
||||
lowerLimit: {
|
||||
type: Date as PropType<Date>,
|
||||
required: false,
|
||||
},
|
||||
upperLimit: {
|
||||
type: Date as PropType<Date>,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const from = computed(() => startOfYear(props.pageDate))
|
||||
const to = computed(() => endOfYear(props.pageDate))
|
||||
|
||||
const format = computed(
|
||||
() => (value: Date | number) =>
|
||||
formatDate(value, props.format, {
|
||||
locale: props.locale,
|
||||
})
|
||||
)
|
||||
|
||||
const isEnabled = (
|
||||
target: Date,
|
||||
lower: Date | undefined,
|
||||
upper: Date | undefined
|
||||
): boolean => {
|
||||
if (!lower && !upper) return true
|
||||
if (lower && isBefore(target, startOfMonth(lower))) return false
|
||||
if (upper && isAfter(target, endOfMonth(upper))) return false
|
||||
return true
|
||||
}
|
||||
|
||||
const months = computed(() =>
|
||||
eachMonthOfInterval({
|
||||
start: from.value,
|
||||
end: to.value,
|
||||
}).map(
|
||||
(value): Item => ({
|
||||
value,
|
||||
display: format.value(value),
|
||||
key: format.value(value),
|
||||
selected: !!props.selected && isSameMonth(props.selected, value),
|
||||
disabled: !isEnabled(value, props.lowerLimit, props.upperLimit),
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
const heading = computed(() => getYear(from.value))
|
||||
|
||||
const leftDisabled = computed(
|
||||
() =>
|
||||
props.lowerLimit &&
|
||||
(isSameYear(props.lowerLimit, props.pageDate) ||
|
||||
isBefore(props.pageDate, props.lowerLimit))
|
||||
)
|
||||
const rightDisabled = computed(
|
||||
() =>
|
||||
props.upperLimit &&
|
||||
(isSameYear(props.upperLimit, props.pageDate) ||
|
||||
isAfter(props.pageDate, props.upperLimit))
|
||||
)
|
||||
|
||||
const previousPage = () =>
|
||||
emit('update:pageDate', subYears(props.pageDate, 1))
|
||||
const nextPage = () => emit('update:pageDate', addYears(props.pageDate, 1))
|
||||
|
||||
return {
|
||||
months,
|
||||
heading,
|
||||
leftDisabled,
|
||||
rightDisabled,
|
||||
previousPage,
|
||||
nextPage,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
255
node_modules/vue3-datepicker/src/datepicker/PickerPopup.vue
generated
vendored
Normal file
255
node_modules/vue3-datepicker/src/datepicker/PickerPopup.vue
generated
vendored
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
<template>
|
||||
<div
|
||||
class="v3dp__popout"
|
||||
:class="`v3dp__popout-${viewMode}`"
|
||||
:style="{ ['--popout-column-definition' as any]: `repeat(${columnCount}, 1fr)` }"
|
||||
@mousedown.prevent
|
||||
>
|
||||
<div class="v3dp__heading">
|
||||
<button
|
||||
class="v3dp__heading__button v3dp__heading__button__left"
|
||||
:disabled="leftDisabled"
|
||||
@click.stop.prevent="$emit('left')"
|
||||
>
|
||||
<slot name="arrow-left">
|
||||
<svg
|
||||
class="v3dp__heading__icon"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 6 8"
|
||||
>
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path stroke="none" d="M-9 16V-8h24v24z" />
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M5 0L1 4l4 4"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</slot>
|
||||
</button>
|
||||
<component
|
||||
:is="headingClickable ? 'button' : 'span'"
|
||||
class="v3dp__heading__center"
|
||||
@click.stop.prevent="$emit('heading')"
|
||||
>
|
||||
<slot name="heading" />
|
||||
</component>
|
||||
<button
|
||||
class="v3dp__heading__button v3dp__heading__button__right"
|
||||
:disabled="rightDisabled"
|
||||
@click.stop.prevent="$emit('right')"
|
||||
>
|
||||
<slot name="arrow-right">
|
||||
<svg
|
||||
class="v3dp__heading__icon"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 6 8"
|
||||
>
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path stroke="none" d="M15-8v24H-9V-8z" />
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M1 8l4-4-4-4"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</slot>
|
||||
</button>
|
||||
</div>
|
||||
<div class="v3dp__body">
|
||||
<template v-if="'subheading' in $slots">
|
||||
<div class="v3dp__subheading">
|
||||
<slot name="subheading" />
|
||||
</div>
|
||||
<hr class="v3dp__divider" />
|
||||
</template>
|
||||
<div class="v3dp__elements">
|
||||
<slot name="body">
|
||||
<button
|
||||
v-for="item in items"
|
||||
:key="item.key"
|
||||
:disabled="item.disabled"
|
||||
:class="[
|
||||
{
|
||||
selected: item.selected,
|
||||
current: item.current,
|
||||
},
|
||||
`v3dp__element__button__${viewMode}`,
|
||||
]"
|
||||
@click.stop.prevent="$emit('elementClick', item.value)"
|
||||
>
|
||||
<span>{{ item.display }}</span>
|
||||
</button>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { isValid } from 'date-fns'
|
||||
import { defineComponent, PropType, computed } from 'vue'
|
||||
|
||||
export interface Item {
|
||||
key: string
|
||||
value: Date
|
||||
display: number | string
|
||||
disabled: boolean
|
||||
selected: boolean
|
||||
current?: boolean
|
||||
}
|
||||
|
||||
export type ViewMode = 'year' | 'month' | 'day' | 'time' | 'custom'
|
||||
const VIEW_MODES = ['year', 'month', 'day', 'time', 'custom']
|
||||
|
||||
export default defineComponent({
|
||||
emits: {
|
||||
elementClick: (value: Date) => isValid(value),
|
||||
left: () => true,
|
||||
right: () => true,
|
||||
heading: () => true,
|
||||
},
|
||||
props: {
|
||||
headingClickable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
leftDisabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
rightDisabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
columnCount: {
|
||||
type: Number,
|
||||
default: 7,
|
||||
},
|
||||
items: {
|
||||
type: Array as PropType<Item[]>,
|
||||
default: (): Item[] => [],
|
||||
},
|
||||
viewMode: {
|
||||
type: String as PropType<ViewMode>,
|
||||
required: true,
|
||||
validate: (x: unknown) => typeof x === 'string' && VIEW_MODES.includes(x),
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v3dp__popout {
|
||||
z-index: 10;
|
||||
position: absolute;
|
||||
/* bottom: 0; */
|
||||
text-align: center;
|
||||
width: 17.5em;
|
||||
background-color: var(--popout-bg-color);
|
||||
box-shadow: var(--box-shadow);
|
||||
border-radius: var(--border-radius);
|
||||
padding: 8px 0 1em;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.v3dp__popout * {
|
||||
color: inherit;
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
.v3dp__popout :deep(button) {
|
||||
background: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.v3dp__popout :deep(button:not(:disabled)) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.v3dp__heading {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
height: var(--heading-size);
|
||||
line-height: var(--heading-size);
|
||||
font-weight: var(--heading-weight);
|
||||
}
|
||||
|
||||
.v3dp__heading__button {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: var(--heading-size);
|
||||
}
|
||||
|
||||
button.v3dp__heading__center:hover,
|
||||
.v3dp__heading__button:not(:disabled):hover {
|
||||
background-color: var(--heading-hover-color);
|
||||
}
|
||||
|
||||
.v3dp__heading__center {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.v3dp__heading__icon {
|
||||
height: 12px;
|
||||
stroke: var(--arrow-color);
|
||||
}
|
||||
|
||||
.v3dp__heading__button:disabled .v3dp__heading__icon {
|
||||
stroke: var(--elem-disabled-color);
|
||||
}
|
||||
|
||||
.v3dp__subheading,
|
||||
.v3dp__elements {
|
||||
display: grid;
|
||||
grid-template-columns: var(--popout-column-definition);
|
||||
font-size: var(--elem-font-size);
|
||||
}
|
||||
|
||||
.v3dp__subheading {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.v3dp__divider {
|
||||
border: 1px solid var(--divider-color);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.v3dp__elements :deep(button:disabled) {
|
||||
color: var(--elem-disabled-color);
|
||||
}
|
||||
|
||||
.v3dp__elements :deep(button) {
|
||||
padding: 0.3em 0.6em;
|
||||
}
|
||||
|
||||
.v3dp__elements :deep(button span) {
|
||||
display: block;
|
||||
line-height: 1.9em;
|
||||
height: 1.8em;
|
||||
border-radius: var(--elem-border-radius);
|
||||
}
|
||||
|
||||
.v3dp__elements :deep(button:not(:disabled):hover span) {
|
||||
background-color: var(--elem-hover-bg-color);
|
||||
color: var(--elem-hover-color);
|
||||
}
|
||||
|
||||
.v3dp__elements :deep(button.selected span) {
|
||||
background-color: var(--elem-selected-bg-color);
|
||||
color: var(--elem-selected-color);
|
||||
}
|
||||
|
||||
.v3dp__elements :deep(button.current span) {
|
||||
font-weight: var(--elem-current-font-weight);
|
||||
outline: 1px solid var(--elem-current-outline-color);
|
||||
}
|
||||
</style>
|
||||
233
node_modules/vue3-datepicker/src/datepicker/Timepicker.vue
generated
vendored
Normal file
233
node_modules/vue3-datepicker/src/datepicker/Timepicker.vue
generated
vendored
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
<template>
|
||||
<picker-popup
|
||||
headingClickable
|
||||
:columnCount="2"
|
||||
:leftDisabled="true"
|
||||
:rightDisabled="true"
|
||||
viewMode="time"
|
||||
@heading="$emit('back')"
|
||||
>
|
||||
<template #heading
|
||||
>{{ padStartZero(hours) }}:{{ padStartZero(minutes) }}</template
|
||||
>
|
||||
<template #body>
|
||||
<div ref="hoursListRef" class="v3dp__column">
|
||||
<button
|
||||
v-for="item in hoursList"
|
||||
:key="item.value"
|
||||
:ref="item.ref"
|
||||
:class="[{ selected: item.selected }, 'v3dp__element_button__hour']"
|
||||
:disabled="!isEnabled(item.date)"
|
||||
@click.stop.prevent="hours = item.value"
|
||||
>
|
||||
<span>{{ padStartZero(item.value) }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div ref="minutesListRef" class="v3dp__column">
|
||||
<button
|
||||
v-for="item in minutesList"
|
||||
:key="item.value"
|
||||
:ref="item.ref"
|
||||
:class="[{ selected: item.selected }, 'v3dp__element_button__minute']"
|
||||
:disabled="!isEnabled(item.date)"
|
||||
@click.stop.prevent="selectMinutes(item)"
|
||||
>
|
||||
<span>{{ padStartZero(item.value) }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</picker-popup>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
defineComponent,
|
||||
computed,
|
||||
ref,
|
||||
watch,
|
||||
nextTick,
|
||||
ComputedRef,
|
||||
Ref,
|
||||
PropType,
|
||||
} from 'vue'
|
||||
import { isSameHour, isSameMinute, isValid, set } from 'date-fns'
|
||||
import PickerPopup from './PickerPopup.vue'
|
||||
|
||||
interface Item {
|
||||
value: number
|
||||
date: Date
|
||||
selected: boolean | undefined
|
||||
ref: Ref<null | HTMLElement>
|
||||
}
|
||||
|
||||
function scrollParentToChild(parent: HTMLElement, child: HTMLElement) {
|
||||
const parentRect = parent.getBoundingClientRect()
|
||||
const parentViewableArea = {
|
||||
height: parent.clientHeight,
|
||||
width: parent.clientWidth,
|
||||
}
|
||||
|
||||
const childRect = child.getBoundingClientRect()
|
||||
const isViewable =
|
||||
childRect.top >= parentRect.top &&
|
||||
childRect.bottom <= parentRect.top + parentViewableArea.height
|
||||
|
||||
if (!isViewable) {
|
||||
const scrollTop = childRect.top - parentRect.top
|
||||
const scrollBot = childRect.bottom - parentRect.bottom
|
||||
if (Math.abs(scrollTop) < Math.abs(scrollBot)) {
|
||||
parent.scrollTop += scrollTop
|
||||
} else {
|
||||
parent.scrollTop += scrollBot
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
PickerPopup,
|
||||
},
|
||||
emits: {
|
||||
select: (date: Date) => isValid(date),
|
||||
back: () => true,
|
||||
},
|
||||
props: {
|
||||
selected: {
|
||||
type: Date as PropType<Date>,
|
||||
required: false,
|
||||
},
|
||||
pageDate: {
|
||||
type: Date as PropType<Date>,
|
||||
required: true,
|
||||
},
|
||||
visible: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
disabledTime: {
|
||||
type: Object as PropType<{
|
||||
dates?: Date[]
|
||||
predicate?: (target: Date) => boolean
|
||||
}>,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const hoursListRef = ref(null as HTMLElement | null)
|
||||
const minutesListRef = ref(null as HTMLElement | null)
|
||||
|
||||
const currentDate = computed(() => props.pageDate ?? props.selected)
|
||||
|
||||
const hours = ref(currentDate.value.getHours())
|
||||
const minutes = ref(currentDate.value.getMinutes())
|
||||
|
||||
watch(
|
||||
() => props.selected,
|
||||
(value: Date | undefined) => {
|
||||
let newHours = 0
|
||||
let newMinutes = 0
|
||||
|
||||
if (value) {
|
||||
newHours = value.getHours()
|
||||
newMinutes = value.getMinutes()
|
||||
}
|
||||
|
||||
hours.value = newHours
|
||||
minutes.value = newMinutes
|
||||
}
|
||||
)
|
||||
|
||||
const hoursList: ComputedRef<Item[]> = computed(() =>
|
||||
[...Array(24).keys()].map(
|
||||
(value): Item => ({
|
||||
value,
|
||||
date: set(new Date(currentDate.value.getTime()), {
|
||||
hours: value,
|
||||
minutes: minutes.value,
|
||||
seconds: 0,
|
||||
}),
|
||||
selected: hours.value === value,
|
||||
ref: ref(null),
|
||||
})
|
||||
)
|
||||
)
|
||||
const minutesList: ComputedRef<Item[]> = computed(() =>
|
||||
[...Array(60).keys()].map((value) => ({
|
||||
value,
|
||||
date: set(new Date(currentDate.value.getTime()), {
|
||||
hours: hours.value,
|
||||
minutes: value,
|
||||
seconds: 0,
|
||||
}),
|
||||
selected: minutes.value === value,
|
||||
ref: ref(null),
|
||||
}))
|
||||
)
|
||||
|
||||
const selectMinutes = (item: Item) => {
|
||||
minutes.value = item.value
|
||||
|
||||
emit('select', item.date)
|
||||
}
|
||||
|
||||
const scroll = () => {
|
||||
const currentHour = hoursList.value.find(
|
||||
(item) => item.ref.value?.classList?.contains('selected') ?? false
|
||||
)
|
||||
const currentMinute = minutesList.value.find(
|
||||
(item) => item.ref.value?.classList?.contains('selected') ?? false
|
||||
)
|
||||
|
||||
if (currentHour && currentMinute) {
|
||||
scrollParentToChild(hoursListRef.value!, currentHour.ref.value!)
|
||||
scrollParentToChild(minutesListRef.value!, currentMinute.ref.value!)
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
nextTick(scroll)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const isEnabled = (target: Date): boolean => {
|
||||
if (
|
||||
props.disabledTime?.dates?.some(
|
||||
(date) => isSameHour(target, date) && isSameMinute(target, date)
|
||||
)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
if (props.disabledTime?.predicate?.(target)) return false
|
||||
return true
|
||||
}
|
||||
|
||||
const padStartZero = (item: number): string => `0${item}`.substr(-2)
|
||||
|
||||
return {
|
||||
hoursListRef,
|
||||
minutesListRef,
|
||||
hours,
|
||||
minutes,
|
||||
hoursList,
|
||||
minutesList,
|
||||
padStartZero,
|
||||
selectMinutes,
|
||||
isEnabled,
|
||||
scroll,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v3dp__column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
height: 190px;
|
||||
}
|
||||
</style>
|
||||
123
node_modules/vue3-datepicker/src/datepicker/YearPicker.vue
generated
vendored
Normal file
123
node_modules/vue3-datepicker/src/datepicker/YearPicker.vue
generated
vendored
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
<template>
|
||||
<picker-popup
|
||||
:columnCount="3"
|
||||
:leftDisabled="leftDisabled"
|
||||
:rightDisabled="rightDisabled"
|
||||
:items="years"
|
||||
viewMode="year"
|
||||
@left="previousPage"
|
||||
@right="nextPage"
|
||||
@elementClick="$emit('select', $event)"
|
||||
>
|
||||
<template #heading>{{ heading }}</template>
|
||||
</picker-popup>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, ref, watchEffect, PropType } from 'vue'
|
||||
import {
|
||||
startOfDecade,
|
||||
endOfDecade,
|
||||
eachYearOfInterval,
|
||||
getYear,
|
||||
subYears,
|
||||
addYears,
|
||||
isAfter,
|
||||
isBefore,
|
||||
getDecade,
|
||||
isValid,
|
||||
} from 'date-fns'
|
||||
import PickerPopup, { Item } from './PickerPopup.vue'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
PickerPopup,
|
||||
},
|
||||
emits: {
|
||||
'update:pageDate': (date: Date) => isValid(date),
|
||||
select: (date: Date) => isValid(date),
|
||||
},
|
||||
props: {
|
||||
selected: {
|
||||
type: Date as PropType<Date>,
|
||||
required: false,
|
||||
},
|
||||
pageDate: {
|
||||
type: Date as PropType<Date>,
|
||||
required: true,
|
||||
},
|
||||
lowerLimit: {
|
||||
type: Date as PropType<Date>,
|
||||
required: false,
|
||||
},
|
||||
upperLimit: {
|
||||
type: Date as PropType<Date>,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const from = computed(() => startOfDecade(props.pageDate))
|
||||
const to = computed(() => endOfDecade(props.pageDate))
|
||||
|
||||
const isEnabled = (
|
||||
target: Date,
|
||||
lower: Date | undefined,
|
||||
upper: Date | undefined
|
||||
): boolean => {
|
||||
if (!lower && !upper) return true
|
||||
if (lower && getYear(target) < getYear(lower)) return false
|
||||
if (upper && getYear(target) > getYear(upper)) return false
|
||||
return true
|
||||
}
|
||||
|
||||
const years = computed(() =>
|
||||
eachYearOfInterval({
|
||||
start: from.value,
|
||||
end: to.value,
|
||||
}).map(
|
||||
(value): Item => ({
|
||||
value,
|
||||
key: String(getYear(value)),
|
||||
display: getYear(value),
|
||||
selected:
|
||||
!!props.selected && getYear(value) === getYear(props.selected),
|
||||
disabled: !isEnabled(value, props.lowerLimit, props.upperLimit),
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
const heading = computed(() => {
|
||||
const start = getYear(from.value)
|
||||
const end = getYear(to.value)
|
||||
|
||||
return `${start} - ${end}`
|
||||
})
|
||||
|
||||
const leftDisabled = computed(
|
||||
() =>
|
||||
props.lowerLimit &&
|
||||
(getDecade(props.lowerLimit) === getDecade(props.pageDate) ||
|
||||
isBefore(props.pageDate, props.lowerLimit))
|
||||
)
|
||||
const rightDisabled = computed(
|
||||
() =>
|
||||
props.upperLimit &&
|
||||
(getDecade(props.upperLimit) === getDecade(props.pageDate) ||
|
||||
isAfter(props.pageDate, props.upperLimit))
|
||||
)
|
||||
|
||||
const previousPage = () =>
|
||||
emit('update:pageDate', subYears(props.pageDate, 10))
|
||||
const nextPage = () => emit('update:pageDate', addYears(props.pageDate, 10))
|
||||
|
||||
return {
|
||||
years,
|
||||
heading,
|
||||
leftDisabled,
|
||||
rightDisabled,
|
||||
previousPage,
|
||||
nextPage,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
Loading…
Add table
Add a link
Reference in a new issue