feat(time-picker): add value-format, formatted-value, on-update:formatted-value & default-formatted-value props

This commit is contained in:
07akioni 2022-01-11 02:51:12 +08:00
parent b4f646339b
commit 8770ae4c4c
9 changed files with 227 additions and 90 deletions

View File

@ -53,6 +53,10 @@
- `n-auto-complete` adds `render-label` prop, closes [#1629](https://github.com/TuSimple/naive-ui/issues/1629).
- `n-tree` adds `render-switcher-icon` prop, closes [#1551](https://github.com/TuSimple/naive-ui/issues/1551).
- `n-message` export `MessageType` type.
- `n-time-picker` adds `value-format` prop.
- `n-time-picker` adds `formatted-value` prop.
- `n-time-picker` adds `on-update:formatted-value` prop.
- `n-time-picker` adds `default-formatted-value` prop.
## 2.23.2 (2021-12-29)

View File

@ -53,6 +53,10 @@
- `n-auto-complete` 新增 `render-label` 属性,关闭 [#1629](https://github.com/TuSimple/naive-ui/issues/1629)
- `n-tree` 新增 `render-switcher-icon` 属性,关闭 [#1551](https://github.com/TuSimple/naive-ui/issues/1551)
- `n-message` 导出 `MessageType` 类型
- `n-time-picker` 新增 `value-format` 属性
- `n-time-picker` 新增 `formatted-value` 属性
- `n-time-picker` 新增 `on-update:formatted-value` 属性
- `n-time-picker` 新增 `default-formatted-value` 属性
## 2.23.2 (2021-12-29)

View File

@ -1,9 +1,11 @@
# TODO
## DataTable
## Urgent
- (moderate) Add fast path for virtual mode if no cell crosses rows
- (moderate) Multiple column sorting
- message render XXX
- carousel refactor arrow style
## DataTable
## Form
@ -11,30 +13,23 @@
## Popover
- (easy) Add `header` slot
## Radio
- (easy) Button custom align & width
## Select
- (moderate) `render-tag` prop
## Tabs
- (moderate) Add iOS styled tabs
- (moderate) Left, right tabs
## Tree
- (moderate) Use set to check if node is checked or selected internally
- (moderate) Leaf-only check
## TreeSelect
- (moderate) Async
- (easy) Leaf-only check
## DatePicker

View File

@ -0,0 +1,25 @@
<markdown>
# Use formatted value
Use `formatted-value` to control formatted value.
</markdown>
<template>
<pre>{{ formattedValue }}</pre>
<n-time-picker
v-model:formatted-value="formattedValue"
value-format="H~m~s"
/>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
export default defineComponent({
setup () {
return {
formattedValue: ref('4~1~8')
}
}
})
</script>

View File

@ -12,30 +12,35 @@ step-time
format
actions
hours12
formatted.vue
```
## API
### TimePicker Props
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| actions | `Array<'now' \| 'confirm'> \| null` | `['now', 'confirm']` | Operations supported by the Time Picker. |
| clearable | `boolean` | `false` | Whether the value is clearable. |
| default-value | `number \| null` | `null` | Default value. |
| disabled | `boolean` | `false` | Disabled state. |
| format | `string` | `'HH:mm:ss'` | Time format. For possible formats see [date-fns.org](https://date-fns.org/v2.23.0/docs/format). |
| hours | `number \| number[]` | `undefined` | The array of hours that can be selected. If a number, it'll be converted into an array of numbers using that increment. |
| minutes | `number \| number[]` | `undefined` | The array of minutes that can be selected. If a number, it'll be converted into an array of numbers using that increment. |
| seconds | `number \| number[]` | `undefined` | The array of seconds that can be selected. If a number, it'll be converted into an array of numbers using that increment. |
| input-readonly | `boolean` | `false` | Readonly state (does not apply to touch devices). |
| is-hour-disabled | `(hour: number) => boolean` | `() => false` | Callback function for disabling hours. |
| is-minute-disabled | `(minute: number, hour: number) => boolean` | `() => false` | Callback function for disabling minutes. |
| is-second-disabled | `(second: number, minute: number, hour: number) => boolean` | `() => false` | Callback function for disabling seconds. |
| placeholder | `string` | `'Select Time'` | Placeholder. |
| size | `'small' \| 'medium' \| 'large'` | `'medium'` | Size. |
| use-12-hours | `boolean` | `false` | Whether to use a 12-hour clock panel. |
| value | `number \| null` | `undefined` | Value when being set manually. |
| on-blur | `() => void` | `undefined` | Callback when the selection box loses focus. |
| on-focus | `() => void` | `undefined` | Callback when the selection box gets focus. |
| on-update:value | `(value: number \| null) => void` | `undefined` | Callback when the value changes. |
| Name | Type | Default | Description | Version |
| --- | --- | --- | --- | --- |
| actions | `Array<'now' \| 'confirm'> \| null` | `['now', 'confirm']` | Operations supported by the Time Picker. | |
| clearable | `boolean` | `false` | Whether the value is clearable. | |
| default-value | `number \| null` | `null` | Default value. | |
| default-formatted-value | `number \| null` | `undefined` | Default formatted value. | NEXT_VERSION |
| disabled | `boolean` | `false` | Disabled state. | |
| format | `string` | `'HH:mm:ss'` | Time format. For possible formats see [date-fns.org](https://date-fns.org/v2.23.0/docs/format). | |
| formatted-value | `string \| null` | `undefined` | Formatted value. | NEXT_VERSION |
| hours | `number \| number[]` | `undefined` | The array of hours that can be selected. If a number, it'll be converted into an array of numbers using that increment. | |
| minutes | `number \| number[]` | `undefined` | The array of minutes that can be selected. If a number, it'll be converted into an array of numbers using that increment. | |
| seconds | `number \| number[]` | `undefined` | The array of seconds that can be selected. If a number, it'll be converted into an array of numbers using that increment. | |
| input-readonly | `boolean` | `false` | Readonly state (does not apply to touch devices). | |
| is-hour-disabled | `(hour: number) => boolean` | `() => false` | Callback function for disabling hours. | |
| is-minute-disabled | `(minute: number, hour: number) => boolean` | `() => false` | Callback function for disabling minutes. | |
| is-second-disabled | `(second: number, minute: number, hour: number) => boolean` | `() => false` | Callback function for disabling seconds. | |
| placeholder | `string` | `'Select Time'` | Placeholder. | |
| size | `'small' \| 'medium' \| 'large'` | `'medium'` | Size. | |
| use-12-hours | `boolean` | `false` | Whether to use a 12-hour clock panel. | |
| value | `number \| null` | `undefined` | Value when being set manually. | |
| value-format | `string` | follows `format` | Format of formatted value. | NEXT_VERSION |
| on-blur | `() => void` | `undefined` | Callback when the selection box loses focus. | |
| on-focus | `() => void` | `undefined` | Callback when the selection box gets focus. | |
| on-update:formatted-value | `(value: number \| null, timestampValue: number \| null) => void` | `undefined` | Callback when formatted value changes. | NEXT_VERSION |
| on-update:value | `(value: number \| null, formattedValue: string \| null) => void` | `undefined` | Callback when the value changes. | `formattedValue` NEXT_VERSION |

View File

@ -0,0 +1,25 @@
<markdown>
# 使用格式化后的值
你可以使用 `formatted-value` 控制格式化后的值
</markdown>
<template>
<pre>{{ formattedValue }}</pre>
<n-time-picker
v-model:formatted-value="formattedValue"
value-format="H~m~s"
/>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
export default defineComponent({
setup () {
return {
formattedValue: ref('4~1~8')
}
}
})
</script>

View File

@ -12,30 +12,35 @@ step-time
format
actions
hours12
formatted.vue
```
## API
### TimePicker Props
| 名称 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| actions | `Array<'now' \| 'confirm'> \| null` | `['now', 'confirm']` | Time Picker 中支持的操作 |
| clearable | `boolean` | `false` | 是否可清空 |
| default-value | `number \| null` | `null` | 非受控模式下的默认值 |
| disabled | `boolean` | `false` | 是否禁用 |
| format | `string` | `'HH:mm:ss'` | 时间格式化字符串,详情见 [format](https://date-fns.org/v2.23.0/docs/format) |
| hours | `number \| number[]` | `undefined` | 通过数组指定显示的小时。当值为 `number` 时,将被当做时间步进处理 |
| minutes | `number \| number[]` | `undefined` | 通过数组指定显示的分钟。当值为 `number` 时,将被当做时间步进处理 |
| seconds | `number \| number[]` | `undefined` | 通过数组指定显示的秒。当值为 `number` 时,将被当做时间步进处理 |
| input-readonly | `boolean` | `false` | 设置输入框为只读(避免在移动设备上打开虚拟键盘) |
| is-hour-disabled | `(hour: number) => boolean` | `() => false` | 用于禁用小时的回调函数 |
| is-minute-disabled | `(minute: number, hour: number) => boolean` | `() => false` | 用于禁用分钟的回调函数 |
| is-second-disabled | `(second: number, minute: number, hour: number) => boolean` | `() => false` | 用于禁用秒钟的回调函数 |
| placeholder | `string` | `'请选择时间'` | 选择框的占位符 |
| size | `'small' \| 'medium' \| 'large'` | `'medium'` | 选择框的尺寸 |
| use-12-hours | `boolean` | `false` | 是否使用 12 小时制的面板 |
| value | `number \| null` | `undefined` | 受控模式下的值 |
| on-blur | `() => void` | `undefined` | 选择框失去焦点时的回调 |
| on-focus | `() => void` | `undefined` | 选择框获得焦点时的回调 |
| on-update:value | `(value: number \| null) => void` | `undefined` | 值发生改变时的回调 |
| 名称 | 类型 | 默认值 | 说明 | 版本 |
| --- | --- | --- | --- | --- |
| actions | `Array<'now' \| 'confirm'> \| null` | `['now', 'confirm']` | Time Picker 中支持的操作 | |
| clearable | `boolean` | `false` | 是否可清空 | |
| default-value | `number \| null` | `null` | 非受控模式下的默认值 | |
| default-formatted-value | `string \| null` | `undefined` | 非受控模式下的默认格式化后的值 | NEXT_VERSION |
| disabled | `boolean` | `false` | 是否禁用 | |
| format | `string` | `'HH:mm:ss'` | 时间格式化字符串,详情见 [format](https://date-fns.org/v2.23.0/docs/format) | |
| formatted-value | `string \| null` | `undefined` | 格式化后的值 | NEXT_VERSION |
| hours | `number \| number[]` | `undefined` | 通过数组指定显示的小时。当值为 `number` 时,将被当做时间步进处理 | |
| minutes | `number \| number[]` | `undefined` | 通过数组指定显示的分钟。当值为 `number` 时,将被当做时间步进处理 | |
| seconds | `number \| number[]` | `undefined` | 通过数组指定显示的秒。当值为 `number` 时,将被当做时间步进处理 | |
| input-readonly | `boolean` | `false` | 设置输入框为只读(避免在移动设备上打开虚拟键盘) | |
| is-hour-disabled | `(hour: number) => boolean` | `() => false` | 用于禁用小时的回调函数 | |
| is-minute-disabled | `(minute: number, hour: number) => boolean` | `() => false` | 用于禁用分钟的回调函数 | |
| is-second-disabled | `(second: number, minute: number, hour: number) => boolean` | `() => false` | 用于禁用秒钟的回调函数 | |
| placeholder | `string` | `'请选择时间'` | 选择框的占位符 | |
| size | `'small' \| 'medium' \| 'large'` | `'medium'` | 选择框的尺寸 | |
| use-12-hours | `boolean` | `false` | 是否使用 12 小时制的面板 | |
| value | `number \| null` | `undefined` | 受控模式下的值 | |
| value-format | `string` | 跟随 `format` | 格式化后值的格式 | NEXT_VERSION |
| on-blur | `() => void` | `undefined` | 选择框失去焦点时的回调 | |
| on-focus | `() => void` | `undefined` | 选择框获得焦点时的回调 | |
| on-update:formatted-value | `(value: number \| null, timestampValue: number \| null) => void` | `undefined` | 格式化的值发生改变时的回调 | NEXT_VERSION |
| on-update:value | `(value: number \| null, formattedValue: string \| null) => void` | `undefined` | 值发生改变时的回调 | `formattedValue` NEXT_VERSION |

View File

@ -38,13 +38,8 @@ import { InputInst, NInput } from '../../input'
import { NBaseIcon } from '../../_internal'
import { useConfig, useTheme, useLocale, useFormItem } from '../../_mixins'
import type { ThemeProps } from '../../_mixins'
import {
call,
useAdjustedTo,
MaybeArray,
ExtractPublicPropTypes,
warnOnce
} from '../../_utils'
import { call, useAdjustedTo, warnOnce } from '../../_utils'
import type { MaybeArray, ExtractPublicPropTypes } from '../../_utils'
import { timePickerLight } from '../styles'
import type { TimePickerTheme } from '../styles'
import Panel from './Panel'
@ -53,6 +48,8 @@ import {
IsMinuteDisabled,
IsSecondDisabled,
ItemValue,
OnUpdateFormattedValue,
OnUpdateFormattedValueImpl,
OnUpdateValue,
OnUpdateValueImpl,
PanelRef,
@ -86,6 +83,7 @@ const timePickerProps = {
type: Number as PropType<number | null>,
default: null
},
defaultFormattedValue: String,
placeholder: String,
placement: {
type: String,
@ -96,6 +94,8 @@ const timePickerProps = {
type: String,
default: 'HH:mm:ss'
},
valueFormat: String,
formattedValue: String as PropType<string | null>,
isHourDisabled: Function as PropType<IsHourDisabled>,
size: String as PropType<Size>,
isMinuteDisabled: Function as PropType<IsMinuteDisabled>,
@ -104,6 +104,12 @@ const timePickerProps = {
clearable: Boolean,
'onUpdate:value': [Function, Array] as PropType<MaybeArray<OnUpdateValue>>,
onUpdateValue: [Function, Array] as PropType<MaybeArray<OnUpdateValue>>,
onUpdateFormattedValue: [Function, Array] as PropType<
MaybeArray<OnUpdateFormattedValue>
>,
'onUpdate:formattedValue': [Function, Array] as PropType<
MaybeArray<OnUpdateFormattedValue>
>,
onBlur: [Function, Array] as PropType<MaybeArray<(e: FocusEvent) => void>>,
onFocus: [Function, Array] as PropType<MaybeArray<(e: FocusEvent) => void>>,
// private
@ -176,17 +182,43 @@ export default defineComponent({
const inputInstRef = ref<InputInst | null>(null)
const panelInstRef = ref<PanelRef | null>(null)
const uncontrolledValueRef = ref(props.defaultValue)
const controlledValueRef = toRef(props, 'value')
const mergedValueRef = useMergedState(
controlledValueRef,
uncontrolledValueRef
)
const dateFnsOptionsRef = computed(() => {
return {
locale: dateLocaleRef.value.locale
}
})
function getTimestampFromFormattedValue (
value: string | null
): number | null {
if (value === null) return null
return strictParse(
value,
props.valueFormat || props.format,
new Date(),
dateFnsOptionsRef.value
).getTime()
}
const { defaultValue, defaultFormattedValue } = props
const uncontrolledValueRef = ref(
defaultFormattedValue !== undefined
? getTimestampFromFormattedValue(defaultFormattedValue)
: defaultValue
)
const mergedValueRef = computed(() => {
const { formattedValue } = props
if (formattedValue !== undefined) {
return getTimestampFromFormattedValue(formattedValue)
}
const { value } = props
if (value !== undefined) {
return value
}
return uncontrolledValueRef.value
})
const { value: mergedValue } = mergedValueRef
const displayTimeStringRef = ref(
mergedValue === null
@ -279,16 +311,46 @@ export default defineComponent({
if (value === null) return null
return Number(format(value, 'ss', dateFnsOptionsRef.value))
})
function doChange (value: number | null): void {
function doUpdateFormattedValue (
value: string | null,
timestampValue: number | null
): void {
const {
onUpdateFormattedValue,
'onUpdate:formattedValue': _onUpdateFormattedValue
} = props
if (onUpdateFormattedValue) {
call(
onUpdateFormattedValue as OnUpdateFormattedValueImpl,
value,
timestampValue
)
}
if (_onUpdateFormattedValue) {
call(
_onUpdateFormattedValue as OnUpdateFormattedValueImpl,
value,
timestampValue
)
}
}
function doUpdateValue (value: number | null): void {
const {
onUpdateValue,
'onUpdate:value': _onUpdateValue,
onChange
} = props
const { nTriggerFormChange, nTriggerFormInput } = formItem
if (onUpdateValue) call(onUpdateValue as OnUpdateValueImpl, value)
if (_onUpdateValue) call(_onUpdateValue as OnUpdateValueImpl, value)
if (onChange) call(onChange as OnUpdateValueImpl, value)
const formattedValue =
value === null ? null : format(value, props.valueFormat || props.format)
if (onUpdateValue) {
call(onUpdateValue as OnUpdateValueImpl, value, formattedValue)
}
if (_onUpdateValue) {
call(_onUpdateValue as OnUpdateValueImpl, value, formattedValue)
}
if (onChange) call(onChange as OnUpdateValueImpl, value, formattedValue)
doUpdateFormattedValue(formattedValue, value)
uncontrolledValueRef.value = value
nTriggerFormChange()
nTriggerFormInput()
@ -307,7 +369,7 @@ export default defineComponent({
}
function handleTimeInputClear (e: MouseEvent): void {
e.stopPropagation()
doChange(null)
doUpdateValue(null)
deriveInputValue(null)
}
function handleFocusDetectorFocus (): void {
@ -347,25 +409,25 @@ export default defineComponent({
function handleHourClick (hour: ItemValue): void {
if (typeof hour === 'string') return
if (mergedValueRef.value === null) {
doChange(getTime(setHours(startOfHour(new Date()), hour)))
doUpdateValue(getTime(setHours(startOfHour(new Date()), hour)))
} else {
doChange(getTime(setHours(mergedValueRef.value, hour)))
doUpdateValue(getTime(setHours(mergedValueRef.value, hour)))
}
}
function handleMinuteClick (minute: ItemValue): void {
if (typeof minute === 'string') return
if (mergedValueRef.value === null) {
doChange(getTime(setMinutes(startOfMinute(new Date()), minute)))
doUpdateValue(getTime(setMinutes(startOfMinute(new Date()), minute)))
} else {
doChange(getTime(setMinutes(mergedValueRef.value, minute)))
doUpdateValue(getTime(setMinutes(mergedValueRef.value, minute)))
}
}
function handleSecondClick (second: ItemValue): void {
if (typeof second === 'string') return
if (mergedValueRef.value === null) {
doChange(getTime(setSeconds(startOfSecond(new Date()), second)))
doUpdateValue(getTime(setSeconds(startOfSecond(new Date()), second)))
} else {
doChange(getTime(setSeconds(mergedValueRef.value, second)))
doUpdateValue(getTime(setSeconds(mergedValueRef.value, second)))
}
}
function handleAmPmClick (amPm: ItemValue): void {
@ -374,17 +436,17 @@ export default defineComponent({
const now = new Date()
const hours = getHours(now)
if (amPm === 'pm' && hours < 12) {
doChange(getTime(setHours(now, hours + 12)))
doUpdateValue(getTime(setHours(now, hours + 12)))
} else if (amPm === 'am' && hours >= 12) {
doChange(getTime(setHours(now, hours - 12)))
doUpdateValue(getTime(setHours(now, hours - 12)))
}
doChange(getTime(now))
doUpdateValue(getTime(now))
} else {
const hours = getHours(mergedValue)
if (amPm === 'pm' && hours < 12) {
doChange(getTime(setHours(mergedValue, hours + 12)))
doUpdateValue(getTime(setHours(mergedValue, hours + 12)))
} else if (amPm === 'am' && hours >= 12) {
doChange(getTime(setHours(mergedValue, hours - 12)))
doUpdateValue(getTime(setHours(mergedValue, hours - 12)))
}
}
}
@ -482,7 +544,7 @@ export default defineComponent({
}
function handleTimeInputUpdateValue (v: string): void {
if (v === '') {
doChange(null)
doUpdateValue(null)
return
}
const time = strictParse(
@ -500,14 +562,14 @@ export default defineComponent({
minutes: getMinutes(time),
seconds: getSeconds(time)
})
doChange(getTime(newTime))
doUpdateValue(getTime(newTime))
} else {
doChange(getTime(time))
doUpdateValue(getTime(time))
}
}
}
function handleCancelClick (): void {
doChange(memorizedValueRef.value)
doUpdateValue(memorizedValueRef.value)
doUpdateShow(false)
}
function handleNowClick (): void {
@ -534,7 +596,7 @@ export default defineComponent({
),
mergeSeconds
)
doChange(getTime(newValue))
doUpdateValue(getTime(newValue))
}
function handleConfirmClick (): void {
deriveInputValue()
@ -556,7 +618,7 @@ export default defineComponent({
})
watch(mergedShowRef, () => {
if (isValueInvalidRef.value) {
doChange(memorizedValueRef.value)
doUpdateValue(memorizedValueRef.value)
}
})
provide(timePickerInjectionKey, {

View File

@ -27,10 +27,22 @@ export interface PanelRef {
amPmScrollRef?: ScrollbarInst
}
export type OnUpdateValue = <T extends number & (number | null)>(
value: T
export type OnUpdateValue = ((value: number, formattedValue: string) => void) &
((value: number | null, formattedValue: string | null) => void)
export type OnUpdateValueImpl = (
value: number | null,
formattedValue: string | null
) => void
export type OnUpdateFormattedValue = ((
value: string,
timestampValue: number
) => void) &
((value: string | null, timestampValue: number | null) => void)
export type OnUpdateFormattedValueImpl = (
value: string | null,
timestampValue: number | null
) => void
export type OnUpdateValueImpl = (value: number | null) => void
export type IsHourDisabled = (hour: number) => boolean
export type IsMinuteDisabled = (minute: number, hour: number | null) => boolean