mirror of
https://gitee.com/element-plus/element-plus.git
synced 2024-12-05 12:48:04 +08:00
refactor(components): [time-picker] time-picker (#8160)
* refactor(components): [time-picker] time-picker * Refactor `time-picker` to tsx. * chore: refactor to script setup * chore: fix typing issue in `picker` * chore: fix API exposing Co-authored-by: JeremyWuuuuu <15975785+JeremyWuuuuu@users.noreply.github.com>
This commit is contained in:
parent
6b6c5a4a87
commit
5b233da008
@ -49,7 +49,6 @@ export default defineComponent({
|
|||||||
|
|
||||||
const commonPicker = ref<InstanceType<typeof CommonPicker>>()
|
const commonPicker = ref<InstanceType<typeof CommonPicker>>()
|
||||||
const refProps = {
|
const refProps = {
|
||||||
...props,
|
|
||||||
focus: (focusStartInput = true) => {
|
focus: (focusStartInput = true) => {
|
||||||
commonPicker.value?.focus(focusStartInput)
|
commonPicker.value?.focus(focusStartInput)
|
||||||
},
|
},
|
||||||
|
@ -23,10 +23,10 @@
|
|||||||
<template #default>
|
<template #default>
|
||||||
<el-input
|
<el-input
|
||||||
v-if="!isRangeInput"
|
v-if="!isRangeInput"
|
||||||
:id="id"
|
:id="(id as string | undefined)"
|
||||||
ref="inputRef"
|
ref="inputRef"
|
||||||
container-role="combobox"
|
container-role="combobox"
|
||||||
:model-value="displayValue"
|
:model-value="(displayValue as string)"
|
||||||
:name="name"
|
:name="name"
|
||||||
:size="pickerSize"
|
:size="pickerSize"
|
||||||
:disabled="pickerDisabled"
|
:disabled="pickerDisabled"
|
||||||
@ -39,7 +39,7 @@
|
|||||||
@input="onUserInput"
|
@input="onUserInput"
|
||||||
@focus="handleFocusInput"
|
@focus="handleFocusInput"
|
||||||
@blur="handleBlurInput"
|
@blur="handleBlurInput"
|
||||||
@keydown="handleKeydownInput"
|
@keydown="handleKeydownInput as any"
|
||||||
@change="handleChange"
|
@change="handleChange"
|
||||||
@mousedown="onMouseDownInput"
|
@mousedown="onMouseDownInput"
|
||||||
@mouseenter="onMouseEnter"
|
@mouseenter="onMouseEnter"
|
||||||
@ -80,7 +80,7 @@
|
|||||||
pickerSize ? nsRange.bm('editor', pickerSize) : '',
|
pickerSize ? nsRange.bm('editor', pickerSize) : '',
|
||||||
$attrs.class,
|
$attrs.class,
|
||||||
]"
|
]"
|
||||||
:style="$attrs.style"
|
:style="($attrs.style as any)"
|
||||||
@click="handleFocusInput"
|
@click="handleFocusInput"
|
||||||
@mousedown="onMouseDownInput"
|
@mousedown="onMouseDownInput"
|
||||||
@mouseenter="onMouseEnter"
|
@mouseenter="onMouseEnter"
|
||||||
@ -162,17 +162,8 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import {
|
import { computed, inject, nextTick, provide, ref, unref, watch } from 'vue'
|
||||||
computed,
|
|
||||||
defineComponent,
|
|
||||||
inject,
|
|
||||||
nextTick,
|
|
||||||
provide,
|
|
||||||
ref,
|
|
||||||
unref,
|
|
||||||
watch,
|
|
||||||
} from 'vue'
|
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { isEqual } from 'lodash-unified'
|
import { isEqual } from 'lodash-unified'
|
||||||
import { onClickOutside } from '@vueuse/core'
|
import { onClickOutside } from '@vueuse/core'
|
||||||
@ -181,27 +172,24 @@ import { formContextKey, formItemContextKey } from '@element-plus/tokens'
|
|||||||
import ElInput from '@element-plus/components/input'
|
import ElInput from '@element-plus/components/input'
|
||||||
import ElIcon from '@element-plus/components/icon'
|
import ElIcon from '@element-plus/components/icon'
|
||||||
import ElTooltip from '@element-plus/components/tooltip'
|
import ElTooltip from '@element-plus/components/tooltip'
|
||||||
import { debugWarn, isEmpty } from '@element-plus/utils'
|
import { debugWarn, isArray, isEmpty } from '@element-plus/utils'
|
||||||
import { EVENT_CODE } from '@element-plus/constants'
|
import { EVENT_CODE } from '@element-plus/constants'
|
||||||
import { Calendar, Clock } from '@element-plus/icons-vue'
|
import { Calendar, Clock } from '@element-plus/icons-vue'
|
||||||
import { timePickerDefaultProps } from './props'
|
import { timePickerDefaultProps } from './props'
|
||||||
|
|
||||||
import type { Dayjs } from 'dayjs'
|
import type { Dayjs } from 'dayjs'
|
||||||
import type { ComponentPublicInstance } from 'vue'
|
import type { ComponentPublicInstance } from 'vue'
|
||||||
import type { FormContext, FormItemContext } from '@element-plus/tokens'
|
|
||||||
import type { Options } from '@popperjs/core'
|
import type { Options } from '@popperjs/core'
|
||||||
|
import type { FormContext, FormItemContext } from '@element-plus/tokens'
|
||||||
interface PickerOptions {
|
import type {
|
||||||
isValidValue: (date: Dayjs) => boolean
|
DateModelType,
|
||||||
handleKeydownInput: (event: KeyboardEvent) => void
|
DateOrDates,
|
||||||
parseUserInput: (value: Dayjs) => dayjs.Dayjs
|
DayOrDays,
|
||||||
formatToString: (value: Dayjs) => string | string[]
|
PickerOptions,
|
||||||
getRangeAvailableTime: (date: Dayjs) => dayjs.Dayjs
|
SingleOrRange,
|
||||||
getDefaultValue: () => Dayjs
|
TimePickerDefaultProps,
|
||||||
panelReady: boolean
|
UserInput,
|
||||||
handleClear: () => void
|
} from './props'
|
||||||
handleFocusPicker?: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
// Date object and string
|
// Date object and string
|
||||||
const dateEquals = function (a: Date | any, b: Date | any) {
|
const dateEquals = function (a: Date | any, b: Date | any) {
|
||||||
@ -217,8 +205,8 @@ const dateEquals = function (a: Date | any, b: Date | any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const valueEquals = function (a: Array<Date> | any, b: Array<Date> | any) {
|
const valueEquals = function (a: Array<Date> | any, b: Array<Date> | any) {
|
||||||
const aIsArray = Array.isArray(a)
|
const aIsArray = isArray(a)
|
||||||
const bIsArray = Array.isArray(b)
|
const bIsArray = isArray(b)
|
||||||
if (aIsArray && bIsArray) {
|
if (aIsArray && bIsArray) {
|
||||||
if (a.length !== b.length) {
|
if (a.length !== b.length) {
|
||||||
return false
|
return false
|
||||||
@ -231,11 +219,11 @@ const valueEquals = function (a: Array<Date> | any, b: Array<Date> | any) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const parser = function (
|
const parseDate = function (
|
||||||
date: string | number | Date,
|
date: string | number | Date,
|
||||||
format: string,
|
format: string | undefined,
|
||||||
lang: string
|
lang: string
|
||||||
): Dayjs {
|
) {
|
||||||
const day =
|
const day =
|
||||||
isEmpty(format) || format === 'x'
|
isEmpty(format) || format === 'x'
|
||||||
? dayjs(date).locale(lang)
|
? dayjs(date).locale(lang)
|
||||||
@ -244,8 +232,8 @@ const parser = function (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const formatter = function (
|
const formatter = function (
|
||||||
date: string | number | Date,
|
date: string | number | Date | Dayjs,
|
||||||
format: string,
|
format: string | undefined,
|
||||||
lang: string
|
lang: string
|
||||||
) {
|
) {
|
||||||
if (isEmpty(format)) return date
|
if (isEmpty(format)) return date
|
||||||
@ -253,15 +241,12 @@ const formatter = function (
|
|||||||
return dayjs(date).locale(lang).format(format)
|
return dayjs(date).locale(lang).format(format)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
defineOptions({
|
||||||
name: 'Picker',
|
name: 'Picker',
|
||||||
components: {
|
})
|
||||||
ElInput,
|
|
||||||
ElTooltip,
|
const props = defineProps(timePickerDefaultProps)
|
||||||
ElIcon,
|
const emit = defineEmits([
|
||||||
},
|
|
||||||
props: timePickerDefaultProps,
|
|
||||||
emits: [
|
|
||||||
'update:modelValue',
|
'update:modelValue',
|
||||||
'change',
|
'change',
|
||||||
'focus',
|
'focus',
|
||||||
@ -270,8 +255,8 @@ export default defineComponent({
|
|||||||
'panel-change',
|
'panel-change',
|
||||||
'visible-change',
|
'visible-change',
|
||||||
'keydown',
|
'keydown',
|
||||||
],
|
])
|
||||||
setup(props, ctx) {
|
|
||||||
const { lang } = useLocale()
|
const { lang } = useLocale()
|
||||||
|
|
||||||
const nsDate = useNamespace('date')
|
const nsDate = useNamespace('date')
|
||||||
@ -286,7 +271,7 @@ export default defineComponent({
|
|||||||
const inputRef = ref<HTMLElement | ComponentPublicInstance>()
|
const inputRef = ref<HTMLElement | ComponentPublicInstance>()
|
||||||
const pickerVisible = ref(false)
|
const pickerVisible = ref(false)
|
||||||
const pickerActualVisible = ref(false)
|
const pickerActualVisible = ref(false)
|
||||||
const valueOnOpen = ref(null)
|
const valueOnOpen = ref<TimePickerDefaultProps['modelValue'] | null>(null)
|
||||||
|
|
||||||
let hasJustTabExitedInput = false
|
let hasJustTabExitedInput = false
|
||||||
let ignoreFocusEvent = false
|
let ignoreFocusEvent = false
|
||||||
@ -301,30 +286,34 @@ export default defineComponent({
|
|||||||
valueOnOpen.value = props.modelValue
|
valueOnOpen.value = props.modelValue
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const emitChange = (val, isClear?: boolean) => {
|
const emitChange = (
|
||||||
|
val: TimePickerDefaultProps['modelValue'] | null,
|
||||||
|
isClear?: boolean
|
||||||
|
) => {
|
||||||
// determine user real change only
|
// determine user real change only
|
||||||
if (isClear || !valueEquals(val, valueOnOpen.value)) {
|
if (isClear || !valueEquals(val, valueOnOpen.value)) {
|
||||||
ctx.emit('change', val)
|
emit('change', val)
|
||||||
props.validateEvent &&
|
props.validateEvent &&
|
||||||
elFormItem.validate?.('change').catch((err) => debugWarn(err))
|
elFormItem.validate?.('change').catch((err) => debugWarn(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const emitInput = (val) => {
|
const emitInput = (input: SingleOrRange<DateModelType | Dayjs> | null) => {
|
||||||
if (!valueEquals(props.modelValue, val)) {
|
if (!valueEquals(props.modelValue, input)) {
|
||||||
let formatValue
|
let formatted
|
||||||
if (Array.isArray(val)) {
|
if (isArray(input)) {
|
||||||
formatValue = val.map((_) =>
|
formatted = input.map((item) =>
|
||||||
formatter(_, props.valueFormat, lang.value)
|
formatter(item, props.valueFormat, lang.value)
|
||||||
)
|
)
|
||||||
} else if (val) {
|
} else if (input) {
|
||||||
formatValue = formatter(val, props.valueFormat, lang.value)
|
formatted = formatter(input, props.valueFormat, lang.value)
|
||||||
}
|
}
|
||||||
ctx.emit('update:modelValue', val ? formatValue : val, lang.value)
|
emit('update:modelValue', input ? formatted : input, lang.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const emitKeydown = (e) => {
|
const emitKeydown = (e: KeyboardEvent) => {
|
||||||
ctx.emit('keydown', e)
|
emit('keydown', e)
|
||||||
}
|
}
|
||||||
|
|
||||||
const refInput = computed<HTMLInputElement[]>(() => {
|
const refInput = computed<HTMLInputElement[]>(() => {
|
||||||
if (inputRef.value) {
|
if (inputRef.value) {
|
||||||
const _r = isRangeInput.value
|
const _r = isRangeInput.value
|
||||||
@ -341,7 +330,7 @@ export default defineComponent({
|
|||||||
return refInput?.value[1]
|
return refInput?.value[1]
|
||||||
})
|
})
|
||||||
|
|
||||||
const setSelectionRange = (start, end, pos) => {
|
const setSelectionRange = (start: number, end: number, pos?: 'min' | 'max') => {
|
||||||
const _inputs = refInput.value
|
const _inputs = refInput.value
|
||||||
if (!_inputs.length) return
|
if (!_inputs.length) return
|
||||||
if (!pos || pos === 'min') {
|
if (!pos || pos === 'min') {
|
||||||
@ -365,7 +354,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
pickerVisible.value = visible
|
pickerVisible.value = visible
|
||||||
let result
|
let result
|
||||||
if (Array.isArray(date)) {
|
if (isArray(date)) {
|
||||||
result = date.map((_) => _.toDate())
|
result = date.map((_) => _.toDate())
|
||||||
} else {
|
} else {
|
||||||
// clear btn emit null
|
// clear btn emit null
|
||||||
@ -380,7 +369,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onShow = () => {
|
const onShow = () => {
|
||||||
ctx.emit('visible-change', true)
|
emit('visible-change', true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onKeydownPopperContent = (event: KeyboardEvent) => {
|
const onKeydownPopperContent = (event: KeyboardEvent) => {
|
||||||
@ -392,7 +381,7 @@ export default defineComponent({
|
|||||||
const onHide = () => {
|
const onHide = () => {
|
||||||
pickerActualVisible.value = false
|
pickerActualVisible.value = false
|
||||||
ignoreFocusEvent = false
|
ignoreFocusEvent = false
|
||||||
ctx.emit('visible-change', false)
|
emit('visible-change', false)
|
||||||
}
|
}
|
||||||
|
|
||||||
const focus = (focusStartInput = true, isIgnoreFocusEvent = false) => {
|
const focus = (focusStartInput = true, isIgnoreFocusEvent = false) => {
|
||||||
@ -406,7 +395,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleFocusInput = (e) => {
|
const handleFocusInput = (e?: FocusEvent) => {
|
||||||
if (
|
if (
|
||||||
props.readonly ||
|
props.readonly ||
|
||||||
pickerDisabled.value ||
|
pickerDisabled.value ||
|
||||||
@ -416,20 +405,19 @@ export default defineComponent({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
pickerVisible.value = true
|
pickerVisible.value = true
|
||||||
ctx.emit('focus', e)
|
emit('focus', e)
|
||||||
}
|
}
|
||||||
|
|
||||||
let currentHandleBlurDeferCallback: () => void | undefined
|
let currentHandleBlurDeferCallback: () => Promise<void> | undefined
|
||||||
|
|
||||||
// Check if document.activeElement is inside popper or any input before popper close
|
// Check if document.activeElement is inside popper or any input before popper close
|
||||||
const handleBlurInput = (e) => {
|
const handleBlurInput = (e?: FocusEvent) => {
|
||||||
const handleBlurDefer = async () => {
|
const handleBlurDefer = async () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (currentHandleBlurDeferCallback === handleBlurDefer) {
|
if (currentHandleBlurDeferCallback === handleBlurDefer) {
|
||||||
if (
|
if (
|
||||||
!(
|
!(
|
||||||
refPopper.value?.isFocusInsideContent() &&
|
refPopper.value?.isFocusInsideContent() && !hasJustTabExitedInput
|
||||||
!hasJustTabExitedInput
|
|
||||||
) &&
|
) &&
|
||||||
refInput.value.filter((input) => {
|
refInput.value.filter((input) => {
|
||||||
return input.contains(document.activeElement)
|
return input.contains(document.activeElement)
|
||||||
@ -437,7 +425,7 @@ export default defineComponent({
|
|||||||
) {
|
) {
|
||||||
handleChange()
|
handleChange()
|
||||||
pickerVisible.value = false
|
pickerVisible.value = false
|
||||||
ctx.emit('blur', e)
|
emit('blur', e)
|
||||||
props.validateEvent &&
|
props.validateEvent &&
|
||||||
elFormItem.validate?.('blur').catch((err) => debugWarn(err))
|
elFormItem.validate?.('blur').catch((err) => debugWarn(err))
|
||||||
}
|
}
|
||||||
@ -454,43 +442,44 @@ export default defineComponent({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const parsedValue = computed(() => {
|
const parsedValue = computed(() => {
|
||||||
let result
|
let dayOrDays: DayOrDays
|
||||||
if (valueIsEmpty.value) {
|
if (valueIsEmpty.value) {
|
||||||
if (pickerOptions.value.getDefaultValue) {
|
if (pickerOptions.value.getDefaultValue) {
|
||||||
result = pickerOptions.value.getDefaultValue()
|
dayOrDays = pickerOptions.value.getDefaultValue()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (Array.isArray(props.modelValue)) {
|
if (isArray(props.modelValue)) {
|
||||||
result = props.modelValue.map((_) =>
|
dayOrDays = props.modelValue.map((d) =>
|
||||||
parser(_, props.valueFormat, lang.value)
|
parseDate(d, props.valueFormat, lang.value)
|
||||||
)
|
) as [Dayjs, Dayjs]
|
||||||
} else {
|
} else {
|
||||||
result = parser(props.modelValue, props.valueFormat, lang.value)
|
dayOrDays = parseDate(props.modelValue, props.valueFormat, lang.value)!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pickerOptions.value.getRangeAvailableTime) {
|
if (pickerOptions.value.getRangeAvailableTime) {
|
||||||
const availableResult =
|
const availableResult = pickerOptions.value.getRangeAvailableTime(
|
||||||
pickerOptions.value.getRangeAvailableTime(result)
|
dayOrDays!
|
||||||
if (!isEqual(availableResult, result)) {
|
)
|
||||||
result = availableResult
|
if (!isEqual(availableResult, dayOrDays!)) {
|
||||||
|
dayOrDays = availableResult
|
||||||
emitInput(
|
emitInput(
|
||||||
Array.isArray(result)
|
(isArray(dayOrDays)
|
||||||
? result.map((_) => _.toDate())
|
? dayOrDays.map((_) => _.toDate())
|
||||||
: result.toDate()
|
: dayOrDays.toDate()) as SingleOrRange<Date>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (Array.isArray(result) && result.some((_) => !_)) {
|
if (isArray(dayOrDays!) && dayOrDays.some((day) => !day)) {
|
||||||
result = []
|
dayOrDays = [] as unknown as DayOrDays
|
||||||
}
|
}
|
||||||
return result
|
return dayOrDays!
|
||||||
})
|
})
|
||||||
|
|
||||||
const displayValue = computed(() => {
|
const displayValue = computed<UserInput>(() => {
|
||||||
if (!pickerOptions.value.panelReady) return
|
if (!pickerOptions.value.panelReady) return ''
|
||||||
const formattedValue = formatDayjsToString(parsedValue.value)
|
const formattedValue = formatDayjsToString(parsedValue.value)
|
||||||
if (Array.isArray(userInput.value)) {
|
if (isArray(userInput.value)) {
|
||||||
return [
|
return [
|
||||||
userInput.value[0] || (formattedValue && formattedValue[0]) || '',
|
userInput.value[0] || (formattedValue && formattedValue[0]) || '',
|
||||||
userInput.value[1] || (formattedValue && formattedValue[1]) || '',
|
userInput.value[1] || (formattedValue && formattedValue[1]) || '',
|
||||||
@ -498,8 +487,8 @@ export default defineComponent({
|
|||||||
} else if (userInput.value !== null) {
|
} else if (userInput.value !== null) {
|
||||||
return userInput.value
|
return userInput.value
|
||||||
}
|
}
|
||||||
if (!isTimePicker.value && valueIsEmpty.value) return
|
if (!isTimePicker.value && valueIsEmpty.value) return ''
|
||||||
if (!pickerVisible.value && valueIsEmpty.value) return
|
if (!pickerVisible.value && valueIsEmpty.value) return ''
|
||||||
if (formattedValue) {
|
if (formattedValue) {
|
||||||
return isDatesPicker.value
|
return isDatesPicker.value
|
||||||
? (formattedValue as Array<string>).join(', ')
|
? (formattedValue as Array<string>).join(', ')
|
||||||
@ -534,8 +523,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
const valueIsEmpty = computed(() => {
|
const valueIsEmpty = computed(() => {
|
||||||
return (
|
return (
|
||||||
!props.modelValue ||
|
!props.modelValue || (isArray(props.modelValue) && !props.modelValue.length)
|
||||||
(Array.isArray(props.modelValue) && !props.modelValue.length)
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
const onMouseDownInput = async (event: MouseEvent) => {
|
const onMouseDownInput = async (event: MouseEvent) => {
|
||||||
@ -569,10 +557,6 @@ export default defineComponent({
|
|||||||
|
|
||||||
const pickerSize = useSize()
|
const pickerSize = useSize()
|
||||||
|
|
||||||
const popperPaneRef = computed(() => {
|
|
||||||
return refPopper.value?.popperRef?.contentRef
|
|
||||||
})
|
|
||||||
|
|
||||||
const popperEl = computed(() => unref(refPopper)?.popperRef?.contentRef)
|
const popperEl = computed(() => unref(refPopper)?.popperRef?.contentRef)
|
||||||
const actualInputRef = computed(() => {
|
const actualInputRef = computed(() => {
|
||||||
if (unref(isRangeInput)) {
|
if (unref(isRangeInput)) {
|
||||||
@ -596,7 +580,7 @@ export default defineComponent({
|
|||||||
pickerVisible.value = false
|
pickerVisible.value = false
|
||||||
})
|
})
|
||||||
|
|
||||||
const userInput = ref(null)
|
const userInput = ref<UserInput>(null)
|
||||||
|
|
||||||
const handleChange = () => {
|
const handleChange = () => {
|
||||||
if (userInput.value) {
|
if (userInput.value) {
|
||||||
@ -604,9 +588,9 @@ export default defineComponent({
|
|||||||
if (value) {
|
if (value) {
|
||||||
if (isValidValue(value)) {
|
if (isValidValue(value)) {
|
||||||
emitInput(
|
emitInput(
|
||||||
Array.isArray(value)
|
(isArray(value)
|
||||||
? value.map((_) => _.toDate())
|
? value.map((_) => _.toDate())
|
||||||
: value.toDate()
|
: value.toDate()) as DateOrDates
|
||||||
)
|
)
|
||||||
userInput.value = null
|
userInput.value = null
|
||||||
}
|
}
|
||||||
@ -619,22 +603,22 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const parseUserInputToDayjs = (value) => {
|
const parseUserInputToDayjs = (value: UserInput) => {
|
||||||
if (!value) return null
|
if (!value) return null
|
||||||
return pickerOptions.value.parseUserInput(value)
|
return pickerOptions.value.parseUserInput!(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatDayjsToString = (value) => {
|
const formatDayjsToString = (value: DayOrDays) => {
|
||||||
if (!value) return null
|
if (!value) return null
|
||||||
return pickerOptions.value.formatToString(value)
|
return pickerOptions.value.formatToString!(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const isValidValue = (value) => {
|
const isValidValue = (value: DayOrDays) => {
|
||||||
return pickerOptions.value.isValidValue(value)
|
return pickerOptions.value.isValidValue!(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleKeydownInput = async (event) => {
|
const handleKeydownInput = async (event: KeyboardEvent) => {
|
||||||
const code = event.code
|
const { code } = event
|
||||||
emitKeydown(event)
|
emitKeydown(event)
|
||||||
if (code === EVENT_CODE.esc) {
|
if (code === EVENT_CODE.esc) {
|
||||||
if (pickerVisible.value === true) {
|
if (pickerVisible.value === true) {
|
||||||
@ -669,7 +653,7 @@ export default defineComponent({
|
|||||||
if (
|
if (
|
||||||
userInput.value === null ||
|
userInput.value === null ||
|
||||||
userInput.value === '' ||
|
userInput.value === '' ||
|
||||||
isValidValue(parseUserInputToDayjs(displayValue.value))
|
isValidValue(parseUserInputToDayjs(displayValue.value) as DayOrDays)
|
||||||
) {
|
) {
|
||||||
handleChange()
|
handleChange()
|
||||||
pickerVisible.value = false
|
pickerVisible.value = false
|
||||||
@ -687,7 +671,7 @@ export default defineComponent({
|
|||||||
pickerOptions.value.handleKeydownInput(event)
|
pickerOptions.value.handleKeydownInput(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const onUserInput = (e) => {
|
const onUserInput = (e: string) => {
|
||||||
userInput.value = e
|
userInput.value = e
|
||||||
// Temporary fix when the picker is dismissed and the input box
|
// Temporary fix when the picker is dismissed and the input box
|
||||||
// is focused, just mimic the behavior of antdesign.
|
// is focused, just mimic the behavior of antdesign.
|
||||||
@ -696,27 +680,34 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleStartInput = (event) => {
|
const handleStartInput = (event: Event) => {
|
||||||
|
const target = event.target as HTMLInputElement
|
||||||
if (userInput.value) {
|
if (userInput.value) {
|
||||||
userInput.value = [event.target.value, userInput.value[1]]
|
userInput.value = [target.value, userInput.value[1]]
|
||||||
} else {
|
} else {
|
||||||
userInput.value = [event.target.value, null]
|
userInput.value = [target.value, null]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleEndInput = (event) => {
|
const handleEndInput = (event: Event) => {
|
||||||
|
const target = event.target as HTMLInputElement
|
||||||
if (userInput.value) {
|
if (userInput.value) {
|
||||||
userInput.value = [userInput.value[0], event.target.value]
|
userInput.value = [userInput.value[0], target.value]
|
||||||
} else {
|
} else {
|
||||||
userInput.value = [null, event.target.value]
|
userInput.value = [null, target.value]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleStartChange = () => {
|
const handleStartChange = () => {
|
||||||
const value = parseUserInputToDayjs(userInput.value && userInput.value[0])
|
const values = userInput.value as string[]
|
||||||
|
const value = parseUserInputToDayjs(values && values[0]) as Dayjs
|
||||||
|
const parsedVal = unref(parsedValue) as [Dayjs, Dayjs]
|
||||||
if (value && value.isValid()) {
|
if (value && value.isValid()) {
|
||||||
userInput.value = [formatDayjsToString(value), displayValue.value[1]]
|
userInput.value = [
|
||||||
const newValue = [value, parsedValue.value && parsedValue.value[1]]
|
formatDayjsToString(value) as string,
|
||||||
|
displayValue.value?.[1] || null,
|
||||||
|
]
|
||||||
|
const newValue = [value, parsedVal && (parsedVal[1] || null)] as DayOrDays
|
||||||
if (isValidValue(newValue)) {
|
if (isValidValue(newValue)) {
|
||||||
emitInput(newValue)
|
emitInput(newValue)
|
||||||
userInput.value = null
|
userInput.value = null
|
||||||
@ -725,10 +716,15 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleEndChange = () => {
|
const handleEndChange = () => {
|
||||||
const value = parseUserInputToDayjs(userInput.value && userInput.value[1])
|
const values = unref(userInput) as string[]
|
||||||
|
const value = parseUserInputToDayjs(values && values[1]) as Dayjs
|
||||||
|
const parsedVal = unref(parsedValue) as [Dayjs, Dayjs]
|
||||||
if (value && value.isValid()) {
|
if (value && value.isValid()) {
|
||||||
userInput.value = [displayValue.value[0], formatDayjsToString(value)]
|
userInput.value = [
|
||||||
const newValue = [parsedValue.value && parsedValue.value[0], value]
|
unref(displayValue)?.[0] || null,
|
||||||
|
formatDayjsToString(value) as string,
|
||||||
|
]
|
||||||
|
const newValue = [parsedVal && parsedVal[0], value] as DayOrDays
|
||||||
if (isValidValue(newValue)) {
|
if (isValidValue(newValue)) {
|
||||||
emitInput(newValue)
|
emitInput(newValue)
|
||||||
userInput.value = null
|
userInput.value = null
|
||||||
@ -744,64 +740,38 @@ export default defineComponent({
|
|||||||
pickerOptions.value.panelReady = true
|
pickerOptions.value.panelReady = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const onCalendarChange = (e) => {
|
const onCalendarChange = (e: [Date, false | Date]) => {
|
||||||
ctx.emit('calendar-change', e)
|
emit('calendar-change', e)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onPanelChange = (value, mode, view) => {
|
const onPanelChange = (
|
||||||
ctx.emit('panel-change', value, mode, view)
|
value: [Dayjs, Dayjs],
|
||||||
|
mode: 'month' | 'year',
|
||||||
|
view: unknown
|
||||||
|
) => {
|
||||||
|
emit('panel-change', value, mode, view)
|
||||||
}
|
}
|
||||||
|
|
||||||
provide('EP_PICKER_BASE', {
|
provide('EP_PICKER_BASE', {
|
||||||
props,
|
props,
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
defineExpose({
|
||||||
nsDate,
|
/**
|
||||||
nsInput,
|
* @description focus input box.
|
||||||
nsRange,
|
*/
|
||||||
// injected popper options
|
|
||||||
elPopperOptions,
|
|
||||||
|
|
||||||
isDatesPicker,
|
|
||||||
handleEndChange,
|
|
||||||
handleStartChange,
|
|
||||||
handleStartInput,
|
|
||||||
handleEndInput,
|
|
||||||
onUserInput,
|
|
||||||
handleChange,
|
|
||||||
handleKeydownInput,
|
|
||||||
popperPaneRef,
|
|
||||||
onClickOutside,
|
|
||||||
pickerSize,
|
|
||||||
isRangeInput,
|
|
||||||
onMouseDownInput,
|
|
||||||
onMouseLeave,
|
|
||||||
onMouseEnter,
|
|
||||||
onTouchStartInput,
|
|
||||||
onClearIconClick,
|
|
||||||
showClose,
|
|
||||||
triggerIcon,
|
|
||||||
onPick,
|
|
||||||
handleFocusInput,
|
|
||||||
handleBlurInput,
|
|
||||||
pickerVisible,
|
|
||||||
pickerActualVisible,
|
|
||||||
displayValue,
|
|
||||||
parsedValue,
|
|
||||||
setSelectionRange,
|
|
||||||
refPopper,
|
|
||||||
inputRef,
|
|
||||||
pickerDisabled,
|
|
||||||
onSetPickerOption,
|
|
||||||
onCalendarChange,
|
|
||||||
onPanelChange,
|
|
||||||
focus,
|
focus,
|
||||||
onShow,
|
/**
|
||||||
onBeforeShow,
|
* @description emit focus event
|
||||||
onHide,
|
*/
|
||||||
onKeydownPopperContent,
|
handleFocusInput,
|
||||||
}
|
/**
|
||||||
},
|
* @description emit blur event
|
||||||
|
*/
|
||||||
|
handleBlurInput,
|
||||||
|
/**
|
||||||
|
* @description pick item manually
|
||||||
|
*/
|
||||||
|
onPick,
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,28 +1,32 @@
|
|||||||
import { isValidComponentSize } from '@element-plus/utils'
|
import { buildProps, definePropType } from '@element-plus/utils'
|
||||||
|
import { useSizeProp } from '@element-plus/hooks'
|
||||||
import { CircleClose } from '@element-plus/icons-vue'
|
import { CircleClose } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
import type { Component, PropType } from 'vue'
|
import type { Component, ExtractPropTypes } from 'vue'
|
||||||
import type { Options } from '@popperjs/core'
|
import type { Options } from '@popperjs/core'
|
||||||
import type { ComponentSize } from '@element-plus/constants'
|
import type { Dayjs } from 'dayjs'
|
||||||
|
|
||||||
export const timePickerDefaultProps = {
|
export type SingleOrRange<T> = T | [T, T]
|
||||||
|
export type DateModelType = number | string | Date
|
||||||
|
export type ModelValueType = SingleOrRange<DateModelType>
|
||||||
|
export type DayOrDays = SingleOrRange<Dayjs>
|
||||||
|
export type DateOrDates = SingleOrRange<Date>
|
||||||
|
export type UserInput = SingleOrRange<string | null>
|
||||||
|
|
||||||
|
export const timePickerDefaultProps = buildProps({
|
||||||
id: {
|
id: {
|
||||||
type: [Array, String],
|
type: definePropType<SingleOrRange<string>>([Array, String]),
|
||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
type: [Array, String],
|
type: definePropType<SingleOrRange<string>>([Array, String]),
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
popperClass: {
|
popperClass: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
format: {
|
format: String,
|
||||||
type: String,
|
valueFormat: String,
|
||||||
},
|
|
||||||
valueFormat: {
|
|
||||||
type: String as PropType<string>,
|
|
||||||
},
|
|
||||||
type: {
|
type: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
@ -32,7 +36,7 @@ export const timePickerDefaultProps = {
|
|||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
clearIcon: {
|
clearIcon: {
|
||||||
type: [String, Object] as PropType<string | Component>,
|
type: definePropType<string | Component>([String, Object]),
|
||||||
default: CircleClose,
|
default: CircleClose,
|
||||||
},
|
},
|
||||||
editable: {
|
editable: {
|
||||||
@ -40,13 +44,10 @@ export const timePickerDefaultProps = {
|
|||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
prefixIcon: {
|
prefixIcon: {
|
||||||
type: [String, Object] as PropType<string | Component>,
|
type: definePropType<string | Component>([String, Object]),
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
size: {
|
size: useSizeProp,
|
||||||
type: String as PropType<ComponentSize>,
|
|
||||||
validator: isValidComponentSize,
|
|
||||||
},
|
|
||||||
readonly: {
|
readonly: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
@ -60,13 +61,11 @@ export const timePickerDefaultProps = {
|
|||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
popperOptions: {
|
popperOptions: {
|
||||||
type: Object as PropType<Partial<Options>>,
|
type: definePropType<Partial<Options>>(Object),
|
||||||
default: () => ({}),
|
default: () => ({}),
|
||||||
},
|
},
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: [Date, Array, String, Number] as PropType<
|
type: definePropType<ModelValueType>([Date, Array, String, Number]),
|
||||||
number | string | Date | (number | string | Date)[]
|
|
||||||
>,
|
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
rangeSeparator: {
|
rangeSeparator: {
|
||||||
@ -76,10 +75,10 @@ export const timePickerDefaultProps = {
|
|||||||
startPlaceholder: String,
|
startPlaceholder: String,
|
||||||
endPlaceholder: String,
|
endPlaceholder: String,
|
||||||
defaultValue: {
|
defaultValue: {
|
||||||
type: [Date, Array] as PropType<Date | Date[]>,
|
type: definePropType<SingleOrRange<Date>>([Date, Array]),
|
||||||
},
|
},
|
||||||
defaultTime: {
|
defaultTime: {
|
||||||
type: [Date, Array] as PropType<Date | Date[]>,
|
type: definePropType<SingleOrRange<Date>>([Date, Array]),
|
||||||
},
|
},
|
||||||
isRange: {
|
isRange: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@ -113,7 +112,7 @@ export const timePickerDefaultProps = {
|
|||||||
default: undefined,
|
default: undefined,
|
||||||
},
|
},
|
||||||
tabindex: {
|
tabindex: {
|
||||||
type: [String, Number],
|
type: definePropType<string | number>([String, Number]),
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
validateEvent: {
|
validateEvent: {
|
||||||
@ -121,4 +120,20 @@ export const timePickerDefaultProps = {
|
|||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
unlinkPanels: Boolean,
|
unlinkPanels: Boolean,
|
||||||
|
} as const)
|
||||||
|
|
||||||
|
export type TimePickerDefaultProps = ExtractPropTypes<
|
||||||
|
typeof timePickerDefaultProps
|
||||||
|
>
|
||||||
|
|
||||||
|
export interface PickerOptions {
|
||||||
|
isValidValue: (date: DayOrDays) => boolean
|
||||||
|
handleKeydownInput: (event: KeyboardEvent) => void
|
||||||
|
parseUserInput: (value: UserInput) => DayOrDays
|
||||||
|
formatToString: (value: DayOrDays) => UserInput
|
||||||
|
getRangeAvailableTime: (date: DayOrDays) => DayOrDays
|
||||||
|
getDefaultValue: () => DayOrDays
|
||||||
|
panelReady: boolean
|
||||||
|
handleClear: () => void
|
||||||
|
handleFocusPicker?: () => void
|
||||||
}
|
}
|
||||||
|
@ -258,7 +258,7 @@ const getRangeAvailableTime = ([start, end]: Array<Dayjs>) => {
|
|||||||
return [
|
return [
|
||||||
getAvailableTime(start, 'start', true, end),
|
getAvailableTime(start, 'start', true, end),
|
||||||
getAvailableTime(end, 'end', false, start),
|
getAvailableTime(end, 'end', false, start),
|
||||||
]
|
] as const
|
||||||
}
|
}
|
||||||
|
|
||||||
const { getAvailableHours, getAvailableMinutes, getAvailableSeconds } =
|
const { getAvailableHours, getAvailableMinutes, getAvailableSeconds } =
|
||||||
|
@ -1,55 +0,0 @@
|
|||||||
import { defineComponent, h, provide, ref } from 'vue'
|
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import customParseFormat from 'dayjs/plugin/customParseFormat.js'
|
|
||||||
import { DEFAULT_FORMATS_TIME } from './common/constant'
|
|
||||||
import Picker from './common/picker.vue'
|
|
||||||
import TimePickPanel from './time-picker-com/panel-time-pick.vue'
|
|
||||||
import TimeRangePanel from './time-picker-com/panel-time-range.vue'
|
|
||||||
import { timePickerDefaultProps } from './common/props'
|
|
||||||
dayjs.extend(customParseFormat)
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'ElTimePicker',
|
|
||||||
install: null,
|
|
||||||
props: {
|
|
||||||
...timePickerDefaultProps,
|
|
||||||
isRange: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
emits: ['update:modelValue'],
|
|
||||||
setup(props, ctx) {
|
|
||||||
const commonPicker = ref(null)
|
|
||||||
const type = props.isRange ? 'timerange' : 'time'
|
|
||||||
const panel = props.isRange ? TimeRangePanel : TimePickPanel
|
|
||||||
const refProps = {
|
|
||||||
...props,
|
|
||||||
focus: () => {
|
|
||||||
commonPicker.value?.handleFocusInput()
|
|
||||||
},
|
|
||||||
blur: () => {
|
|
||||||
commonPicker.value?.handleBlurInput()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
provide('ElPopperOptions', props.popperOptions)
|
|
||||||
ctx.expose(refProps)
|
|
||||||
return () => {
|
|
||||||
const format = props.format ?? DEFAULT_FORMATS_TIME
|
|
||||||
return h(
|
|
||||||
Picker,
|
|
||||||
{
|
|
||||||
...props, // allow format to be overwrite
|
|
||||||
format,
|
|
||||||
type,
|
|
||||||
ref: commonPicker,
|
|
||||||
'onUpdate:modelValue': (value) =>
|
|
||||||
ctx.emit('update:modelValue', value),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
default: (scopedProps) => h(panel, scopedProps),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
63
packages/components/time-picker/src/time-picker.tsx
Normal file
63
packages/components/time-picker/src/time-picker.tsx
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import { defineComponent, provide, ref } from 'vue'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import customParseFormat from 'dayjs/plugin/customParseFormat.js'
|
||||||
|
import { DEFAULT_FORMATS_TIME } from './common/constant'
|
||||||
|
import Picker from './common/picker.vue'
|
||||||
|
import TimePickPanel from './time-picker-com/panel-time-pick.vue'
|
||||||
|
import TimeRangePanel from './time-picker-com/panel-time-range.vue'
|
||||||
|
import { timePickerDefaultProps } from './common/props'
|
||||||
|
dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'ElTimePicker',
|
||||||
|
install: null,
|
||||||
|
props: {
|
||||||
|
...timePickerDefaultProps,
|
||||||
|
isRange: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ['update:modelValue'],
|
||||||
|
setup(props, ctx) {
|
||||||
|
const commonPicker = ref<InstanceType<typeof Picker>>()
|
||||||
|
const [type, Panel] = props.isRange
|
||||||
|
? ['timerange', TimeRangePanel]
|
||||||
|
: ['time', TimePickPanel]
|
||||||
|
|
||||||
|
const modelUpdater = (value: any) => ctx.emit('update:modelValue', value)
|
||||||
|
provide('ElPopperOptions', props.popperOptions)
|
||||||
|
ctx.expose({
|
||||||
|
/**
|
||||||
|
* @description focus on the input element
|
||||||
|
*/
|
||||||
|
focus: (e: FocusEvent | undefined) => {
|
||||||
|
commonPicker.value?.handleFocusInput(e)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @description blur from the input element
|
||||||
|
*/
|
||||||
|
blur: (e: FocusEvent | undefined) => {
|
||||||
|
commonPicker.value?.handleBlurInput(e)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
const format = props.format ?? DEFAULT_FORMATS_TIME
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Picker
|
||||||
|
{...props}
|
||||||
|
ref={commonPicker}
|
||||||
|
type={type}
|
||||||
|
format={format}
|
||||||
|
onUpdate:modelValue={modelUpdater}
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
default: (props: any) => <Panel {...props} />,
|
||||||
|
}}
|
||||||
|
</Picker>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
Loading…
Reference in New Issue
Block a user