refactor(components): [date-picker] panel-date-pick (#8011)

* refactor(components): [date-picker] panel-date-pick

* Enhance typing.

* chore: restore handle shortcut click

* chore: fix format

Co-authored-by: JeremyWuuuuu <15975785+JeremyWuuuuu@users.noreply.github.com>
This commit is contained in:
Jeremy
2022-05-31 16:40:19 +08:00
committed by GitHub
parent cced377628
commit 97eae00e65
5 changed files with 174 additions and 118 deletions

View File

@@ -1,35 +1,15 @@
import {
getCurrentInstance,
inject,
ref,
unref,
useAttrs,
useSlots,
watch,
} from 'vue'
import dayjs from 'dayjs'
import { isArray, isFunction } from '@element-plus/utils'
import { getCurrentInstance, inject, ref, unref, watch } from 'vue'
import { isArray } from '@element-plus/utils'
import { ROOT_PICKER_INJECTION_KEY } from '@element-plus/tokens'
import { useLocale, useNamespace } from '@element-plus/hooks'
import { getDefaultValue, isValidRange } from '../utils'
import { useShortcut } from './use-shortcut'
import type { Ref, SetupContext } from 'vue'
import type { Ref } from 'vue'
import type { Dayjs } from 'dayjs'
import type {
PanelRangeSharedProps,
RangePickerSharedEmits,
RangeState,
} from '../props/shared'
import type { PanelRangeSharedProps, RangeState } from '../props/shared'
import type { DefaultValue } from '../utils'
// FIXME: extract this to `date-picker.ts`
type Shortcut = {
text: string
value: [Date, Date] | (() => [Date, Date])
onClick?: (ctx: Omit<SetupContext<RangePickerSharedEmits>, 'expose'>) => void
}
type UseRangePickerProps = {
onParsedValueChanged: (
minDate: Dayjs | undefined,
@@ -53,12 +33,11 @@ export const useRangePicker = (
}: UseRangePickerProps
) => {
const { emit } = getCurrentInstance()!
const attrs = useAttrs()
const slots = useSlots()
const { pickerNs } = inject(ROOT_PICKER_INJECTION_KEY)!
const drpNs = useNamespace('date-range-picker')
const { t, lang } = useLocale()
const handleShortcutClick = useShortcut(lang)
const minDate = ref<Dayjs>()
const maxDate = ref<Dayjs>()
const rangeState = ref<RangeState>({
@@ -86,27 +65,6 @@ export const useRangePicker = (
}
}
const handleShortcutClick = (shortcut: Shortcut) => {
const shortcutValues = isFunction(shortcut.value)
? shortcut.value()
: shortcut.value
if (shortcutValues) {
emit('pick', [
dayjs(shortcutValues[0]).locale(lang.value),
dayjs(shortcutValues[1]).locale(lang.value),
])
return
}
if (shortcut.onClick) {
shortcut.onClick({
attrs,
slots,
emit,
})
}
}
const restoreDefault = () => {
const [start, end] = getDefaultValue(unref(defaultValue), {
lang: unref(lang),

View File

@@ -0,0 +1,43 @@
import { getCurrentInstance, useAttrs, useSlots } from 'vue'
import dayjs from 'dayjs'
import { isFunction } from '@element-plus/utils'
import type { SetupContext } from 'vue'
import type { useLocale } from '@element-plus/hooks'
import type { RangePickerSharedEmits } from '../props/shared'
// FIXME: extract this to `date-picker.ts`
export type Shortcut = {
text: string
value: [Date, Date] | (() => [Date, Date])
onClick?: (ctx: Omit<SetupContext<RangePickerSharedEmits>, 'expose'>) => void
}
export const useShortcut = (lang: ReturnType<typeof useLocale>['lang']) => {
const { emit } = getCurrentInstance()!
const attrs = useAttrs()
const slots = useSlots()
const handleShortcutClick = (shortcut: Shortcut) => {
const shortcutValues = isFunction(shortcut.value)
? shortcut.value()
: shortcut.value
if (shortcutValues) {
emit('pick', [
dayjs(shortcutValues[0]).locale(lang.value),
dayjs(shortcutValues[1]).locale(lang.value),
])
return
}
if (shortcut.onClick) {
shortcut.onClick({
attrs,
slots,
emit,
})
}
}
return handleShortcutClick
}

View File

@@ -183,7 +183,16 @@
</template>
<script lang="ts" setup>
import { computed, inject, nextTick, ref, toRef, watch } from 'vue'
import {
computed,
inject,
nextTick,
ref,
toRef,
useAttrs,
useSlots,
watch,
} from 'vue'
import dayjs from 'dayjs'
import ElButton from '@element-plus/components/button'
import { ClickOutside as vClickOutside } from '@element-plus/directives'
@@ -195,7 +204,7 @@ import {
extractTimeFormat,
} from '@element-plus/components/time-picker'
import { ElIcon } from '@element-plus/components/icon'
import { isFunction } from '@element-plus/utils'
import { isArray, isFunction } from '@element-plus/utils'
import { EVENT_CODE } from '@element-plus/constants'
import {
ArrowLeft,
@@ -209,16 +218,25 @@ import DateTable from './basic-date-table.vue'
import MonthTable from './basic-month-table.vue'
import YearTable from './basic-year-table.vue'
import type { ComponentPublicInstance, Ref } from 'vue'
import type { ComponentPublicInstance, Ref, SetupContext } from 'vue'
import type { ConfigType, Dayjs } from 'dayjs'
import type { PanelDatePickProps } from '../props/panel-date-pick'
import type {
DateTableEmits,
DatesPickerEmits,
WeekPickerEmits,
} from '../props/basic-date-table'
type DatePickType = PanelDatePickProps['type']
// todo
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const timeWithinRange = (_: ConfigType, __: any, ___: string) => true
const props = defineProps(panelDatePickProps)
const _emit = defineEmits(['pick', 'set-picker-option', 'panel-change'])
const contextEmit = defineEmits(['pick', 'set-picker-option', 'panel-change'])
const ppNs = useNamespace('picker-panel')
const dpNs = useNamespace('date-picker')
const attrs = useAttrs()
const slots = useSlots()
const { t, lang } = useLocale()
const pickerBase = inject('EP_PICKER_BASE') as any
@@ -244,8 +262,8 @@ const year = computed(() => {
})
const selectableRange = ref([])
const userInputDate = ref(null)
const userInputTime = ref(null)
const userInputDate = ref<string | null>(null)
const userInputTime = ref<string | null>(null)
// todo update to disableHour
const checkDateWithinRange = (date: ConfigType) => {
return selectableRange.value.length > 0
@@ -262,20 +280,21 @@ const formatEmit = (emitDayjs: Dayjs) => {
if (showTime.value) return emitDayjs.millisecond(0)
return emitDayjs.startOf('day')
}
const emit = (value, ...args) => {
const emit = (value: Dayjs | Dayjs[], ...args: any[]) => {
if (!value) {
_emit('pick', value, ...args)
} else if (Array.isArray(value)) {
contextEmit('pick', value, ...args)
} else if (isArray(value)) {
const dates = value.map(formatEmit)
_emit('pick', dates, ...args)
contextEmit('pick', dates, ...args)
} else {
_emit('pick', formatEmit(value), ...args)
contextEmit('pick', formatEmit(value), ...args)
}
userInputDate.value = null
userInputTime.value = null
}
const handleDatePick = (value: Dayjs, keepOpen: boolean = undefined) => {
const handleDatePick = (value: DateTableEmits, keepOpen?: boolean) => {
if (selectionMode.value === 'date') {
value = value as Dayjs
let newDate = props.parsedValue
? (props.parsedValue as Dayjs)
.year(value.year())
@@ -292,9 +311,9 @@ const handleDatePick = (value: Dayjs, keepOpen: boolean = undefined) => {
innerDate.value = newDate
emit(newDate, showTime.value || keepOpen)
} else if (selectionMode.value === 'week') {
emit(value.date)
emit((value as WeekPickerEmits).date)
} else if (selectionMode.value === 'dates') {
emit(value, true) // set true to keep panel open
emit(value as DatesPickerEmits, true) // set true to keep panel open
}
}
const prevMonth_ = () => {
@@ -341,23 +360,32 @@ const yearLabel = computed(() => {
return `${year.value} ${yearTranslation}`
})
const handleShortcutClick = (shortcut) => {
const shortcutValue =
typeof shortcut.value === 'function' ? shortcut.value() : shortcut.value
type Shortcut = {
value: (() => Dayjs) | Dayjs
onClick?: (ctx: Omit<SetupContext, 'expose'>) => void
}
const handleShortcutClick = (shortcut: Shortcut) => {
const shortcutValue = isFunction(shortcut.value)
? shortcut.value()
: shortcut.value
if (shortcutValue) {
emit(dayjs(shortcutValue).locale(lang.value))
return
}
if (shortcut.onClick) {
shortcut.onClick(ctx)
shortcut.onClick({
attrs,
slots,
emit: contextEmit as SetupContext['emit'],
})
}
}
const selectionMode = computed(() => {
if (['week', 'month', 'year', 'dates'].includes(props.type)) {
return props.type
}
return 'date'
const selectionMode = computed<DatePickType>(() => {
const { type } = props
if (['week', 'month', 'year', 'dates'].includes(type)) return type
return 'date' as DatePickType
})
const keyboardMode = computed<string>(() => {
@@ -387,7 +415,7 @@ watch(
const hasShortcuts = computed(() => !!shortcuts.length)
const handleMonthPick = async (month) => {
const handleMonthPick = async (month: number) => {
innerDate.value = innerDate.value.startOf('month').month(month)
if (selectionMode.value === 'month') {
emit(innerDate.value, false)
@@ -402,7 +430,7 @@ const handleMonthPick = async (month) => {
handlePanelChange('month')
}
const handleYearPick = async (year) => {
const handleYearPick = async (year: number) => {
if (selectionMode.value === 'year') {
innerDate.value = innerDate.value.startOf('year').year(year)
emit(innerDate.value, false)
@@ -440,7 +468,7 @@ const footerVisible = computed(() => {
const onConfirm = () => {
if (selectionMode.value === 'dates') {
emit(props.parsedValue)
emit(props.parsedValue as Dayjs[])
} else {
// deal with the scenario where: user opens the date time picker, then confirm without doing anything
let result = props.parsedValue as Dayjs
@@ -503,7 +531,7 @@ const handleTimePickClose = () => {
timePickerVisible.value = false
}
const handleTimePick = (value, visible, first) => {
const handleTimePick = (value: Dayjs, visible: boolean, first: boolean) => {
const newDate = props.parsedValue
? (props.parsedValue as Dayjs)
.hour(value.hour())
@@ -517,7 +545,7 @@ const handleTimePick = (value, visible, first) => {
}
}
const handleVisibleTimeChange = (value) => {
const handleVisibleTimeChange = (value: string) => {
const newDate = dayjs(value, timeFormat.value).locale(lang.value)
if (newDate.isValid() && checkDateWithinRange(newDate)) {
innerDate.value = newDate
@@ -530,7 +558,7 @@ const handleVisibleTimeChange = (value) => {
}
}
const handleVisibleDateChange = (value) => {
const handleVisibleDateChange = (value: string) => {
const newDate = dayjs(value, dateFormat.value).locale(lang.value)
if (newDate.isValid()) {
if (disabledDate && disabledDate(newDate.toDate())) {
@@ -553,14 +581,14 @@ const isValidValue = (date: unknown) => {
)
}
const formatToString = (value) => {
const formatToString = (value: Dayjs | Dayjs[]) => {
if (selectionMode.value === 'dates') {
return value.map((_) => _.format(props.format))
return (value as Dayjs[]).map((_) => _.format(props.format))
}
return value.format(props.format)
return (value as Dayjs).format(props.format)
}
const parseUserInput = (value) => {
const parseUserInput = (value: Dayjs) => {
return dayjs(value, props.format).locale(lang.value)
}
@@ -586,7 +614,7 @@ const handleFocusPicker = async () => {
}
}
const handleKeydownTable = (event) => {
const handleKeydownTable = (event: KeyboardEvent) => {
const { code } = event
const list = [
EVENT_CODE.up,
@@ -615,46 +643,55 @@ const handleKeydownTable = (event) => {
const handleKeyControl = (code: string) => {
type KeyControlMappingCallableOffset = (date: Date, step?: number) => number
interface KeyControlMapping {
[key: string]: {
offset: (date: Date, step: number) => any
[key: number]: number | KeyControlMappingCallableOffset
}
type KeyControl = {
[key: string]:
| number
| KeyControlMappingCallableOffset
| ((date: Date, step: number) => any)
offset: (date: Date, step: number) => any
}
interface KeyControlMapping {
[key: string]: KeyControl
}
const { up, down, left, right, home, end, pageUp, pageDown } = EVENT_CODE
const mapping: KeyControlMapping = {
year: {
[EVENT_CODE.up]: -4,
[EVENT_CODE.down]: 4,
[EVENT_CODE.left]: -1,
[EVENT_CODE.right]: 1,
offset: (date, step) => date.setFullYear(date.getFullYear() + step),
[up]: -4,
[down]: 4,
[left]: -1,
[right]: 1,
offset: (date: Date, step: number) =>
date.setFullYear(date.getFullYear() + step),
},
month: {
[EVENT_CODE.up]: -4,
[EVENT_CODE.down]: 4,
[EVENT_CODE.left]: -1,
[EVENT_CODE.right]: 1,
offset: (date, step) => date.setMonth(date.getMonth() + step),
[up]: -4,
[down]: 4,
[left]: -1,
[right]: 1,
offset: (date: Date, step: number) =>
date.setMonth(date.getMonth() + step),
},
week: {
[EVENT_CODE.up]: -1,
[EVENT_CODE.down]: 1,
[EVENT_CODE.left]: -1,
[EVENT_CODE.right]: 1,
offset: (date, step) => date.setDate(date.getDate() + step * 7),
[up]: -1,
[down]: 1,
[left]: -1,
[right]: 1,
offset: (date: Date, step: number) =>
date.setDate(date.getDate() + step * 7),
},
date: {
[EVENT_CODE.up]: -7,
[EVENT_CODE.down]: 7,
[EVENT_CODE.left]: -1,
[EVENT_CODE.right]: 1,
[EVENT_CODE.home]: (date) => -date.getDay(),
[EVENT_CODE.end]: (date) => -date.getDay() + 6,
[EVENT_CODE.pageUp]: (date) =>
[up]: -7,
[down]: 7,
[left]: -1,
[right]: 1,
[home]: (date: Date) => -date.getDay(),
[end]: (date: Date) => -date.getDay() + 6,
[pageUp]: (date: Date) =>
-new Date(date.getFullYear(), date.getMonth(), 0).getDate(),
[EVENT_CODE.pageDown]: (date) =>
[pageDown]: (date: Date) =>
new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(),
offset: (date, step) => date.setDate(date.getDate() + step),
offset: (date: Date, step: number) => date.setDate(date.getDate() + step),
},
}
@@ -673,19 +710,19 @@ const handleKeyControl = (code: string) => {
}
const result = dayjs(newDate).locale(lang.value)
innerDate.value = result
_emit('pick', result, true)
contextEmit('pick', result, true)
break
}
}
const handlePanelChange = (mode: 'month' | 'year') => {
_emit('panel-change', innerDate.value.toDate(), mode, currentView.value)
contextEmit('panel-change', innerDate.value.toDate(), mode, currentView.value)
}
_emit('set-picker-option', ['isValidValue', isValidValue])
_emit('set-picker-option', ['formatToString', formatToString])
_emit('set-picker-option', ['parseUserInput', parseUserInput])
_emit('set-picker-option', ['handleFocusPicker', handleFocusPicker])
contextEmit('set-picker-option', ['isValidValue', isValidValue])
contextEmit('set-picker-option', ['formatToString', formatToString])
contextEmit('set-picker-option', ['parseUserInput', parseUserInput])
contextEmit('set-picker-option', ['handleFocusPicker', handleFocusPicker])
watch(
() => defaultValue.value,

View File

@@ -2,6 +2,7 @@ import { buildProps, definePropType } from '@element-plus/utils'
import { datePickerSharedProps, selectionModeWithDefault } from './shared'
import type { ExtractPropTypes } from 'vue'
import type { Dayjs } from 'dayjs'
export const basicDateTableProps = buildProps({
...datePickerSharedProps,
@@ -13,3 +14,19 @@ export const basicDateTableProps = buildProps({
} as const)
export type BasicDateTableProps = ExtractPropTypes<typeof basicDateTableProps>
export type RangePickerEmits = { minDate: Dayjs; maxDate: null }
export type DatePickerEmits = Dayjs
export type DatesPickerEmits = Dayjs[]
export type WeekPickerEmits = {
year: number
week: number
value: string
date: Dayjs
}
export type DateTableEmits =
| RangePickerEmits
| DatePickerEmits
| DatesPickerEmits
| WeekPickerEmits

View File

@@ -3,6 +3,7 @@ import { datePickTypes } from '@element-plus/constants'
import type { ExtractPropTypes } from 'vue'
import type { Dayjs } from 'dayjs'
import type { DatePickType } from '@element-plus/constants'
const selectionModes = ['date', 'dates', 'year', 'month', 'week', 'range']
@@ -39,7 +40,7 @@ export const datePickerSharedProps = buildProps({
export const panelSharedProps = buildProps({
type: {
type: definePropType<typeof datePickTypes>(String),
type: definePropType<DatePickType>(String),
required: true,
values: datePickTypes,
},