Skip to content

Commit

Permalink
fix(comp:time-picker,date-picker): missing parts from input format sh…
Browse files Browse the repository at this point in the history
…ould be applied by prop value (#1857)
  • Loading branch information
sallerli1 committed Mar 5, 2024
1 parent c82e28b commit 6a75ae2
Show file tree
Hide file tree
Showing 14 changed files with 316 additions and 187 deletions.
5 changes: 3 additions & 2 deletions packages/components/config/src/dateConfig.ts
Expand Up @@ -83,7 +83,7 @@ export type DateConfig<P = number | Date, R = Date> = {
isValid: (date: unknown) => boolean

format: (date: P, format: string) => string
parse: (dateString: string, format: string) => R
parse: (dateString: string, format: string, referenceDate?: Date | number) => R
convert: (date: unknown, format: string) => R

getLocalizedLabels: (
Expand Down Expand Up @@ -111,7 +111,8 @@ export function useDateConfig(): DateConfig<number | Date, Date> {
function createDefaultDateConfig(): DateConfig<number | Date, Date> {
const locale = useGlobalConfig('locale')
const now = () => new Date()
const parse = (dateString: string, format: string) => parseDate(dateString, format, now(), { locale: locale.date })
const parse = (dateString: string, format: string, referenceDate?: Date | number) =>
parseDate(dateString, format, referenceDate ?? now(), { locale: locale.date })
return {
now,
weekStartsOn: () => locale.date.options?.weekStartsOn ?? 1,
Expand Down
10 changes: 8 additions & 2 deletions packages/components/date-picker/__tests__/datePicker.spec.ts
Expand Up @@ -133,9 +133,10 @@ describe('DatePicker', () => {
const onInput = vi.fn()
const onChange = vi.fn()
const onUpdateValue = vi.fn()
const defaultValue = new Date('2021-10-01')
const wrapper = DatePickerMount({
props: {
value: new Date('2021-10-01'),
value: defaultValue,
format: 'yyyy-MM-dd',
onInput,
onChange,
Expand All @@ -147,7 +148,12 @@ describe('DatePicker', () => {
expect(onInput).toBeCalled()

const newDate = parse('2021-10-11', 'yyyy-MM-dd', new Date())
expect(onChange).toBeCalledWith(newDate, new Date('2021-10-01'))
newDate.setHours(defaultValue.getHours())
newDate.setMinutes(defaultValue.getMinutes())
newDate.setSeconds(defaultValue.getSeconds())
newDate.setMilliseconds(defaultValue.getMilliseconds())

expect(onChange).toBeCalledWith(newDate, defaultValue)
expect(onUpdateValue).toBeCalledWith(newDate)
})

Expand Down
95 changes: 75 additions & 20 deletions packages/components/date-picker/src/composables/useControl.ts
Expand Up @@ -5,14 +5,15 @@
* found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE
*/

import type { DateConfig, DateConfigType, TimeConfigType } from '@idux/components/config'

import { type ComputedRef, type Ref, watch } from 'vue'

import { useState } from '@idux/cdk/utils'
import { type DateConfig } from '@idux/components/config'
import { applyDateTime, convertToDate, isSameDateTime } from '@idux/components/utils'

import { type FormatContext } from './useFormat'
import { type DateInputEnabledStatus, type FormatContext, useDateInputEnabledStatus } from './useFormat'
import { type InputEnableStatus } from './useInputEnableStatus'
import { applyDateTime, convertToDate, isSameDateTime } from '../utils'

export interface PickerControlContext {
inputValue: ComputedRef<string>
Expand Down Expand Up @@ -45,7 +46,8 @@ export function useControl(
valueRef: Ref<string | number | Date | undefined>,
handleChange: (value: Date | undefined) => void,
): PickerControlContext {
const { formatRef, dateFormatRef, timeFormatRef } = formatContext
const { formatRef, dateFormatRef, timeFormatRef, hourEnabled, minuteEnabled, secondEnabled, use12Hours } =
formatContext

const [inputValue, setInputValue] = useState<string>('')
const [dateInputValue, setDateInputValue] = useState<string>('')
Expand All @@ -55,6 +57,9 @@ export function useControl(
const [dateInputFocused, setDateInputFocused] = useState(false)
const [timeInputFocused, setTimeInputFocused] = useState(false)

const dateInputEnableStatus = useDateInputEnabledStatus(formatRef)
const innerDateInputEnableStatus = useDateInputEnabledStatus(dateFormatRef)

function initInputValue(currValue: Date | undefined, force = false) {
if (!currValue) {
setInputValue('')
Expand All @@ -63,7 +68,11 @@ export function useControl(

const { parse, format } = dateConfig

if (force || parse(inputValue.value, formatRef.value).valueOf() !== currValue.valueOf()) {
if (
force ||
parse(inputValue.value, formatRef.value).valueOf() !==
parse(format(currValue, formatRef.value), formatRef.value).valueOf()
) {
setInputValue(format(currValue, formatRef.value))
}
}
Expand Down Expand Up @@ -107,52 +116,98 @@ export function useControl(
watch([valueRef, formatRef], () => init(), { immediate: true })
watch(inputEnableStatus, () => init())

function parseInput(value: string, format: string) {
return value ? dateConfig.parse(value, format) : undefined
function parseInput(value: string, format: string, referenceDate: Date | undefined) {
return value ? dateConfig.parse(value, format, referenceDate) : undefined
}
function checkInputValid(date: Date | undefined) {
return !date || dateConfig.isValid(date)
}

enum DateTimeAdjustType {
input,
timeInput,
dateInput,
}
function getDatePartAdjustTypes(enableStatus: DateInputEnabledStatus) {
const { yearEnabled, monthEnabled, dateEnabled } = enableStatus

return [!yearEnabled && 'year', !monthEnabled && 'month', !dateEnabled && 'date'].filter(Boolean) as (
| DateConfigType
| TimeConfigType
)[]
}
function getTimePartAdjustTypes() {
const _hourEnabled = hourEnabled.value || use12Hours.value
return [
!_hourEnabled && 'hour',
!minuteEnabled.value && 'minute',
!secondEnabled.value && 'second',
'millisecond',
].filter(Boolean) as (DateConfigType | TimeConfigType)[]
}
function adjustDateTimeOfInput(value: Date | undefined, propValue: Date | undefined, type: DateTimeAdjustType) {
if (!value || !propValue) {
return value
}

let typesToApply: (DateConfigType | TimeConfigType)[]

if (type === DateTimeAdjustType.dateInput) {
typesToApply = [
...getDatePartAdjustTypes(innerDateInputEnableStatus.value),
'hour',
'minute',
'second',
'millisecond',
]
} else if (type === DateTimeAdjustType.timeInput) {
typesToApply = ['year', 'month', 'date', ...getTimePartAdjustTypes()]
} else {
typesToApply = [...getDatePartAdjustTypes(dateInputEnableStatus.value), ...getTimePartAdjustTypes()]
}

return applyDateTime(dateConfig, propValue, value, typesToApply)
}

function handleInput(evt: Event) {
const value = (evt.target as HTMLInputElement).value
const referenceValue = convertToDate(dateConfig, valueRef.value, formatRef.value)

setInputValue(value)
const currDate = parseInput(value, formatRef.value)
if (checkInputValid(currDate)) {
handleChange(currDate)
let currDate = parseInput(value, formatRef.value, referenceValue)
if (!checkInputValid(currDate)) {
return
}

currDate = adjustDateTimeOfInput(currDate, referenceValue, DateTimeAdjustType.input)
handleChange(currDate)
}
function handleDateInput(evt: Event) {
const value = (evt.target as HTMLInputElement).value
const referenceValue = convertToDate(dateConfig, valueRef.value, formatRef.value)

setDateInputValue(value)
let currDate = parseInput(value, dateFormatRef.value)
let currDate = parseInput(value, dateFormatRef.value, referenceValue)
if (!checkInputValid(currDate)) {
return
}

const accessorValue = convertToDate(dateConfig, valueRef.value, formatRef.value)
if (currDate && accessorValue) {
currDate = applyDateTime(dateConfig, accessorValue, currDate, ['hour', 'minute', 'second'])
}
currDate = adjustDateTimeOfInput(currDate, referenceValue, DateTimeAdjustType.dateInput)

handleChange(currDate)
setVisiblePanel('datePanel')
}
function handleTimeInput(evt: Event) {
const value = (evt.target as HTMLInputElement).value
const referenceValue = convertToDate(dateConfig, valueRef.value, formatRef.value)

setTimeInputValue(value)
let currDate = parseInput(value, timeFormatRef.value)
let currDate = parseInput(value, timeFormatRef.value, referenceValue)
if (!checkInputValid(currDate)) {
return
}

const accessorValue = convertToDate(dateConfig, valueRef.value, formatRef.value)
if (currDate && accessorValue) {
currDate = applyDateTime(dateConfig, accessorValue, currDate, ['year', 'month', 'date'])
}
currDate = adjustDateTimeOfInput(currDate, referenceValue, DateTimeAdjustType.timeInput)

handleChange(currDate)
setVisiblePanel('timePanel')
Expand Down
18 changes: 18 additions & 0 deletions packages/components/date-picker/src/composables/useFormat.ts
Expand Up @@ -21,6 +21,12 @@ interface TimePanelEnabledStatus {
use12Hours: ComputedRef<boolean>
}

export interface DateInputEnabledStatus {
yearEnabled: boolean
monthEnabled: boolean
dateEnabled: boolean
}

export interface FormatContext extends Omit<TimePanelEnabledStatus, 'hourUse12Hours'> {
formatRef: ComputedRef<string>
dateFormatRef: ComputedRef<string>
Expand Down Expand Up @@ -94,3 +100,15 @@ function useTimePanelEnabledStatus(
use12Hours: computed(() => /[aA]/.test(_formatRef.value)),
}
}

export function useDateInputEnabledStatus(formatRef: ComputedRef<string>): ComputedRef<DateInputEnabledStatus> {
return computed(() => {
const format = formatRef.value

return {
yearEnabled: /[yYR]/.test(format),
monthEnabled: /[MQID]/.test(format),
dateEnabled: /[dDiEec]/.test(format),
}
})
}
Expand Up @@ -13,9 +13,9 @@ import { type FormAccessor, ValidateStatus, useAccessorAndControl } from '@idux/
import { callEmit, convertArray } from '@idux/cdk/utils'
import { type DateConfig } from '@idux/components/config'
import { FormSize, useFormItemRegister, useFormSize, useFormStatus } from '@idux/components/form'
import { convertToDate, sortRangeValue } from '@idux/components/utils'

import { type DatePickerProps, type DateRangePickerProps } from '../types'
import { convertToDate, sortRangeValue } from '../utils'

type StateValueType<T extends DatePickerProps | DateRangePickerProps> = T extends DatePickerProps
? Date | undefined
Expand Down
Expand Up @@ -11,17 +11,12 @@ import type { DateConfig, DateConfigType, TimeConfigType } from '@idux/component
import { type ComputedRef, type Ref, computed, watch } from 'vue'

import { convertArray, useState } from '@idux/cdk/utils'
import { adjustRangeValue, compareDateTime, convertToDate, sortRangeValue } from '@idux/components/utils'

import { type PickerControlContext, useControl } from './useControl'
import { type FormatContext } from './useFormat'
import { type InputEnableStatus } from './useInputEnableStatus'
import {
adjustRangeValue,
compareDateTime,
convertPickerTypeToConfigType,
convertToDate,
sortRangeValue,
} from '../utils'
import { convertPickerTypeToConfigType } from '../utils'

export interface PickerRangeControlContext {
buffer: ComputedRef<(Date | undefined)[] | undefined>
Expand Down
Expand Up @@ -11,8 +11,9 @@ import type { DateConfig } from '@idux/components/config'
import { type ComputedRef, computed, watch } from 'vue'

import { callEmit, convertArray, useState } from '@idux/cdk/utils'
import { adjustRangeValue, sortRangeValue } from '@idux/components/utils'

import { adjustRangeValue, convertPickerTypeToConfigType, sortRangeValue } from '../utils'
import { convertPickerTypeToConfigType } from '../utils'

export interface RangePanelStateContext {
panelValue: ComputedRef<(Date | undefined)[] | undefined>
Expand Down
9 changes: 7 additions & 2 deletions packages/components/date-picker/src/panel/Panel.tsx
Expand Up @@ -13,11 +13,12 @@ import { ɵTimePanel } from '@idux/components/_private/time-panel'
import { useDateConfig, useGlobalConfig } from '@idux/components/config'
import { useThemeToken } from '@idux/components/theme'
import { getTimePickerThemeTokens } from '@idux/components/time-picker'
import { applyDateTime } from '@idux/components/utils'

import { getThemeTokens } from '../../theme'
import { useActiveValue } from '../composables/useActiveValue'
import { datePanelProps } from '../types'
import { applyDateTime, convertPickerTypeToConfigType } from '../utils'
import { convertPickerTypeToConfigType } from '../utils'

export default defineComponent({
name: 'IxDatePanel',
Expand All @@ -38,7 +39,11 @@ export default defineComponent({
callEmit(props.onChange, value)
}
function handleDatePanelChange(value: Date) {
handleChange(props.value ? applyDateTime(dateConfig, props.value, value, ['hour', 'minute', 'second']) : value)
handleChange(
props.value
? applyDateTime(dateConfig, props.value, value, ['hour', 'minute', 'second', 'millisecond'])
: value,
)
}
function handleTimePanelChange(value: Date) {
handleChange(props.value ? applyDateTime(dateConfig, props.value, value, ['year', 'month', 'date']) : value)
Expand Down

0 comments on commit 6a75ae2

Please sign in to comment.