From ff8fb7736b45093b388b0df510810c102bf1d0b9 Mon Sep 17 00:00:00 2001
From: FairyYang <269291280@qq.com>
Date: Mon, 21 Oct 2024 11:09:54 +0800
Subject: [PATCH] refactor(TimePicker2): convert to TypeScript, impove docs and
tests, close #4616
---
.../__docs__/demo/disabled/index.tsx | 8 +-
.../__docs__/demo/field/index.tsx | 6 +-
.../__docs__/demo/preset/index.tsx | 5 +-
.../__docs__/demo/render-menu/index.tsx | 5 +-
.../time-picker2/__docs__/demo/step/index.tsx | 3 +-
.../__docs__/demo/value/index.tsx | 22 +-
.../time-picker2/__docs__/index.en-us.md | 133 ++--
components/time-picker2/__docs__/index.md | 145 ++--
.../time-picker2/__tests__/index-spec.js | 422 ------------
.../time-picker2/__tests__/index-spec.tsx | 378 ++++++++++
.../time-picker2/{constant.js => constant.ts} | 0
components/time-picker2/index.d.ts | 206 ------
components/time-picker2/index.jsx | 10 -
components/time-picker2/index.tsx | 21 +
.../mobile/{index.jsx => index.tsx} | 0
.../module/{date-input.jsx => date-input.tsx} | 72 +-
.../module/{time-menu.jsx => time-menu.tsx} | 43 +-
.../time-picker2/{panel.jsx => panel.tsx} | 147 ++--
.../{prop-types.js => prop-types.ts} | 22 +-
.../time-picker2/{style.js => style.ts} | 0
.../{time-picker.jsx => time-picker.tsx} | 328 ++++-----
components/time-picker2/types.ts | 650 ++++++++++++++++++
.../time-picker2/utils/{index.js => index.ts} | 51 +-
components/util/func.ts | 4 +-
24 files changed, 1565 insertions(+), 1116 deletions(-)
delete mode 100644 components/time-picker2/__tests__/index-spec.js
create mode 100644 components/time-picker2/__tests__/index-spec.tsx
rename components/time-picker2/{constant.js => constant.ts} (100%)
delete mode 100644 components/time-picker2/index.d.ts
delete mode 100644 components/time-picker2/index.jsx
create mode 100644 components/time-picker2/index.tsx
rename components/time-picker2/mobile/{index.jsx => index.tsx} (100%)
rename components/time-picker2/module/{date-input.jsx => date-input.tsx} (70%)
rename components/time-picker2/module/{time-menu.jsx => time-menu.tsx} (72%)
rename components/time-picker2/{panel.jsx => panel.tsx} (63%)
rename components/time-picker2/{prop-types.js => prop-types.ts} (65%)
rename components/time-picker2/{style.js => style.ts} (100%)
rename components/time-picker2/{time-picker.jsx => time-picker.tsx} (68%)
create mode 100644 components/time-picker2/types.ts
rename components/time-picker2/utils/{index.js => index.ts} (53%)
diff --git a/components/time-picker2/__docs__/demo/disabled/index.tsx b/components/time-picker2/__docs__/demo/disabled/index.tsx
index dffc109e33..277bc6cc80 100644
--- a/components/time-picker2/__docs__/demo/disabled/index.tsx
+++ b/components/time-picker2/__docs__/demo/disabled/index.tsx
@@ -6,7 +6,7 @@ const disabledHours = [1, 2, 3, 4, 5];
const disabledMinutes = [10, 20, 30, 40, 50];
const disabledSeconds = [10, 20, 30, 40, 50];
-const disabledItems = list => index => {
+const disabledItems = (list: number[]) => (index: number) => {
return list.indexOf(index) >= 0;
};
@@ -20,6 +20,12 @@ ReactDOM.render(
disabledMinutes={disabledItems(disabledMinutes)}
disabledSeconds={disabledItems(disabledSeconds)}
/>
+
RangePicker Disable Hours/Minutes/Seconds
+ disabledHours}
+ disabledMinutes={() => disabledMinutes}
+ disabledSeconds={() => disabledSeconds}
+ />
,
mountNode
);
diff --git a/components/time-picker2/__docs__/demo/field/index.tsx b/components/time-picker2/__docs__/demo/field/index.tsx
index 918ba9bd49..0b16bc9758 100644
--- a/components/time-picker2/__docs__/demo/field/index.tsx
+++ b/components/time-picker2/__docs__/demo/field/index.tsx
@@ -1,14 +1,14 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { TimePicker2, Field, Button } from '@alifd/next';
-import dayjs from 'dayjs';
+import dayjs, { type Dayjs } from 'dayjs';
class Demo extends React.Component {
field = new Field(this);
onClick = () => {
- const value = this.field.getValue('time-picker');
- console.log(value.format('HH:mm:ss'));
+ const value: Dayjs | undefined = this.field.getValue('time-picker');
+ console.log(value!.format('HH:mm:ss'));
};
render() {
diff --git a/components/time-picker2/__docs__/demo/preset/index.tsx b/components/time-picker2/__docs__/demo/preset/index.tsx
index 2172ec87f3..37d0fcb72b 100644
--- a/components/time-picker2/__docs__/demo/preset/index.tsx
+++ b/components/time-picker2/__docs__/demo/preset/index.tsx
@@ -3,12 +3,13 @@ import ReactDOM from 'react-dom';
import React, { useState } from 'react';
import dayjs from 'dayjs';
import { TimePicker2 } from '@alifd/next';
+import type { TimePickerProps, ValueType } from '@alifd/next/types/time-picker2';
const nowTime = dayjs(new Date());
const currentHour = dayjs().hour(nowTime.hour()).minute(0).second(0);
const nextHour = currentHour.hour(currentHour.hour() + 1);
-const preset = [
+const preset: TimePickerProps['preset'] = [
{
label: '此刻',
value: () => nowTime,
@@ -23,7 +24,7 @@ const presetRange = [
];
function Picker() {
- const [value, onChange] = useState(dayjs('12:00:00', 'HH:mm:ss', true));
+ const [value, onChange] = useState(dayjs('12:00:00', 'HH:mm:ss', true));
return (
diff --git a/components/time-picker2/__docs__/demo/render-menu/index.tsx b/components/time-picker2/__docs__/demo/render-menu/index.tsx
index 835e6a0e01..01275cb349 100644
--- a/components/time-picker2/__docs__/demo/render-menu/index.tsx
+++ b/components/time-picker2/__docs__/demo/render-menu/index.tsx
@@ -1,9 +1,10 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { TimePicker2 } from '@alifd/next';
+import type { TimePickerProps } from '@alifd/next/types/time-picker2';
-const renderTimeMenuItems = list => {
- return list.map(({ label, value }) => {
+const renderTimeMenuItems: TimePickerProps['renderTimeMenuItems'] = list => {
+ return list.map(({ value }) => {
return {
value,
label: value > 9 ? String(value) : `0${value}`,
diff --git a/components/time-picker2/__docs__/demo/step/index.tsx b/components/time-picker2/__docs__/demo/step/index.tsx
index 6007824432..fb89ebebe3 100644
--- a/components/time-picker2/__docs__/demo/step/index.tsx
+++ b/components/time-picker2/__docs__/demo/step/index.tsx
@@ -1,10 +1,11 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { TimePicker2 } from '@alifd/next';
+import type { Dayjs } from 'dayjs';
ReactDOM.render(
console.log(val.format('HH:mm:ss'))}
+ onChange={(val: Dayjs) => console.log(val.format('HH:mm:ss'))}
hourStep={2}
minuteStep={5}
secondStep={5}
diff --git a/components/time-picker2/__docs__/demo/value/index.tsx b/components/time-picker2/__docs__/demo/value/index.tsx
index e36a4223a9..c0f22ae4a1 100644
--- a/components/time-picker2/__docs__/demo/value/index.tsx
+++ b/components/time-picker2/__docs__/demo/value/index.tsx
@@ -1,23 +1,33 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { TimePicker2 } from '@alifd/next';
-import dayjs from 'dayjs';
+import dayjs, { type Dayjs } from 'dayjs';
+import type { TimePickerProps, ValueType } from '@alifd/next/types/time-picker2';
-class ControlledTimePicker2 extends React.Component {
- constructor(props, context) {
- super(props, context);
+interface ControlledTimePicker2Props {
+ onChange: (value: ValueType) => void;
+}
+class ControlledTimePicker2 extends React.Component<
+ ControlledTimePicker2Props,
+ {
+ value: Dayjs | null;
+ rangeValue: (Dayjs | null)[] | null;
+ }
+> {
+ constructor(props: ControlledTimePicker2Props) {
+ super(props);
this.state = {
value: dayjs('12:00:00', 'HH:mm:ss', true),
rangeValue: [dayjs('14:00:00', 'HH:mm:ss'), dayjs('16:00:00', 'HH:mm:ss')],
};
}
- onSelect = value => {
+ onSelect: TimePickerProps['onChange'] = (value: Dayjs | null) => {
this.setState({ value });
this.props.onChange(value);
};
- onRangeSelect = rangeValue => {
+ onRangeSelect: TimePickerProps['onChange'] = (rangeValue: (Dayjs | null)[] | null) => {
this.setState({ rangeValue });
this.props.onChange(rangeValue);
};
diff --git a/components/time-picker2/__docs__/index.en-us.md b/components/time-picker2/__docs__/index.en-us.md
index 02d73c3e73..71f20f3151 100644
--- a/components/time-picker2/__docs__/index.en-us.md
+++ b/components/time-picker2/__docs__/index.en-us.md
@@ -13,11 +13,11 @@
A TimePicker is used to input a time by displaying an interface the user can interact with. The TimePicker panel only support 24h clock. Setting `format` with:
-| Format | Example | Description |
-| ------ | ------- | -------- |
-| `H HH` | `0..23` | Hour,24h |
-| `m mm` | `0..59` | Minute |
-| `s ss` | `0..59` | Second |
+| Format | Example | Description |
+| ------ | ------- | ----------- |
+| `H HH` | `0..23` | Hour,24h |
+| `m mm` | `0..59` | Minute |
+| `s ss` | `0..59` | Second |
By default, TimePicker using dayjs instance as input value, which is the suggestion way. In addition, input value as string is also supported, e.g. "12:00:00". The type of the first parameter in the callback of `onChange` is based on the input value.
@@ -25,47 +25,88 @@ By default, TimePicker using dayjs instance as input value, which is the suggest
### TimePicker
-| Param | Descripiton | Type | Default Value |
-| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ---------- |
-| label | Inset label of input | ReactNode | - |
-| size | Size of input **option**: 'small', 'medium', 'large' | Enum | 'medium' |
-| state | State of input **option**: 'error', 'success' | Enum | - |
-| placeholder | Placeholder of input | String | - |
-| value | Time value | custom | - |
-| defaultValue | Defualt time value | custom | - |
-| hasClear | Has clear icon | Boolean | true |
-| format | time format | String | 'HH:mm:ss' |
-| hourStep | Step of hour | Number | - |
-| minuteStep | Step of minute | Number | - |
-| secondStep | Step of second | Number | - |
-| disabledHours | Function to disable hours **signature**: Function(index: Number) => Boolean **paramter**: _index_: {Number} hour 0 - 23 **return**: {Boolean} if disabled | Function | - |
-| disabledMinutes | Function to disable minutes **signature**: Function(index: Number) => Boolean **paramter**: _index_: {Number} minute 0 - 59 **return**: {Boolean} if disabled | Function | - |
-| disabledSeconds | Function to disable seconds **signature**: Function(index: Number) => Boolean **paramter**: _index_: {Number} second 0 - 59 **return**: {Boolean} if disabled | Function | - |
-| renderTimeMenuItems | Render time menu [{ label: '01', value: 1 }] **签名**: Function(list: Array, mode: String, value: dayjs) => Array **参数**: _list_: {Array} default time menu list _mode_: {String} menu type: hour, minute, second _value_: {dayjs} value **返回值**: {Array}
-| visible | Visible state of popup | Boolean | - |
-| defaultVisible | Default visible state of popup | Boolean | - |
-| popupContainer | Container of popup **signature**: Function(target: Object) => ReactNode **paramter**: _target_: {Object} target container **return**: {ReactNode} container element | Function | - |
-| popupAlign | Align of popup, @see Overylay doc for detail | String | 'tl tl' |
-| popupTriggerType | Trigger type of popup **option**: 'click', 'hover' | Enum | 'click' |
-| onVisibleChange | Callback when visible changes **signature**: Function(visible: Boolean, reason: String) => void **paramter**: _visible_: {Boolean} visible of popup _reason_: {String} reason to change visible | Function | func.noop |
-| popupStyle | Custom style of popup | Object | - |
-| popupClassName | Custom className of popup | String | - |
-| popupProps | Props of popup | Object | - |
-| followTrigger | follow Trigger or not | Boolean | - |
-| disabled | Disable the picker | Boolean | false |
-| hasBorder | Whether the input has border | Boolean | true |
-| preset | Rreset values, shown below the time panel. Can be object or array of object, with the following properties. **properties**: label: {String} shown text name: {String} key of React element, can be empty, and index will become key instead value: {String/dayjs instance} date value | Object/Array | - |
-| onChange | Callback when date changes **signature**: Function(value: Object/String) => void **paramter**: _value_: {Object/String} date value | Function | func.noop |
+| Param | Description | Type | Default Value | Required |
+| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | -------- |
+| label | Button text | ReactNode | - | |
+| size | Size of time picker | 'small' \| 'medium' \| 'large' | 'medium' | |
+| state | Input state | 'error' \| 'success' | - | |
+| hasClear | Whether to allow clearing time | boolean | true | |
+| format | Time format | string | 'HH:mm:ss' | |
+| hourStep | Hour option step | number | - | |
+| minuteStep | Minute option step | number | - | |
+| secondStep | Second option step | number | - | |
+| renderTimeMenuItems | Render the selectable time list **signature**: **params**: _list_: list _mode_: mode _value_: value | ( list: Array\, mode: TimeMenuProps['mode'], value: TimeMenuProps['value'] ) => Array\ | - | |
+| visible | Popup layer display status (controlled) | boolean | - | |
+| defaultVisible | Popup layer default display status (uncontrolled) | boolean | - | |
+| popupContainer | Popup layer container | string \| HTMLElement \| ((target: HTMLElement) => HTMLElement) | - | |
+| popupAlign | Popup layer alignment, see Overlay documentation | string | 'tl bl' | |
+| popupTriggerType | Popup layer trigger type | 'click' \| 'hover' | 'click' | |
+| onVisibleChange | Callback when the popup layer display status changes | (visible: boolean, reason?: string) => void | - | |
+| popupStyle | Popup layer custom style | CSSProperties | - | |
+| popupClassName | Popup layer custom style class | string | - | |
+| popupProps | Popup layer property | PopupProps | - | |
+| followTrigger | Follow trigger element | boolean | - | |
+| hasBorder | Whether the input has border | boolean | true | |
+| isPreview | Is preview | boolean | - | |
+| renderPreview | Content of preview mode | (value: ValueType, props: TimePickerProps) => ReactNode | - | |
+| inputProps | Custom input property | InputProps | - | |
+| placeholder | Input hint | string | - | |
+| value | Time value (Dayjs object or time string, controlled state use) | string \| Dayjs \| null \| (Dayjs \| null \| string)[] | - | |
+| defaultValue | Time init value (Dayjs object or time string, uncontrolled state use) | string \| Dayjs \| (Dayjs \| null)[] | - | |
+| disabledHours | For the disabled hours function, if it's a TimePicker.RangePicker, the function should return a number[] and it shouldn't have an index parameter. If it's not a TimePicker.RangePicker, the function should return a boolean and it should have an index parameter. | (index?: number) => boolean \| number[] | - | |
+| disabledMinutes | For the disabled minutes function, if it's a TimePicker.RangePicker, the function should return a number[] and it shouldn't have an index parameter. If it's not a TimePicker.RangePicker, the function should return a boolean and it should have an index parameter. | (index?: number) => boolean \| number[] | - | |
+| disabledSeconds | For the disabled seconds function, if it's a TimePicker.RangePicker, the function should return a number[] and it shouldn't have an index parameter. If it's not a TimePicker.RangePicker, the function should return a boolean and it should have an index parameter. | (index?: number) => boolean \| number[] | - | |
+| onChange | Callback when the time value changes | (value: ValueType) => void | - | |
+| preset | Rreset values, shown below the time panel. Can be object or array of object, with the following properties. properties: label: shown text name: key of React element, can be empty, and index will become key instead value: date value | PresetType \| PresetType[] | - | |
+| disabled | Disable | boolean \| boolean[] | false | |
+
+### TimePicker.RangePicker
+
+| Param | Description | Type | Default Value | Required |
+| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | -------- |
+| label | Button text | ReactNode | - | |
+| size | Size of time picker | 'small' \| 'medium' \| 'large' | 'medium' | |
+| state | Input state | 'error' \| 'success' | - | |
+| hasClear | Whether to allow clearing time | boolean | true | |
+| format | Time format | string | 'HH:mm:ss' | |
+| hourStep | Hour option step | number | - | |
+| minuteStep | Minute option step | number | - | |
+| secondStep | Second option step | number | - | |
+| renderTimeMenuItems | Render the selectable time list **signature**: **params**: _list_: list _mode_: mode _value_: value | ( list: Array\, mode: TimeMenuProps['mode'], value: TimeMenuProps['value'] ) => Array\ | - | |
+| visible | Popup layer display status (controlled) | boolean | - | |
+| defaultVisible | Popup layer default display status (uncontrolled) | boolean | - | |
+| popupContainer | Popup layer container | string \| HTMLElement \| ((target: HTMLElement) => HTMLElement) | - | |
+| popupAlign | Popup layer alignment, see Overlay documentation | string | 'tl bl' | |
+| popupTriggerType | Popup layer trigger type | 'click' \| 'hover' | 'click' | |
+| onVisibleChange | Callback when the popup layer display status changes | (visible: boolean, reason?: string) => void | - | |
+| popupStyle | Popup layer custom style | CSSProperties | - | |
+| popupClassName | Popup layer custom style class | string | - | |
+| popupProps | Popup layer property | PopupProps | - | |
+| followTrigger | Follow trigger element | boolean | - | |
+| hasBorder | Whether the input has border | boolean | true | |
+| isPreview | Is preview | boolean | - | |
+| renderPreview | Content of preview mode | (value: ValueType, props: TimePickerProps) => ReactNode | - | |
+| inputProps | Custom input property | InputProps | - | |
+| disabledHours | For the disabled hours function, if it's a TimePicker.RangePicker, the function should return a number[] and it shouldn't have an index parameter. If it's not a TimePicker.RangePicker, the function should return a boolean and it should have an index parameter. | (index?: number) => boolean \| number[] | - | |
+| disabledMinutes | For the disabled minutes function, if it's a TimePicker.RangePicker, the function should return a number[] and it shouldn't have an index parameter. If it's not a TimePicker.RangePicker, the function should return a boolean and it should have an index parameter. | (index?: number) => boolean \| number[] | - | |
+| disabledSeconds | For the disabled seconds function, if it's a TimePicker.RangePicker, the function should return a number[] and it shouldn't have an index parameter. If it's not a TimePicker.RangePicker, the function should return a boolean and it should have an index parameter. | (index?: number) => boolean \| number[] | - | |
+| disabled | Disable | boolean \| boolean[] | false | |
+| placeholder | Input hint | string \| string[] | - | |
+| value | Time value (Dayjs object or time string, controlled state use) | Array\ | - | |
+| defaultValue | Time init value (Dayjs object or time string, uncontrolled state use) | Array\ | - | |
+| onChange | Callback when the time value changes | (value: Array\) => void | - | |
+| onOk | Callback when the ok button is clicked | (value: Array\) => void | - | |
+| preset | Rreset values, shown below the time panel. Can be object or array of object, with the following properties. properties: label: shown text name: key of React element, can be empty, and index will become key instead value: date value | PresetType[] | - | |
## ARIA and KeyBoard
-| 按键 | 说明 |
-| :---- | :--------------- |
-| Enter | Open time select popup |
-| Esc | Close time select popup |
-| Up | Input previous seconds (if `disabledMinutes={true}` is previous minutes or previous hours) |
-| Down | Input next seconds (if `disabledMinutes={true}` is next minutes or next hours) |
-| Page Up | Input previous minutes |
-| Page Down | Input next minutes |
-| Alt + Page Up | Input previous hours |
-| Alt + Page Down | Input next hours |
+| 按键 | 说明 |
+| :-------------- | :------------------------------------------------------------------------------------------- |
+| Enter | Open time select popup |
+| Esc | Close time select popup |
+| Up | Input previous seconds (if `disabledMinutes={true}` is previous minutes or previous hours) |
+| Down | Input next seconds (if `disabledMinutes={true}` is next minutes or next hours) |
+| Page Up | Input previous minutes |
+| Page Down | Input next minutes |
+| Alt + Page Up | Input previous hours |
+| Alt + Page Down | Input next hours |
diff --git a/components/time-picker2/__docs__/index.md b/components/time-picker2/__docs__/index.md
index f7c7f10331..f8f05a33cb 100644
--- a/components/time-picker2/__docs__/index.md
+++ b/components/time-picker2/__docs__/index.md
@@ -28,79 +28,100 @@ API变化:
当用户需要输入一个时间,可以点击输入框,在弹出的时间选择面板上操作。时间选择面板仅支持 24 小时制。`format` 支持的时间格式如下:
-| 格式 | 例子 | 说明 |
-| ------ | ------- | -------- |
+| 格式 | 例子 | 说明 |
+| ------ | ------- | ------------- |
| `H HH` | `0..23` | 时,24 小时制 |
-| `m mm` | `0..59` | 分 |
-| `s ss` | `0..59` | 秒 |
+| `m mm` | `0..59` | 分 |
+| `s ss` | `0..59` | 秒 |
组件默认使用 dayjs 实例作为输入输出值,推荐使用结合 dayjs 的方式使用组件。此外,组件也支持传入时间字符串的方式,例如直接传入 "12:00:00"。用户传入什么类型的 value/defaultValue 值,就会在 onChange 中返回相应的类型。
## API
-### 公共API
-
-| 参数 | 说明 | 类型 | 默认值 |
-| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | ---------- |
-| label | 按钮的文案 | ReactNode | - |
-| size | 时间选择框的尺寸 **可选值**: 'small', 'medium', 'large' | Enum | 'medium' |
-| state | 输入框状态 **可选值**: 'error', 'success' | Enum | - |
-| hasClear | 是否允许清空时间 | Boolean | true |
-| format | 时间的格式 | String | 'HH:mm:ss' |
-| hourStep | 小时选项步长 | Number | - |
-| minuteStep | 分钟选项步长 | Number | - |
-| secondStep | 秒钟选项步长 | Number | - |
-| disabledHours | 禁用小时函数 **签名**: Function(index: Number) => Boolean **参数**: _index_: {Number} 时 0 - 23 **返回值**: {Boolean} 是否禁用 | Function | - |
-| disabledMinutes | 禁用分钟函数 **签名**: Function(index: Number) => Boolean **参数**: _index_: {Number} 分 0 - 59 **返回值**: {Boolean} 是否禁用 | Function | - |
-| disabledSeconds | 禁用秒钟函数 **签名**: Function(index: Number) => Boolean **参数**: _index_: {Number} 秒 0 - 59 **返回值**: {Boolean} 是否禁用 | Function | - |
-| renderTimeMenuItems | 渲染的可选择时间列表 [{ label: '01', value: 1 }] **签名**: Function(list: Array, mode: String, value: dayjs) => Array **参数**: _list_: {Array} 默认渲染的列表 _mode_: {String} 渲染的菜单 hour, minute, second _value_: {dayjs} 当前时间,可能为 null **返回值**: {Array} 返回需要渲染的数据 | Function | - |
-| visible | 弹层是否显示(受控) | Boolean | - |
-| defaultVisible | 弹层默认是否显示(非受控) | Boolean | - |
-| popupContainer | 弹层容器 | any | - |
-| popupAlign | 弹层对齐方式, 详情见Overlay 文档 | String | 'tl bl' |
-| popupTriggerType | 弹层触发方式 **可选值**: 'click', 'hover' | Enum | 'click' |
-| onVisibleChange | 弹层展示状态变化时的回调 **签名**: Function(visible: Boolean, type: String) => void **参数**: _visible_: {Boolean} 弹层是否隐藏和显示 _type_: {String} 触发弹层显示和隐藏的来源 fromTrigger 表示由trigger的点击触发; docClick 表示由document的点击触发 | Function | func.noop |
-| popupStyle | 弹层自定义样式 | Object | - |
-| popupClassName | 弹层自定义样式类 | String | - |
-| popupProps | 弹层属性 | Object | - |
-| followTrigger | 是否跟随滚动 | Boolean | - |
-| disabled | 是否禁用 | Boolean | false |
-| hasBorder | 输入框是否有边框 | Boolean | true |
-| isPreview | 是否为预览态 | Boolean | - |
-| renderPreview | 预览态模式下渲染的内容 **签名**: Function(value: DayjsObject/DayjsObject[]) => void **参数**: _value_: {DayjsObject/DayjsObject[]} 时间 | Function | - |
-
-### TimePicker2
-
-| 参数 | 说明 | 类型 | 默认值 |
-| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | ---------- |
-| placeholder | 输入框提示 | String | - |
-| value | 时间值,dayjs格式或者2020-01-01字符串格式,受控状态使用 | custom | - |
-| defaultValue | 时间初值,dayjs格式或者2020-01-01字符串格式,非受控状态使用 | custom | - |
-| onChange | 时间值改变时的回调 **签名**: Function(dateString: Object/String, date: DayjsObject) => void **参数**: _dateString_: {Object/String} 时间对象或时间字符串 _date_: {DayjsObject} dayjs时间对象 | Function | func.noop |
-| onChange | 时间值改变时的回调 **签名**: Function(date: DayjsObject, dateString: Object/String) => void **参数**: _date_: {DayjsObject} dayjs时间对象 _dateString_: {Object/String} 时间对象或时间字符串 | Function | func.noop |
-| preset | 预设值,会显示在时间面板下面 | Array<custom>/custom | - |
-| disabled | 是否禁用 | Boolean[] | [false, false] |
+### TimePicker
+
+| 参数 | 说明 | 类型 | 默认值 | 是否必填 |
+| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -------- |
+| label | 按钮的文案 | ReactNode | - | |
+| size | 时间选择框的尺寸 | 'small' \| 'medium' \| 'large' | 'medium' | |
+| state | 输入框状态 | 'error' \| 'success' | - | |
+| hasClear | 是否允许清空时间 | boolean | true | |
+| format | 时间的格式 | string | 'HH:mm:ss' | |
+| hourStep | 小时选项步长 | number | - | |
+| minuteStep | 分钟选项步长 | number | - | |
+| secondStep | 秒钟选项步长 | number | - | |
+| renderTimeMenuItems | 渲染的可选择时间列表 [\{ label: '01', value: 1 \}] **签名**: **参数**: _list_: 默认渲染的列表 _mode_: 渲染的菜单 hour, minute, second _value_: 当前时间,可能为 null **返回值**: 返回需要渲染的数据 | ( list: Array\, mode: TimeMenuProps['mode'], value: TimeMenuProps['value'] ) => Array\ | - | |
+| visible | 弹层是否显示(受控) | boolean | - | |
+| defaultVisible | 弹层默认是否显示(非受控) | boolean | - | |
+| popupContainer | 弹层容器 | string \| HTMLElement \| ((target: HTMLElement) => HTMLElement) | - | |
+| popupAlign | 弹层对齐方式,详情见 Overlay 文档 | string | 'tl bl' | |
+| popupTriggerType | 弹层触发方式 | 'click' \| 'hover' | 'click' | |
+| onVisibleChange | 弹层展示状态变化时的回调 | (visible: boolean, reason?: string) => void | - | |
+| popupStyle | 弹层自定义样式 | CSSProperties | - | |
+| popupClassName | 弹层自定义样式类 | string | - | |
+| popupProps | 弹层属性 | PopupProps | - | |
+| followTrigger | 跟随触发元素 | boolean | - | |
+| hasBorder | 是否有边框 | boolean | true | |
+| isPreview | 是否为预览态 | boolean | - | |
+| renderPreview | 预览态模式下渲染的内容 | (value: ValueType, props: TimePickerProps) => ReactNode | - | |
+| inputProps | 自定义输入框属性 | InputProps | - | |
+| placeholder | 输入框提示 | string | - | |
+| value | 时间值(Dayjs 对象或时间字符串,受控状态使用) | string \| Dayjs \| null \| (Dayjs \| null \| string)[] | - | |
+| defaultValue | 时间初值(Dayjs 对象或时间字符串,非受控状态使用) | string \| Dayjs \| (Dayjs \| null)[] | - | |
+| disabledHours | 禁用小时的函数,TimePicker.RangePicker 时,函数需要返回 number[],且函数中没有 index 入参,非 TimePicker.RangePicker 时,函数需要返回 boolean,函数中有 index 入参 | (index?: number) => boolean \| number[] | - | |
+| disabledMinutes | 禁用分钟函数,TimePicker.RangePicker 时,函数需要返回 number[],且函数中没有 index 入参,非 TimePicker.RangePicker 时,函数需要返回 boolean,函数中有 index 入参 | (index?: number) => boolean \| number[] | - | |
+| disabledSeconds | 禁用秒钟函数,TimePicker.RangePicker 时,函数需要返回 number[],且函数中没有 index 入参,非 TimePicker.RangePicker 时,函数需要返回 boolean,函数中有 index 入参 | (index?: number) => boolean \| number[] | - | |
+| onChange | 时间值改变时的回调 | (value: ValueType) => void | - | |
+| preset | 预设值,会显示在时间面板下面 | PresetType \| PresetType[] | - | |
+| disabled | 禁用 | boolean \| boolean[] | false | |
### TimePicker.RangePicker
-| 参数 | 说明 | 类型 | 默认值
-| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | -------------------------------------------------------------------------------------------- |
-| placeholder | 输入提示,例如 ['开始时间', '结束时间'] | String | [String, String] | - |
-| value | 日期值(受控)例如 ['11:00:00', '11:59:59'] | [Dayjs, Dayjs] | - |
-| defaultValue | 初始日期值 例如 ['11:00:00', '11:59:59'] | [Dayjs, Dayjs] | - |
-| onChange | 日期值改变时的回调 **签名**: Function(value) => void **参数**: _value_: {[Dayjs, Dayjs]} 日期范围 | Function | func.noop |
-| onOk | 点击确认按钮时的回调 **签名**: Function(value) => void **参数**: _value_: {[Dayjs, Dayjs]} 日期范围 | Function | func.noop
-| preset | 预设值,会显示在时间面板下面 | Array<custom>/custom | - |
+| 参数 | 说明 | 类型 | 默认值 | 是否必填 |
+| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -------- |
+| label | 按钮的文案 | ReactNode | - | |
+| size | 时间选择框的尺寸 | 'small' \| 'medium' \| 'large' | 'medium' | |
+| state | 输入框状态 | 'error' \| 'success' | - | |
+| hasClear | 是否允许清空时间 | boolean | true | |
+| format | 时间的格式 | string | 'HH:mm:ss' | |
+| hourStep | 小时选项步长 | number | - | |
+| minuteStep | 分钟选项步长 | number | - | |
+| secondStep | 秒钟选项步长 | number | - | |
+| renderTimeMenuItems | 渲染的可选择时间列表 [\{ label: '01', value: 1 \}] **签名**: **参数**: _list_: 默认渲染的列表 _mode_: 渲染的菜单 hour, minute, second _value_: 当前时间,可能为 null **返回值**: 返回需要渲染的数据 | ( list: Array\, mode: TimeMenuProps['mode'], value: TimeMenuProps['value'] ) => Array\ | - | |
+| visible | 弹层是否显示(受控) | boolean | - | |
+| defaultVisible | 弹层默认是否显示(非受控) | boolean | - | |
+| popupContainer | 弹层容器 | string \| HTMLElement \| ((target: HTMLElement) => HTMLElement) | - | |
+| popupAlign | 弹层对齐方式,详情见 Overlay 文档 | string | 'tl bl' | |
+| popupTriggerType | 弹层触发方式 | 'click' \| 'hover' | 'click' | |
+| onVisibleChange | 弹层展示状态变化时的回调 | (visible: boolean, reason?: string) => void | - | |
+| popupStyle | 弹层自定义样式 | CSSProperties | - | |
+| popupClassName | 弹层自定义样式类 | string | - | |
+| popupProps | 弹层属性 | PopupProps | - | |
+| followTrigger | 跟随触发元素 | boolean | - | |
+| hasBorder | 是否有边框 | boolean | true | |
+| isPreview | 是否为预览态 | boolean | - | |
+| renderPreview | 预览态模式下渲染的内容 | (value: ValueType, props: TimePickerProps) => ReactNode | - | |
+| inputProps | 自定义输入框属性 | InputProps | - | |
+| disabledHours | 禁用小时的函数,TimePicker.RangePicker 时,函数需要返回 number[],且函数中没有 index 入参,非 TimePicker.RangePicker 时,函数需要返回 boolean,函数中有 index 入参 | (index?: number) => boolean \| number[] | - | |
+| disabledMinutes | 禁用分钟函数,TimePicker.RangePicker 时,函数需要返回 number[],且函数中没有 index 入参,非 TimePicker.RangePicker 时,函数需要返回 boolean,函数中有 index 入参 | (index?: number) => boolean \| number[] | - | |
+| disabledSeconds | 禁用秒钟函数,TimePicker.RangePicker 时,函数需要返回 number[],且函数中没有 index 入参,非 TimePicker.RangePicker 时,函数需要返回 boolean,函数中有 index 入参 | (index?: number) => boolean \| number[] | - | |
+| disabled | 禁用 | boolean \| boolean[] | false | |
+| placeholder | 输入框提示 | string \| string[] | - | |
+| value | 时间值(Dayjs 对象或时间字符串,受控状态使用) | Array\ | - | |
+| defaultValue | 时间初值(Dayjs 对象或时间字符串,非受控状态使用) | Array\ | - | |
+| onChange | 时间值改变时的回调 | (value: Array\) => void | - | |
+| onOk | 确定按钮点击时的回调 | (value: Array\) => void | - | |
+| preset | 预设值,会显示在时间面板下面 | PresetType[] | - | |
## ARIA and KeyBoard
-| 按键 | 说明 |
-| :-------------- | :---------------------------------------------------- |
-| Enter | 打开时间选择框 |
-| Esc | 关闭时间选择框 |
+| 按键 | 说明 |
+| :-------------- | :------------------------------------------------------------------------------ |
+| Enter | 打开时间选择框 |
+| Esc | 关闭时间选择框 |
| Up | 输入上一秒的时间 (如果 `disabledMinutes={true}` 则可能是上一分钟或者上一小时) |
| Down | 输入下一秒的时间 (如果 `disabledMinutes={true}` 则可能是下一分钟或者下一小时) |
-| Page Up | 输入上一分钟的时间 |
-| Page Down | 输入下一分钟的时间 |
-| Alt + Page Up | 输入上一小时的时间 |
-| Alt + Page Down | 输入下一小时的时间 |
+| Page Up | 输入上一分钟的时间 |
+| Page Down | 输入下一分钟的时间 |
+| Alt + Page Up | 输入上一小时的时间 |
+| Alt + Page Down | 输入下一小时的时间 |
diff --git a/components/time-picker2/__tests__/index-spec.js b/components/time-picker2/__tests__/index-spec.js
deleted file mode 100644
index f260aef5d8..0000000000
--- a/components/time-picker2/__tests__/index-spec.js
+++ /dev/null
@@ -1,422 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom';
-import Enzyme, { mount } from 'enzyme';
-import Adapter from 'enzyme-adapter-react-16';
-import assert from 'power-assert';
-import dayjs from 'dayjs';
-import TimePicker2 from '../index';
-import '../../time-picker/style';
-import { KEYCODE, dom } from '../../util';
-
-const { hasClass } = dom;
-Enzyme.configure({ adapter: new Adapter() });
-const defaultValue = dayjs('11:12:13', 'HH:mm:ss', true);
-
-const TimeRangePicker = TimePicker2.RangePicker;
-
-/* eslint-disable */
-describe('TimePicker2', () => {
- describe('render', () => {
- let wrapper;
-
- afterEach(() => {
- wrapper.unmount();
- wrapper = null;
- });
-
- it('should render time-picker', () => {
- wrapper = mount( );
- assert(wrapper.find('.next-time-picker2').length === 1);
- });
-
- it('should render with defaultValue', () => {
- wrapper = mount( );
- assert(wrapper.find('.next-time-picker2-input input').instance().value === '11:12:13');
- });
-
- it('should render with defaultValue as string', () => {
- wrapper = mount( );
- assert(wrapper.find('.next-time-picker2-input input').instance().value === '11:11:11');
- });
-
- it('should render with format', () => {
- wrapper = mount( );
- assert(wrapper.find('.next-time-picker2-input input').instance().value === '10:1:1');
- });
-
- it('should render with defaultVisible', () => {
- wrapper = mount( );
- assert(
- wrapper
- .find('.next-time-picker2-menu-hour .next-time-picker2-menu-item.next-selected')
- .instance().title === '11'
- );
- assert(
- wrapper
- .find(
- '.next-time-picker2-menu-minute .next-time-picker2-menu-item.next-selected'
- )
- .instance().title === '12'
- );
- assert(
- wrapper
- .find(
- '.next-time-picker2-menu-second .next-time-picker2-menu-item.next-selected'
- )
- .instance().title === '13'
- );
- });
-
- it('should render with value controlled', () => {
- wrapper = mount( );
- const newValue = dayjs('12:22:22', 'HH:mm:ss', true);
- wrapper.setProps({ value: newValue });
- assert(wrapper.find('.next-time-picker2-input input').instance().value === '12:22:22');
- });
-
- it('should render with visisble controlled', () => {
- wrapper = mount( );
- assert(wrapper.find('.next-time-picker2-body').length === 0);
- wrapper.setProps({ visible: true });
- assert(wrapper.find('.next-time-picker2-body').length === 1);
- });
-
- it('should render with step', () => {
- wrapper = mount( );
- assert(
- wrapper.find('.next-time-picker2-menu-second .next-time-picker2-menu-item')
- .length === 12
- );
- });
-
- it('should render menu items', () => {
- const renderTimeMenuItems = list => {
- return list.map(({ label, value }) => {
- return {
- value,
- label: value > 9 ? String(value) : `0${value}`,
- };
- });
- };
- wrapper = mount(
-
- );
-
- assert(
- wrapper
- .find('.next-time-picker2-menu-second .next-time-picker2-menu-item')
- .at(0)
- .text() === '00'
- );
-
- assert(
- wrapper
- .find('.next-time-picker2-menu-second .next-time-picker2-menu-item')
- .at(9)
- .text() === '09'
- );
-
- assert(
- wrapper
- .find('.next-time-picker2-menu-second .next-time-picker2-menu-item')
- .at(10)
- .text() === '10'
- );
- });
- it('should support preview mode render', () => {
- wrapper = mount( );
- assert(wrapper.find('.next-form-preview').length > 0);
- assert(wrapper.find('.next-form-preview').text() === '12:00:00');
- wrapper.setProps({
- renderPreview: value => {
- assert(value.format('HH:mm:ss') === '12:00:00');
- return 'Hello World';
- },
- });
- assert(wrapper.find('.next-form-preview').text() === 'Hello World');
- });
-
- it('should support string value', () => {
- wrapper = mount( );
- assert.equal(wrapper.find('.next-form-preview').text(), '12:00:00');
- });
- it('should support preview mode render when no value set', () => {
- wrapper = mount( );
- assert(wrapper.find('.next-form-preview').length > 0);
- });
- it('should support preview mode & setValue', () => {
- const container = document.createElement('div');
- document.body.appendChild(container);
- wrapper = mount( , { attachTo: container });
- assert(wrapper.find('.next-form-preview').length > 0);
- assert.equal(wrapper.find('.next-form-preview').text(), '');
- const value = dayjs('12:22:22', 'HH:mm:ss', true);
- wrapper.setProps({ value: value });
- assert(wrapper.find('.next-form-preview').length > 0);
- assert.equal(wrapper.find('.next-form-preview').text(), '12:22:22');
- });
- it('should support preview mode on type is range', () => {
- wrapper = mount( );
- assert(wrapper.find('.next-form-preview').length > 0);
- const startValue = dayjs('12:22:22', 'HH:mm:ss', true);
- const endValue = dayjs('17:22:22', 'HH:mm:ss', true);
- wrapper.setProps({ value: [startValue, endValue] });
- assert.equal(wrapper.find('.next-form-preview').text(), '12:22:22-17:22:22');
- wrapper.setProps({ value: [startValue] });
- assert.equal(wrapper.find('.next-form-preview').text(), '12:22:22-');
- wrapper.setProps({ value: [null, endValue] });
- assert.equal(wrapper.find('.next-form-preview').text(), '-17:22:22');
- });
- });
-
- describe('action', () => {
- let wrapper;
- let ret;
-
- afterEach(() => {
- wrapper.unmount();
- wrapper = null;
- ret = null;
- });
-
- it('should reset value', () => {
- ret = 'hello';
- wrapper = mount(
- {
- ret = val;
- }}
- />
- );
- wrapper.find('.next-icon-delete-filling').simulate('click');
- assert(ret === null);
- });
-
- it('should format value(hide hours)', () => {
- wrapper = mount( );
- wrapper.find('.next-time-picker2-input input').simulate('click');
- assert(wrapper.find('.next-time-picker2-menu-hour').length === 0);
- });
-
- it('should format value(hide seconds)', () => {
- wrapper = mount( );
- wrapper.find('.next-time-picker2-input input').simulate('click');
- assert(wrapper.find('.next-time-picker2-menu-second').length === 0);
- });
-
- it('should input value in picker', () => {
- wrapper = mount(
- {
- ret = val.format('HH:mm:ss');
- }}
- />
- );
- wrapper
- .find('.next-time-picker2-input input')
- .simulate('change', { target: { value: '20:00:00' } });
- wrapper
- .find('.next-time-picker2-input input')
- .simulate('keydown', { keyCode: KEYCODE.ENTER });
- assert(wrapper.find('.next-time-picker2-input input').instance().value === '20:00:00');
- assert(ret === '20:00:00');
- });
-
- it('should render presets & change value on clicking presets', () => {
- ret = null;
- wrapper = mount(
- {
- ret = val.format('HH:mm:ss');
- }}
- preset={[
- {
- label: 'now',
- name: 'preset-key',
- value: () => {
- return dayjs('13:12:11', 'HH:mm:ss', true);
- },
- },
- ]}
- />
- );
-
- wrapper.find('.next-time-picker2-input input').simulate('click');
-
- wrapper.find('.next-time-picker2-footer button').simulate('click');
-
- assert(ret === '13:12:11');
- });
-
- it('should select time-picker2 panel', () => {
- wrapper = mount(
- {
- ret = val.format('HH:mm:ss');
- }}
- />
- );
- wrapper.find('.next-time-picker2-input input').simulate('click');
- wrapper
- .find('.next-time-picker2-menu-hour .next-time-picker2-menu-item')
- .at(2)
- .simulate('click');
- assert(ret === '02:00:00');
- wrapper
- .find('.next-time-picker2-menu-minute .next-time-picker2-menu-item')
- .at(2)
- .simulate('click');
- assert(ret === '02:02:00');
- wrapper
- .find('.next-time-picker2-menu-second .next-time-picker2-menu-item')
- .at(2)
- .simulate('click');
- assert(ret === '02:02:02');
- });
-
- it('should keyboard date time input', () => {
- wrapper = mount( );
-
- const timeInput = wrapper.find('.next-time-picker2-input input');
- const instance = wrapper.instance().getInstance();
- timeInput.simulate('keydown', { keyCode: KEYCODE.DOWN });
- assert(instance.state.inputStr === '00:00:00');
- timeInput.simulate('keydown', { keyCode: KEYCODE.LEFT });
- timeInput.simulate('keydown', { keyCode: KEYCODE.DOWN, altKey: true });
- timeInput.simulate('keydown', { keyCode: KEYCODE.DOWN, shiftKey: true });
- timeInput.simulate('keydown', { keyCode: KEYCODE.DOWN, controlKey: true });
- assert(instance.state.inputStr === '00:00:00');
- timeInput.simulate('keydown', { keyCode: KEYCODE.DOWN });
- assert(instance.state.inputStr === '00:00:01');
- timeInput.simulate('keydown', { keyCode: KEYCODE.UP });
- assert(instance.state.inputStr === '00:00:00');
- timeInput.simulate('keydown', { keyCode: KEYCODE.PAGE_DOWN });
- assert(instance.state.inputStr === '00:01:00');
- timeInput.simulate('keydown', { keyCode: KEYCODE.PAGE_UP });
- assert(instance.state.inputStr === '00:00:00');
- timeInput.simulate('keydown', { keyCode: KEYCODE.PAGE_DOWN, altKey: true });
- assert(instance.state.inputStr === '01:00:00');
- timeInput.simulate('keydown', { keyCode: KEYCODE.PAGE_UP, altKey: true });
- assert(instance.state.inputStr === '00:00:00');
- });
- });
-
- describe('range', () => {
- let wrapper;
- let ret;
-
- afterEach(() => {
- wrapper.unmount();
- wrapper = null;
- ret = null;
- });
-
- it('should support default value', () => {
- wrapper = mount(
-
- );
-
- assert.deepEqual(getStrValue(wrapper), ['11:12:13', '12:12:13']);
- });
-
- it('should select value', async () => {
- wrapper = mount( );
-
- findInput(wrapper, 0).simulate('click');
- assert(findTime(wrapper, 12, 'hour').length === 2);
- findTime(wrapper, 12, 'hour').at(1).simulate('click');
- clickOk(wrapper);
-
- assert.deepEqual(getStrValue(wrapper), ['11:12:13', '12:00:00']);
- });
- it('should render with value controlled', () => {
- wrapper = mount(
-
- );
-
- assert.deepEqual(getStrValue(wrapper), ['11:12:13', '12:12:13']);
-
- findInput(wrapper, 0).simulate('click');
- assert(findTime(wrapper, 12, 'hour').length === 2);
- findTime(wrapper, 13, 'hour').at(1).simulate('click');
- clickOk(wrapper);
-
- assert.deepEqual(getStrValue(wrapper), ['11:12:13', '12:12:13']);
-
- const first = dayjs('11:13:00', 'HH:mm:ss', true);
- const second = dayjs('12:22:22', 'HH:mm:ss', true);
- wrapper.setProps({ value: [first, second] });
-
- assert.deepEqual(getStrValue(wrapper), ['11:13:00', '12:22:22']);
- });
- });
- describe('issues', () => {
- it('should has border when single time input is focusing', done => {
- const container = document.createElement('div');
- document.body.appendChild(container);
- ReactDOM.render( , container);
- const inputNode = document.querySelector('.next-time-picker2-input');
- inputNode.querySelector('input').click();
- assert(hasClass(inputNode, 'next-time-picker2-input-focus'));
- document.body.click();
- setTimeout(() => {
- assert(!hasClass(inputNode, 'next-time-picker2-input-focus'));
- ReactDOM.unmountComponentAtNode(container);
- document.body.removeChild(container);
- done();
- }, 1000);
- });
-
- it('should has border when time-range input is focusing', done => {
- const container = document.createElement('div');
- document.body.appendChild(container);
- ReactDOM.render( , container);
- const inputNode = document.querySelector('.next-time-picker2-input');
- inputNode.querySelectorAll('input')[0].click();
- assert(hasClass(inputNode, 'next-time-picker2-input-focus'));
- inputNode.querySelectorAll('input')[1].click();
- assert(hasClass(inputNode, 'next-time-picker2-input-focus'));
- document.body.click();
- setTimeout(() => {
- assert(!hasClass(inputNode, 'next-time-picker2-input-focus'));
- ReactDOM.unmountComponentAtNode(container);
- document.body.removeChild(container);
- done();
- }, 1000);
- });
-
- it('should support custom formatting , close #3651', () => {
- const div = document.createElement('div');
- document.body.appendChild(div);
- const wrapper = mount( , { attachTo: div });
- wrapper
- .find('.next-time-picker2-input input')
- .simulate('change', { target: { value: '12' } });
- wrapper.update();
- assert(
- document
- .querySelector('li[title="12"][role="option"]')
- .classList.contains('next-selected')
- );
- });
- });
-});
-
-function getStrValue(wrapper) {
- const inputEl = wrapper.find('.next-time-picker2-input input');
- return inputEl.length === 1 ? inputEl.instance().value : inputEl.map(el => el.instance().value);
-}
-
-function findInput(wrapper, idx) {
- const input = wrapper.find('.next-input > input');
- return idx !== undefined ? input.at(idx) : input;
-}
-
-function findTime(wrapper, strVal, mode = 'hour') {
- return wrapper.find(`.next-time-picker2-menu-${mode}>li[title=${strVal}]`);
-}
-
-function clickOk(wrapper) {
- wrapper.find('button.next-date-picker2-footer-ok').simulate('click');
-}
diff --git a/components/time-picker2/__tests__/index-spec.tsx b/components/time-picker2/__tests__/index-spec.tsx
new file mode 100644
index 0000000000..7bcb82251b
--- /dev/null
+++ b/components/time-picker2/__tests__/index-spec.tsx
@@ -0,0 +1,378 @@
+import React from 'react';
+import dayjs, { type Dayjs } from 'dayjs';
+import TimePicker2, { type TimePickerProps } from '../index';
+import '../../time-picker/style';
+
+const defaultValue = dayjs('11:12:13', 'HH:mm:ss', true);
+
+const TimeRangePicker = TimePicker2.RangePicker;
+
+function checkInputValues(compareValue?: string[] | string) {
+ cy.get('.next-time-picker2-input input').then($inputEl => {
+ if ($inputEl.length === 1) {
+ const value = $inputEl.val() as string | undefined;
+ expect(value).to.equal(compareValue);
+ } else {
+ const values: (string | number)[] = [];
+ $inputEl.each((index, el) => {
+ values.push((el as HTMLInputElement).value);
+ });
+ expect(values).to.deep.equal(compareValue);
+ }
+ });
+}
+
+function findInput(idx: number) {
+ if (idx !== undefined) {
+ return cy.get('.next-input > input').eq(idx);
+ }
+ return cy.get('.next-input > input');
+}
+
+function findTime(strVal: string | number, mode = 'hour') {
+ return cy.get(`.next-time-picker2-menu-${mode}>li[title=${strVal}]`);
+}
+
+function clickOk() {
+ cy.get('button.next-date-picker2-footer-ok').click();
+}
+
+describe('TimePicker2', () => {
+ describe('render', () => {
+ it('should render time-picker', () => {
+ cy.mount( );
+ cy.get('.next-time-picker2').should('exist');
+ });
+
+ it('should render with defaultValue', () => {
+ cy.mount( );
+ cy.get('.next-time-picker2-input input').should('have.value', '11:12:13');
+ });
+
+ it('should render with defaultValue as string', () => {
+ cy.mount( );
+ cy.get('.next-time-picker2-input input').should('have.value', '11:11:11');
+ });
+
+ it('should render with format', () => {
+ cy.mount( );
+ cy.get('.next-time-picker2-input input').should('have.value', '10:1:1');
+ });
+
+ it('should render with defaultVisible', () => {
+ cy.mount( );
+ cy.get(
+ '.next-time-picker2-menu-hour .next-time-picker2-menu-item.next-selected'
+ ).should('have.attr', 'title', '11');
+ cy.get(
+ '.next-time-picker2-menu-minute .next-time-picker2-menu-item.next-selected'
+ ).should('have.attr', 'title', '12');
+ cy.get(
+ '.next-time-picker2-menu-second .next-time-picker2-menu-item.next-selected'
+ ).should('have.attr', 'title', '13');
+ });
+
+ it('should render with value controlled', () => {
+ cy.mount( ).as('Demo');
+ const newValue = dayjs('12:22:22', 'HH:mm:ss', true);
+ cy.rerender('Demo', { value: newValue });
+ cy.get('.next-time-picker2-input input').should('have.value', '12:22:22');
+ });
+
+ it('should render with visisble controlled', () => {
+ cy.mount( ).as('Demo');
+ cy.get('.next-time-picker2-body').should('not.exist');
+ cy.rerender('Demo', { visible: true });
+ cy.get('.next-time-picker2-body').should('exist');
+ });
+
+ it('should render with step', () => {
+ cy.mount( );
+ cy.get('.next-time-picker2-menu-second .next-time-picker2-menu-item').should(
+ 'have.length',
+ 12
+ );
+ });
+
+ it('should render menu items', () => {
+ const renderTimeMenuItems: TimePickerProps['renderTimeMenuItems'] = list => {
+ return list.map(({ value }) => {
+ return {
+ value,
+ label: value > 9 ? String(value) : `0${value}`,
+ };
+ });
+ };
+ cy.mount( );
+ cy.get('.next-time-picker2-menu-second .next-time-picker2-menu-item')
+ .eq(0)
+ .should('have.text', '00');
+ cy.get('.next-time-picker2-menu-second .next-time-picker2-menu-item')
+ .eq(9)
+ .should('have.text', '09');
+ cy.get('.next-time-picker2-menu-second .next-time-picker2-menu-item')
+ .eq(10)
+ .should('have.text', '10');
+ });
+ it('should support preview mode render', () => {
+ cy.mount( ).as('Demo');
+ cy.get('.next-form-preview').should('exist');
+ cy.get('.next-form-preview').should('have.text', '12:00:00');
+ const handleRenderPreview = cy.spy();
+ cy.rerender('Demo', {
+ renderPreview: (value: Dayjs) => {
+ handleRenderPreview(value.format('HH:mm:ss'));
+ return 'Hello World';
+ },
+ });
+ cy.wrap(handleRenderPreview).should('be.calledWith', '12:00:00');
+ cy.get('.next-form-preview').should('have.text', 'Hello World');
+ });
+
+ it('should support string value', () => {
+ cy.mount( );
+ cy.get('.next-form-preview').should('have.text', '12:00:00');
+ });
+ it('should support preview mode render when no value set', () => {
+ cy.mount( );
+ cy.get('.next-form-preview').should('exist');
+ });
+ it('should support preview mode & setValue', () => {
+ cy.mount( ).as('Demo');
+ cy.get('.next-form-preview').should('exist');
+ cy.get('.next-form-preview').should('have.text', '');
+ const value = dayjs('12:22:22', 'HH:mm:ss', true);
+ cy.rerender('Demo', { value });
+ cy.get('.next-form-preview').should('exist');
+ cy.get('.next-form-preview').should('have.text', '12:22:22');
+ });
+ it('should support preview mode on type is range', () => {
+ cy.mount( ).as('Demo');
+ cy.get('.next-form-preview').should('exist');
+
+ const startValue = dayjs('12:22:22', 'HH:mm:ss', true);
+ const endValue = dayjs('17:22:22', 'HH:mm:ss', true);
+ cy.rerender('Demo', { value: [startValue, endValue] });
+ cy.get('.next-form-preview').should('have.text', '12:22:22-17:22:22');
+
+ cy.rerender('Demo', { value: [startValue] });
+ cy.get('.next-form-preview').should('have.text', '12:22:22-');
+
+ cy.rerender('Demo', { value: [null, endValue] });
+ cy.get('.next-form-preview').should('have.text', '-17:22:22');
+ });
+ });
+
+ describe('action', () => {
+ it('should reset value', () => {
+ const handleChange = cy.spy();
+ cy.mount( );
+ cy.get('.next-icon-delete-filling').click();
+ cy.wrap(handleChange).should('be.calledWith', null);
+ });
+
+ it('should format value(hide hours)', () => {
+ cy.mount( );
+ cy.get('.next-time-picker2-input input').click();
+ cy.get('.next-time-picker2-menu-hour').should('not.exist');
+ });
+
+ it('should format value(hide seconds)', () => {
+ cy.mount( );
+ cy.get('.next-time-picker2-input input').click();
+ cy.get('.next-time-picker2-menu-second').should('not.exist');
+ });
+
+ it('should input value in picker', () => {
+ const handleChange = cy.spy();
+ cy.mount(
+ {
+ handleChange((val as Dayjs).format('HH:mm:ss'));
+ }}
+ />
+ );
+ cy.get('.next-time-picker2-input input').type('20:00:00');
+ cy.get('.next-time-picker2-input input').trigger('keydown', { keyCode: 13 });
+ cy.get('.next-time-picker2-input input').should('have.value', '20:00:00');
+ cy.wrap(handleChange).should('be.calledWith', '20:00:00');
+ });
+
+ it('should render presets & change value on clicking presets', () => {
+ const handleChange = cy.spy();
+ cy.mount(
+ {
+ handleChange((val as Dayjs).format('HH:mm:ss'));
+ }}
+ preset={[
+ {
+ label: 'now',
+ name: 'preset-key',
+ value: () => {
+ return dayjs('13:12:11', 'HH:mm:ss', true);
+ },
+ },
+ ]}
+ />
+ );
+ cy.get('.next-time-picker2-input input').click();
+
+ cy.get('.next-time-picker2-footer button').click();
+ cy.wrap(handleChange).should('be.calledWith', '13:12:11');
+ });
+
+ it('should select time-picker2 panel', () => {
+ const handleChange = cy.spy();
+ cy.mount(
+ {
+ if (dayjs.isDayjs(val)) {
+ handleChange(val.format('HH:mm:ss'));
+ } else {
+ handleChange(val);
+ }
+ }}
+ />
+ );
+ cy.get('.next-time-picker2-input input').click();
+ cy.get('.next-time-picker2-menu-hour .next-time-picker2-menu-item').eq(2).click();
+ cy.wrap(handleChange).should('be.calledWith', '02:00:00');
+ cy.get('.next-time-picker2-menu-minute .next-time-picker2-menu-item').eq(2).click();
+ cy.wrap(handleChange).should('be.calledWith', '02:02:00');
+ cy.get('.next-time-picker2-menu-second .next-time-picker2-menu-item').eq(2).click();
+ cy.wrap(handleChange).should('be.calledWith', '02:02:02');
+ });
+
+ it('should keyboard date time input', () => {
+ cy.mount( );
+ const timeInput = '.next-time-picker2-input input';
+
+ cy.get(timeInput).type('{downarrow}');
+ cy.get(timeInput).should('have.value', '00:00:00');
+
+ cy.get(timeInput).type('{leftarrow}', { force: true });
+ cy.get(timeInput).type('{alt+downarrow}', { force: true });
+ cy.get(timeInput).type('{shift+downarrow}', { force: true });
+
+ cy.get(timeInput).should('have.value', '00:00:00');
+
+ cy.get(timeInput).type('{downarrow}');
+ cy.get(timeInput).should('have.value', '00:00:01');
+
+ cy.get(timeInput).type('{uparrow}');
+ cy.get(timeInput).should('have.value', '00:00:00');
+
+ cy.get(timeInput).type('{pagedown}');
+ cy.get(timeInput).should('have.value', '00:01:00');
+
+ cy.get(timeInput).type('{pageup}');
+ cy.get(timeInput).should('have.value', '00:00:00');
+
+ cy.get(timeInput).type('{alt+pageDown}');
+ cy.get(timeInput).should('have.value', '01:00:00');
+
+ cy.get(timeInput).type('{alt+pageUp}');
+ cy.get(timeInput).should('have.value', '00:00:00');
+ });
+
+ it('should keyboard date time input', () => {
+ cy.mount( );
+ const timeInput = '.next-time-picker2-input input';
+
+ cy.get(timeInput).type('{ctrl+downarrow}', { force: true });
+ cy.get(timeInput).should('have.value', '00:00:00');
+ });
+ });
+
+ describe('range', () => {
+ it('should support default value', () => {
+ cy.mount(
+
+ );
+
+ checkInputValues(['11:12:13', '12:12:13']);
+ });
+
+ it('should select value', () => {
+ cy.mount( );
+
+ findInput(0).click();
+ findTime(12, 'hour').should('have.length', 2);
+ findTime(12, 'hour').eq(1).click();
+ clickOk();
+
+ checkInputValues(['11:12:13', '12:00:00']);
+ });
+
+ it('should render with value controlled', () => {
+ cy.mount( ).as(
+ 'Demo'
+ );
+
+ checkInputValues(['11:12:13', '12:12:13']);
+ findInput(0).click();
+ findTime(12, 'hour').should('have.length', 2);
+ findTime(13, 'hour').eq(1).click();
+ clickOk();
+
+ checkInputValues(['11:12:13', '12:12:13']);
+
+ const first = dayjs('11:13:00', 'HH:mm:ss', true);
+ const second = dayjs('12:22:22', 'HH:mm:ss', true);
+ cy.rerender('Demo', { value: [first, second] });
+ checkInputValues(['11:13:00', '12:22:22']);
+ });
+ });
+ describe('issues', () => {
+ it('should has border when single time input is focusing', () => {
+ cy.mount(
+
+
+
+ );
+ cy.get('.next-time-picker2-input input').click();
+ cy.get('.next-time-picker2-input').should(
+ 'have.class',
+ 'next-time-picker2-input-focus'
+ );
+ cy.get('body').click();
+
+ cy.get('.next-time-picker2-input').should(
+ 'not.have.class',
+ 'next-time-picker2-input-focus'
+ );
+ });
+
+ it('should has border when time-range input is focusing', () => {
+ cy.mount(
+
+
+
+ );
+ cy.get('.next-time-picker2-input input').eq(0).click();
+
+ cy.get('.next-time-picker2-input').should(
+ 'have.class',
+ 'next-time-picker2-input-focus'
+ );
+ cy.get('.next-time-picker2-input input').eq(1).click();
+ cy.get('.next-time-picker2-input').should(
+ 'have.class',
+ 'next-time-picker2-input-focus'
+ );
+ cy.get('body').click();
+ cy.get('.next-time-picker2-input').should(
+ 'not.have.class',
+ 'next-time-picker2-input-focus'
+ );
+ });
+
+ it('should support custom formatting , close #3651', () => {
+ cy.mount( );
+ cy.get('.next-time-picker2-input input').type('12');
+
+ cy.get('li[title="12"][role="option"]').should('have.class', 'next-selected');
+ });
+ });
+});
diff --git a/components/time-picker2/constant.js b/components/time-picker2/constant.ts
similarity index 100%
rename from components/time-picker2/constant.js
rename to components/time-picker2/constant.ts
diff --git a/components/time-picker2/index.d.ts b/components/time-picker2/index.d.ts
deleted file mode 100644
index b7466c1de4..0000000000
--- a/components/time-picker2/index.d.ts
+++ /dev/null
@@ -1,206 +0,0 @@
-///
-
-import React from 'react';
-import { CommonProps } from '../util';
-import { PopupProps } from '../overlay';
-import { InputProps } from '../input';
-import { ButtonProps } from '../button';
-import { Dayjs, ConfigType } from 'dayjs';
-
-interface IPresetType {
- label?: string;
- value?: ConfigType | (() => ConfigType);
-}
-interface HTMLAttributesWeak extends React.HTMLAttributes {
- defaultValue?: any;
- onChange?: any;
-}
-
-export interface DatePreset extends ButtonProps {
- label: string;
- // 时间值(dayjs 对象或时间字符串)或者返回时间值的函数
- value: any;
-}
-
-export interface RangePreset {
- [propName: string]: Dayjs[];
-}
-
-export type StateValue = Dayjs | null;
-
-export interface TimePickerProps extends HTMLAttributesWeak, CommonProps {
- /**
- * 按钮的文案
- */
- label?: React.ReactNode;
- name?: string;
-
- /**
- * 输入框状态
- */
- state?: 'error' | 'success';
-
- /**
- * 输入框提示
- */
- placeholder?: string;
-
- /**
- * 时间值(dayjs 对象或时间字符串,受控状态使用)
- */
- value?: ConfigType;
-
- /**
- * 时间初值(dayjs 对象或时间字符串,非受控状态使用)
- */
- defaultValue?: ConfigType;
-
- /**
- * 时间选择框的尺寸
- */
- size?: 'small' | 'medium' | 'large';
-
- /**
- * 是否允许清空时间
- */
- hasClear?: boolean;
-
- /**
- * 时间的格式
- * https://dayjs.gitee.io/docs/zh-CN/display/format
- */
- format?: string;
-
- /**
- * 小时选项步长
- */
- hourStep?: number;
-
- /**
- * 分钟选项步长
- */
- minuteStep?: number;
-
- /**
- * 秒钟选项步长
- */
- secondStep?: number;
-
- /**
- * 禁用小时函数
- */
- disabledHours?: (index: number) => boolean;
-
- /**
- * 禁用分钟函数
- */
- disabledMinutes?: (index: number) => boolean;
-
- /**
- * 禁用秒钟函数
- */
- disabledSeconds?: (index: number) => boolean;
-
- /**
- * 弹层是否显示(受控)
- */
- visible?: boolean;
-
- /**
- * 弹层默认是否显示(非受控)
- */
- defaultVisible?: boolean;
-
- /**
- * 弹层容器
- */
- popupContainer?: string | HTMLElement | ((target: HTMLElement) => HTMLElement);
-
- /**
- * 弹层对齐方式, 详情见Overlay 文档
- */
- popupAlign?: string;
-
- /**
- * 弹层触发方式
- */
- popupTriggerType?: 'click' | 'hover';
-
- /**
- * 弹层展示状态变化时的回调
- */
- onVisibleChange?: (visible: boolean, reason: string) => void;
-
- /**
- * 弹层自定义样式
- */
- popupStyle?: React.CSSProperties;
-
- /**
- * 弹层自定义样式类
- */
- popupClassName?: string;
-
- /**
- * 弹层属性
- */
- popupProps?: PopupProps;
-
- /**
- * 是否禁用
- */
- disabled?: boolean;
-
- /**
- * 输入框是否有边框
- */
- hasBorder?: boolean;
-
- /**
- * 透传给 Input 的属性
- */
- inputProps?: InputProps;
-
- /**
- * 是否为预览态
- */
- isPreview?: boolean;
-
- /**
- * 预览态模式下渲染的内容
- * @param value 时间
- */
- renderPreview?: (value: StateValue | [StateValue, StateValue]) => React.ReactNode;
-
- /**
- * 预设值,会显示在时间面板下面
- */
- ranges?: RangePreset | DatePreset[];
-
- /**
- * 时间值改变时的回调
- */
- onChange?: (date: Dayjs, dateString: string) => void;
-}
-
-export interface RangePickerProps
- extends Omit<
- TimePickerProps,
- 'value' | 'placeholder' | 'defaultValue' | 'onOk' | 'disabled' | 'onChange' | 'preset'
- > {
- value?: Array;
- defaultValue?: Array;
- onOk?: (value: Array, strVal: Array) => void;
- onChange?: (value: Array, strVal: Array) => void;
- placeholder?: string | Array;
- disabled?: boolean | boolean[];
- preset?: IPresetType | IPresetType[];
-}
-
-export class RangePicker extends React.Component {
- type: 'range';
-}
-
-export default class TimePicker extends React.Component {
- static RangePicker: typeof RangePicker;
-}
diff --git a/components/time-picker2/index.jsx b/components/time-picker2/index.jsx
deleted file mode 100644
index e2323d83bd..0000000000
--- a/components/time-picker2/index.jsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import React from 'react';
-import ConfigProvider from '../config-provider';
-import TimePicker from './time-picker';
-
-const ConfigTimePicker = ConfigProvider.config(TimePicker);
-
-ConfigTimePicker.RangePicker = React.forwardRef((props, ref) => );
-ConfigTimePicker.RangePicker.displayName = 'RangePicker';
-
-export default ConfigTimePicker;
diff --git a/components/time-picker2/index.tsx b/components/time-picker2/index.tsx
new file mode 100644
index 0000000000..d5f8d2f939
--- /dev/null
+++ b/components/time-picker2/index.tsx
@@ -0,0 +1,21 @@
+import React, { type ComponentRef, type LegacyRef } from 'react';
+
+import { assignSubComponent } from '../util/component';
+import ConfigProvider from '../config-provider';
+import TimePicker from './time-picker';
+import type { TimePickerProps, ValueType, RangePickerProps, PresetType } from './types';
+
+const ConfigTimePicker = ConfigProvider.config(TimePicker);
+
+const TimePickerWithSub = assignSubComponent(TimePicker, {
+ RangePicker: React.forwardRef(
+ (props: TimePickerProps, ref: LegacyRef>) => (
+
+ )
+ ),
+});
+TimePickerWithSub.RangePicker.displayName = 'RangePicker';
+
+export default TimePickerWithSub;
+
+export type { TimePickerProps, ValueType, RangePickerProps, PresetType };
diff --git a/components/time-picker2/mobile/index.jsx b/components/time-picker2/mobile/index.tsx
similarity index 100%
rename from components/time-picker2/mobile/index.jsx
rename to components/time-picker2/mobile/index.tsx
diff --git a/components/time-picker2/module/date-input.jsx b/components/time-picker2/module/date-input.tsx
similarity index 70%
rename from components/time-picker2/module/date-input.jsx
rename to components/time-picker2/module/date-input.tsx
index 1605dd7383..04bf518391 100644
--- a/components/time-picker2/module/date-input.jsx
+++ b/components/time-picker2/module/date-input.tsx
@@ -2,15 +2,17 @@ import React from 'react';
import { polyfill } from 'react-lifecycles-compat';
import PT from 'prop-types';
import classnames from 'classnames';
+import { type Dayjs } from 'dayjs';
+
import SharedPT from '../prop-types';
import { TIME_INPUT_TYPE } from '../constant';
import { func, datejs, obj } from '../../util';
import { fmtValue } from '../../date-picker2/util';
-
-import Input from '../../input';
+import Input, { type InputProps } from '../../input';
import Icon from '../../icon';
+import type { DateInputProps } from '../types';
-class DateInput extends React.Component {
+class DateInput extends React.Component {
static propTypes = {
prefix: PT.string,
rtl: PT.bool,
@@ -44,45 +46,50 @@ class DateInput extends React.Component {
size: 'medium',
};
- constructor(props) {
+ prefixCls: string;
+ input?: InstanceType | InstanceType[];
+
+ constructor(props: DateInputProps) {
super(props);
this.prefixCls = `${props.prefix}time-picker2-input`;
}
- setInputRef = (el, index) => {
+ setInputRef = (el: InstanceType, index?: number) => {
if (this.props.isRange) {
if (!this.input) {
this.input = [];
}
- this.input[index] = el;
+ (this.input as (InstanceType | undefined)[])[index!] = el;
} else {
this.input = el;
}
};
- setValue = v => {
+ setValue = (v: string | number | null) => {
const { isRange, inputType, value } = this.props;
- let newVal = v;
+ let newVal: string | number | null | Array = v;
if (isRange) {
- newVal = [...value];
- newVal[inputType] = v;
+ newVal = [...value!];
+ newVal[inputType!] = v;
}
return newVal;
};
- formatter = v => {
+ formatter = (v: Dayjs) => {
const { format } = this.props;
return typeof format === 'function' ? format(v) : v.format(format);
};
- onInput = (v, e, eventType) => {
+ onInput: InputProps['onChange'] = (v, e, eventType) => {
+ // @ts-expect-error v 的类型是 string | number,this.setValue(v) 返回类型是 string | number | null,此处不应该重新赋值,应该定一个新的变量处理
v = this.setValue(v);
if (eventType === 'clear') {
+ // @ts-expect-error v 的类型是 string | number,this.setValue(v) 返回类型是 string | number | null,此处不应该重新赋值,应该定一个新的变量处理
v = null;
e.stopPropagation();
}
@@ -90,7 +97,7 @@ class DateInput extends React.Component {
func.invoke(this.props, 'onInput', [v, eventType]);
};
- handleTypeChange = inputType => {
+ handleTypeChange = (inputType: number) => {
if (inputType !== this.props.inputType) {
func.invoke(this.props, 'onInputTypeChange', [inputType]);
}
@@ -98,7 +105,7 @@ class DateInput extends React.Component {
getPlaceholder = () => {
const { isRange } = this.props;
- let holder = this.props.placeholder;
+ let holder: string | string[] | undefined = this.props.placeholder;
if (isRange && !Array.isArray(holder)) {
holder = Array(2).fill(holder);
@@ -116,7 +123,7 @@ class DateInput extends React.Component {
let size = 0;
if (isRange) {
- const fmtStr = fmtValue([value, value].map(datejs), format);
+ const fmtStr = fmtValue([value, value].map(datejs), format) as string[];
size = Math.max(...fmtStr.map(s => (s && s.length) || 0));
} else {
const fmtStr = fmtValue(datejs(value), format);
@@ -153,7 +160,9 @@ class DateInput extends React.Component {
const placeholder = this.getPlaceholder();
const htmlSize = this.getHtmlSize();
- const sharedProps = {
+ // @ts-expect-error 下面 pickProps 使用错误,导致报错
+ const sharedProps: InputProps = {
+ // @ts-expect-error 正确写法应该是 obj.pickProps(Input.propTypes, restProps)
...obj.pickProps(restProps, Input),
...inputProps,
size,
@@ -167,7 +176,7 @@ class DateInput extends React.Component {
onKeyDown: onKeyDown,
};
- let rangeProps;
+ let rangeProps: InputProps[];
if (isRange) {
rangeProps = [TIME_INPUT_TYPE.BEGIN, TIME_INPUT_TYPE.END].map(idx => {
const _disabled = Array.isArray(disabled) ? disabled[idx] : disabled;
@@ -175,10 +184,10 @@ class DateInput extends React.Component {
return {
...sharedProps,
autoFocus,
- placeholder: placeholder[idx],
- value: value[idx] || '',
+ placeholder: placeholder![idx],
+ value: value![idx] || '',
disabled: _disabled,
- ref: ref => setInputRef(ref, idx),
+ ref: (ref: InstanceType) => setInputRef(ref, idx),
onFocus: _disabled ? undefined : () => handleTypeChange(idx),
className: classnames({
[`${prefixCls}-active`]: inputType === idx,
@@ -192,29 +201,32 @@ class DateInput extends React.Component {
{
[`${prefixCls}-focus`]: focus,
[`${prefixCls}-noborder`]: !hasBorder,
- [`${prefixCls}-disabled`]: isRange && Array.isArray(disabled) ? disabled.every(v => v) : disabled,
+ [`${prefixCls}-disabled`]:
+ isRange && Array.isArray(disabled) ? disabled.every(v => v) : disabled,
[`${prefixCls}-error`]: state === 'error',
}
);
- const calendarIcon = ;
+ const calendarIcon = (
+
+ );
return (
{isRange ? (
{separator}
@@ -223,12 +235,12 @@ class DateInput extends React.Component {
{...sharedProps}
label={label}
state={state}
- disabled={disabled}
- hasClear={!state && hasClear}
- placeholder={placeholder}
+ disabled={disabled as boolean}
+ hasClear={!state && hasClear!}
+ placeholder={placeholder as string}
autoFocus={autoFocus} // eslint-disable-line jsx-a11y/no-autofocus
ref={setInputRef}
- value={value || ''}
+ value={(value as string) || ''}
hint={state ? null : calendarIcon}
/>
)}
diff --git a/components/time-picker2/module/time-menu.jsx b/components/time-picker2/module/time-menu.tsx
similarity index 72%
rename from components/time-picker2/module/time-menu.jsx
rename to components/time-picker2/module/time-menu.tsx
index 2823be07df..de6dd016a5 100644
--- a/components/time-picker2/module/time-menu.jsx
+++ b/components/time-picker2/module/time-menu.tsx
@@ -2,11 +2,12 @@ import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { checkDayjsObj } from '../utils';
+import type { TimeMenuListItem, TimeMenuProps } from '../types';
-function scrollTo(element, to, duration) {
+function scrollTo(element: HTMLElement, to: number, duration: number) {
const requestAnimationFrame =
window.requestAnimationFrame ||
- function requestAnimationFrameTimeout(...params) {
+ function requestAnimationFrameTimeout(...params: [() => void]) {
return setTimeout(params[0], 10);
};
@@ -31,7 +32,7 @@ function scrollTo(element, to, duration) {
const noop = () => {};
-class TimeMenu extends React.Component {
+class TimeMenu extends React.Component
{
static propTypes = {
prefix: PropTypes.string,
title: PropTypes.node,
@@ -48,40 +49,41 @@ class TimeMenu extends React.Component {
static defaultProps = {
step: 1,
disabledItems: () => false,
- renderTimeMenuItems: list => list,
+ renderTimeMenuItems: (list: unknown) => list,
onSelect: () => {},
disabled: false,
};
+ menu: HTMLUListElement | null;
+ menuWrapper: HTMLDivElement | null;
+ prefixCls = `${this.props.prefix}time-picker2`;
componentDidMount() {
this.scrollToSelected(0);
}
- componentDidUpdate(prevProps) {
+ componentDidUpdate(prevProps: TimeMenuProps) {
if (prevProps.activeIndex !== this.props.activeIndex) {
this.scrollToSelected(120);
}
}
- prefixCls = `${this.props.prefix}time-picker2`;
-
scrollToSelected(duration = 0) {
const { activeIndex, step } = this.props;
- const targetIndex = Math.floor((activeIndex || 0) / step);
- const firstItem = this.menu.children[targetIndex];
+ const targetIndex = Math.floor((activeIndex || 0) / step!);
+ const firstItem = this.menu!.children[targetIndex] as HTMLElement;
const offsetTo = firstItem.offsetTop;
- scrollTo(this.menu, offsetTo, duration);
+ scrollTo(this.menu!, offsetTo, duration);
}
- _menuRefHandler = ref => {
+ _menuRefHandler = (ref: HTMLUListElement | null) => {
this.menu = ref;
};
- _menuWrapperRefHandler = ref => {
+ _menuWrapperRefHandler = (ref: HTMLDivElement | null) => {
this.menuWrapper = ref;
};
- createMenuItems = list => {
+ createMenuItems = (list: Array) => {
const {
prefix,
mode,
@@ -92,21 +94,22 @@ class TimeMenu extends React.Component {
renderTimeMenuItems,
value: timeValue,
} = this.props;
- list = renderTimeMenuItems(list, mode, timeValue) || list;
+ list = renderTimeMenuItems!(list, mode, timeValue) || list;
return list.map(({ label, value }) => {
- const isDisabled = disabled || disabledItems(value);
+ const isDisabled = disabled || disabledItems!(value);
const itemCls = classnames({
[`${this.prefixCls}-menu-item`]: true,
[`${prefix}disabled`]: isDisabled,
[`${prefix}selected`]: value === activeIndex,
});
- const onClick = isDisabled ? noop : () => onSelect(value, mode);
+ const onClick = isDisabled ? noop : () => onSelect!(value, mode);
return (
{/* {menuTitle} */}
-
+
{this.createMenuItems(list)}
diff --git a/components/time-picker2/panel.jsx b/components/time-picker2/panel.tsx
similarity index 63%
rename from components/time-picker2/panel.jsx
rename to components/time-picker2/panel.tsx
index 5396aef9f4..2119743a82 100644
--- a/components/time-picker2/panel.jsx
+++ b/components/time-picker2/panel.tsx
@@ -1,78 +1,30 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
+import type { Dayjs } from 'dayjs';
+
import nextLocale from '../locale/zh-cn';
import { func, datejs, pickAttrs } from '../util';
import TimeMenu from './module/time-menu';
import SharedPT from './prop-types';
+import type { PannelType, PanelProps, DisabledItems } from './types';
const { noop } = func;
-class TimePickerPanel extends Component {
+class TimePickerPanel extends Component {
static propTypes = {
prefix: PropTypes.string,
- /**
- * 时间值(dayjs 对象)
- */
value: SharedPT.value,
- /**
- * 是否显示小时
- */
showHour: PropTypes.bool,
- /**
- * 是否显示分钟
- */
showMinute: PropTypes.bool,
- /**
- * 是否显示秒
- */
showSecond: PropTypes.bool,
- /**
- * 小时选项步长
- */
hourStep: PropTypes.number,
- /**
- * 分钟选项步长
- */
minuteStep: PropTypes.number,
- /**
- * 秒钟选项步长
- */
secondStep: PropTypes.number,
- /**
- * 禁用小时函数
- * @param {Number} index 时 0 - 23
- * @return {Boolean} 是否禁用
- */
disabledHours: PropTypes.func,
- /**
- * 禁用分钟函数
- * @param {Number} index 分 0 - 59
- * @return {Boolean} 是否禁用
- */
disabledMinutes: PropTypes.func,
- /**
- * 禁用秒函数
- * @param {Number} index 秒 0 - 59
- * @return {Boolean} 是否禁用
- */
disabledSeconds: PropTypes.func,
- /**
- * 渲染的可选择时间列表
- * [{
- * label: '01',
- * value: 1
- * }]
- * @param {Array} list 默认渲染的列表
- * @param {String} mode 渲染的菜单 hour, minute, second
- * @param {dayjs} value 当前时间,可能为 null
- * @return {Array} 返回需要渲染的数据
- */
renderTimeMenuItems: PropTypes.func,
- /**
- * 选择某个日期值时的回调
- * @param {Object} 选中后的日期值
- */
onSelect: PropTypes.func,
isRange: PropTypes.bool,
locale: PropTypes.object,
@@ -98,11 +50,15 @@ class TimePickerPanel extends Component {
/**
*
- * @param {enum} panelType 'start' | 'end' | 'panel'
- * @param {*} index
- * @param {*} type 'hour' | 'minute' | 'second'
+ * @param panelType - 'start' | 'end' | 'panel'
+ * @param index - number
+ * @param type - 'hour' | 'minute' | 'second'
*/
- onSelectMenuItem = (panelType, index, type) => {
+ onSelectMenuItem = (
+ panelType: PannelType,
+ index: number,
+ type: 'hour' | 'minute' | 'second'
+ ) => {
const { value, isRange } = this.props;
const valueArr = Array.isArray(value) ? value : [value];
const val = panelType === 'end' ? valueArr[1] : valueArr[0];
@@ -125,9 +81,9 @@ class TimePickerPanel extends Component {
const nextValueArray = [];
if (panelType === 'start') {
nextValueArray[0] = newValue;
- nextValueArray[1] = value[1];
+ nextValueArray[1] = (value as Dayjs[])[1];
} else if (panelType === 'end') {
- nextValueArray[0] = value[0];
+ nextValueArray[0] = (value as Dayjs[])[0];
nextValueArray[1] = newValue;
}
@@ -137,7 +93,7 @@ class TimePickerPanel extends Component {
}
};
- getDisabledItems = () => {
+ getDisabledItems: () => DisabledItems = () => {
const { disabledHours, disabledMinutes, disabledSeconds, value, isRange } = this.props;
const disableds = {
@@ -146,28 +102,36 @@ class TimePickerPanel extends Component {
newDisabledSeconds: [disabledSeconds],
};
if (!isRange) {
- return disableds;
+ return disableds as DisabledItems;
}
- const dHours = disabledHours() || [];
- const dMinutes = disabledMinutes() || [];
- const dSeconds = disabledSeconds() || [];
+ const dHours = (disabledHours!() || []) as number[];
+ const dMinutes = (disabledMinutes!() || []) as number[];
+ const dSeconds = (disabledSeconds!() || []) as number[];
- const v0 = value[0];
- const v1 = value[1];
+ // fixme: value 可以换为 valueArr
+ const v0 = (value as Dayjs[])[0];
+ const v1 = (value as Dayjs[])[1];
const hoursEqual = () => v0 && v1 && v0.hour() === v1.hour();
- const minutesEqual = () => v0 && v1 && v0.hour() === v1.hour() && v0.minute() === v1.minute();
+ const minutesEqual = () =>
+ v0 && v1 && v0.hour() === v1.hour() && v0.minute() === v1.minute();
- disableds.newDisabledHours[0] = h => (v1 && h > v1.hour()) || dHours.indexOf(h) > -1;
- disableds.newDisabledMinutes[0] = m => (v1 && (hoursEqual() && m > v1.minute())) || dMinutes.indexOf(m) > -1;
- disableds.newDisabledSeconds[0] = s => (v1 && (minutesEqual() && s > v1.second())) || dSeconds.indexOf(s) > -1;
+ disableds.newDisabledHours[0] = (h: number) =>
+ (v1 && h > v1.hour()) || dHours.indexOf(h) > -1;
+ disableds.newDisabledMinutes[0] = (m: number) =>
+ (v1 && hoursEqual() && m > v1.minute()) || dMinutes.indexOf(m) > -1;
+ disableds.newDisabledSeconds[0] = (s: number) =>
+ (v1 && minutesEqual() && s > v1.second()) || dSeconds.indexOf(s) > -1;
- disableds.newDisabledHours[1] = h => (v0 && h < v0.hour()) || dHours.indexOf(h) > -1;
- disableds.newDisabledMinutes[1] = m => (v0 && m < (hoursEqual() && v0.minute())) || dMinutes.indexOf(m) > -1;
- disableds.newDisabledSeconds[1] = s => (v0 && (minutesEqual() && s < v0.second())) || dSeconds.indexOf(s) > -1;
+ disableds.newDisabledHours[1] = (h: number) =>
+ (v0 && h < v0.hour()) || dHours.indexOf(h) > -1;
+ disableds.newDisabledMinutes[1] = (m: number) =>
+ (v0 && m < ((hoursEqual() && v0.minute()) as number)) || dMinutes.indexOf(m) > -1;
+ disableds.newDisabledSeconds[1] = (s: number) =>
+ (v0 && minutesEqual() && s < v0.second()) || dSeconds.indexOf(s) > -1;
- return disableds;
+ return disableds as DisabledItems;
};
render() {
@@ -198,9 +162,9 @@ class TimePickerPanel extends Component {
className
);
- const activeHour = [];
- const activeMinute = [];
- const activeSecond = [];
+ const activeHour: number[] = [];
+ const activeMinute: number[] = [];
+ const activeSecond: number[] = [];
const valueArr = Array.isArray(value) ? value : [value];
valueArr.forEach((val, i) => {
@@ -217,19 +181,23 @@ class TimePickerPanel extends Component {
renderTimeMenuItems,
};
- const { newDisabledHours, newDisabledMinutes, newDisabledSeconds } = this.getDisabledItems();
+ const { newDisabledHours, newDisabledMinutes, newDisabledSeconds } =
+ this.getDisabledItems();
- const generatePanel = index => (
+ const generatePanel = (index: number) => (
{showHour ? (
) : null}
@@ -238,10 +206,13 @@ class TimePickerPanel extends Component {
{...commonProps}
value={valueArr[index]}
activeIndex={activeMinute[index]}
- title={locale.minute}
+ title={locale!.minute}
mode="minute"
step={minuteStep}
- onSelect={this.onSelectMenuItem.bind(this, `${index === 0 ? 'start' : 'end'}`)}
+ onSelect={this.onSelectMenuItem.bind(
+ this,
+ `${index === 0 ? 'start' : 'end'}`
+ )}
disabledItems={newDisabledMinutes[index]}
/>
) : null}
@@ -250,10 +221,13 @@ class TimePickerPanel extends Component {
{...commonProps}
value={valueArr[index]}
activeIndex={activeSecond[index]}
- title={locale.second}
+ title={locale!.second}
step={secondStep}
mode="second"
- onSelect={this.onSelectMenuItem.bind(this, `${index === 0 ? 'start' : 'end'}`)}
+ onSelect={this.onSelectMenuItem.bind(
+ this,
+ `${index === 0 ? 'start' : 'end'}`
+ )}
disabledItems={newDisabledSeconds[index]}
/>
) : null}
@@ -262,7 +236,10 @@ class TimePickerPanel extends Component {
const singlePanel = generatePanel(0);
- const panelClassNames = classnames(`${this.prefixCls}-panel-col-${colLen}`, `${this.prefixCls}-panel-list`);
+ const panelClassNames = classnames(
+ `${this.prefixCls}-panel-col-${colLen}`,
+ `${this.prefixCls}-panel-list`
+ );
const doublePanel = (
diff --git a/components/time-picker2/prop-types.js b/components/time-picker2/prop-types.ts
similarity index 65%
rename from components/time-picker2/prop-types.js
rename to components/time-picker2/prop-types.ts
index 9f16e729da..a63f9d9dd2 100644
--- a/components/time-picker2/prop-types.js
+++ b/components/time-picker2/prop-types.ts
@@ -1,12 +1,14 @@
import PT from 'prop-types';
+import type { ConfigType } from 'dayjs';
+
import { TIME_PICKER_TYPE, TIME_INPUT_TYPE } from './constant';
import { datejs } from '../util';
-export const error = (propName, ComponentName) =>
+export const error = (propName: string, ComponentName: string) =>
new Error(`Invalid prop ${propName} supplied to ${ComponentName}. Validation failed.`);
-function checkType(type) {
- return (props, propName, componentName) => {
+function checkType(type: string | string[]) {
+ return (props: Record, propName: string, componentName: string) => {
let value = props[propName];
if (value) {
if (!Array.isArray(value)) {
@@ -17,7 +19,7 @@ function checkType(type) {
type = [type];
}
- if (!value.every(v => type.includes(typeof v))) {
+ if (!(value as unknown[]).every(v => type.includes(typeof v))) {
throw error(propName, componentName);
}
}
@@ -25,12 +27,12 @@ function checkType(type) {
}
const SharedPT = {
- date(props, propName, componentName) {
- if (propName in props && !datejs(props.propName).isValid()) {
+ date(props: Record, propName: string, componentName: string) {
+ if (propName in props && !datejs(props.propName as ConfigType).isValid()) {
throw error(propName, componentName);
}
},
- value(props, propName, componentName) {
+ value(props: Record, propName: string, componentName: string) {
if (props[propName]) {
let value = props[propName];
@@ -40,7 +42,11 @@ const SharedPT = {
value = [value];
}
- if (!value.every(v => !v || datejs(v).isValid() || typeof v === 'string')) {
+ if (
+ !(value as unknown[]).every(
+ v => !v || datejs(v as ConfigType).isValid() || typeof v === 'string'
+ )
+ ) {
throw error(propName, componentName);
}
}
diff --git a/components/time-picker2/style.js b/components/time-picker2/style.ts
similarity index 100%
rename from components/time-picker2/style.js
rename to components/time-picker2/style.ts
diff --git a/components/time-picker2/time-picker.jsx b/components/time-picker2/time-picker.tsx
similarity index 68%
rename from components/time-picker2/time-picker.jsx
rename to components/time-picker2/time-picker.tsx
index 882631ae13..04ebed5587 100644
--- a/components/time-picker2/time-picker.jsx
+++ b/components/time-picker2/time-picker.tsx
@@ -1,7 +1,9 @@
-import React, { Component } from 'react';
+import React, { Component, type KeyboardEvent } from 'react';
import PropTypes from 'prop-types';
import { polyfill } from 'react-lifecycles-compat';
import classnames from 'classnames';
+import type { Dayjs } from 'dayjs';
+
import ConfigProvider from '../config-provider';
import Input from '../input';
import Button from '../button';
@@ -15,6 +17,13 @@ import { switchInputType, fmtValue, isValueChanged } from '../date-picker2/util'
import FooterPanel from '../date-picker2/panels/footer-panel';
import DateInput from './module/date-input';
import { TIME_PICKER_TYPE, TIME_INPUT_TYPE } from './constant';
+import type {
+ DateInputProps,
+ TimePickerProps,
+ TimePickerState,
+ ValueType,
+ InputType,
+} from './types';
const { Popup } = Overlay;
const { noop, checkDate, checkRangeDate } = func;
@@ -26,160 +35,48 @@ const presetPropType = PropTypes.shape({
...Button.propTypes,
});
-class TimePicker2 extends Component {
+type SharedInputProps = DateInputProps & {
+ ref: (el: InstanceType) => void;
+};
+class TimePicker2 extends Component {
static propTypes = {
...ConfigProvider.propTypes,
prefix: PropTypes.string,
rtl: PropTypes.bool,
- /**
- * 按钮的文案
- */
label: PropTypes.node,
- /**
- * 输入框状态
- */
state: PropTypes.oneOf(['error', 'success']),
- /**
- * 输入框提示
- */
placeholder: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.string), PropTypes.string]),
- /**
- * 时间值,dayjs格式或者2020-01-01字符串格式,受控状态使用
- */
value: SharedPT.value,
- /**
- * 时间初值,dayjs格式或者2020-01-01字符串格式,非受控状态使用
- */
defaultValue: SharedPT.value,
- /**
- * 时间选择框的尺寸
- */
size: PropTypes.oneOf(['small', 'medium', 'large']),
- /**
- * 是否允许清空时间
- */
hasClear: PropTypes.bool,
- /**
- * 时间的格式
- * https://dayjs.gitee.io/docs/zh-CN/display/format
- */
format: PropTypes.string,
- /**
- * 小时选项步长
- */
hourStep: PropTypes.number,
- /**
- * 分钟选项步长
- */
minuteStep: PropTypes.number,
- /**
- * 秒钟选项步长
- */
secondStep: PropTypes.number,
- /**
- * 禁用小时函数
- * @param {Number} index 时 0 - 23
- * @return {Boolean} 是否禁用
- */
disabledHours: PropTypes.func,
- /**
- * 禁用分钟函数
- * @param {Number} index 分 0 - 59
- * @return {Boolean} 是否禁用
- */
disabledMinutes: PropTypes.func,
- /**
- * 禁用秒钟函数
- * @param {Number} index 秒 0 - 59
- * @return {Boolean} 是否禁用
- */
disabledSeconds: PropTypes.func,
- /**
- * 渲染的可选择时间列表
- * [{
- * label: '01',
- * value: 1
- * }]
- * @param {Array} list 默认渲染的列表
- * @param {String} mode 渲染的菜单 hour, minute, second
- * @param {dayjs} value 当前时间,可能为 null
- * @return {Array} 返回需要渲染的数据
- */
renderTimeMenuItems: PropTypes.func,
- /**
- * 弹层是否显示(受控)
- */
visible: PropTypes.bool,
- /**
- * 弹层默认是否显示(非受控)
- */
defaultVisible: PropTypes.bool,
- /**
- * 弹层容器
- * @param {Object} target 目标节点
- * @return {ReactNode} 容器节点
- */
popupContainer: PropTypes.any,
- /**
- * 弹层对齐方式, 详情见Overlay 文档
- */
popupAlign: PropTypes.string,
- /**
- * 弹层触发方式
- */
popupTriggerType: PropTypes.oneOf(['click', 'hover']),
- /**
- * 弹层展示状态变化时的回调
- * @param {Boolean} visible 弹层是否隐藏和显示
- * @param {String} type 触发弹层显示和隐藏的来源 fromTrigger 表示由trigger的点击触发; docClick 表示由document的点击触发
- */
onVisibleChange: PropTypes.func,
- /**
- * 弹层自定义样式
- */
popupStyle: PropTypes.object,
- /**
- * 弹层自定义样式类
- */
popupClassName: PropTypes.string,
- /**
- * 弹层属性
- */
popupProps: PropTypes.object,
- /**
- * 是否跟随滚动
- */
followTrigger: PropTypes.bool,
- /**
- * 是否禁用
- */
disabled: PropTypes.bool,
- /**
- * 输入框是否有边框
- */
hasBorder: PropTypes.bool,
- /**
- * 是否为预览态
- */
isPreview: PropTypes.bool,
- /**
- * 预览态模式下渲染的内容
- * @param {DayjsObject|DayjsObject[]} value 时间
- */
renderPreview: PropTypes.func,
- /**
- * 时间值改变时的回调
- * @param {DayjsObject} date dayjs时间对象
- * @param {Object|String} dateString 时间对象或时间字符串
- */
onChange: PropTypes.func,
className: PropTypes.string,
name: PropTypes.string,
- /**
- * 预设值,会显示在时间面板下面
- */
preset: PropTypes.oneOfType([PropTypes.arrayOf(presetPropType), presetPropType]),
- inputProps: PropTypes.shape(Input.propTypes),
+ inputProps: PropTypes.shape(Input.propTypes!),
popupComponent: PropTypes.elementType,
type: PropTypes.oneOf(['time', 'range']),
};
@@ -200,8 +97,12 @@ class TimePicker2 extends Component {
onVisibleChange: noop,
};
- constructor(props, context) {
- super(props, context);
+ prefixCls: string;
+ dateInput: InstanceType;
+ clearTimeoutId: number;
+
+ constructor(props: TimePickerProps) {
+ super(props);
const isRange = props.type === TIME_PICKER_TYPE.RANGE;
this.state = {};
@@ -221,10 +122,10 @@ class TimePicker2 extends Component {
this.state = {
...this.state,
isRange,
- inputStr: '', // 输入框的输入值, string类型
- value, // 确定值 dayjs类型
- curValue: value, // 临时值 dayjs类型
- preValue: value, // 上个值 dayjs类型
+ inputStr: '', // 输入框的输入值,string 类型
+ value, // 确定值 dayjs 类型
+ curValue: value, // 临时值 dayjs 类型
+ preValue: value, // 上个值 dayjs 类型
inputValue: fmtValue(value, format),
inputing: false,
visible: 'visible' in this.props ? visible : defaultVisible,
@@ -232,22 +133,23 @@ class TimePicker2 extends Component {
this.prefixCls = `${prefix}time-picker2`;
}
- static getDerivedStateFromProps(props, prevState) {
+ static getDerivedStateFromProps(props: TimePickerProps, prevState: TimePickerState) {
const { disabled, type, format, value: propsValue } = props;
const isRange = type === TIME_PICKER_TYPE.RANGE;
- let state = {
+ let state: TimePickerState = {
isRange,
};
if ('value' in props) {
// checkDate function doesn't support hh:mm:ss format, convert to valid dayjs value in advance
- const formatter = v => (typeof v === 'string' ? datejs(v, format) : v);
- const formattedValue = Array.isArray(propsValue)
+ const formatter = (v: string | Dayjs | null) =>
+ typeof v === 'string' ? datejs(v, format) : v;
+ const formattedValue: ValueType = Array.isArray(propsValue)
? propsValue.map(v => formatter(v))
- : formatter(propsValue);
+ : formatter(propsValue!);
const value = isRange
- ? checkRangeDate(formattedValue, state.inputType, disabled)
- : checkDate(formattedValue);
+ ? checkRangeDate(formattedValue, state.inputType!, disabled)
+ : checkDate(formattedValue as Dayjs | null);
if (isValueChanged(value, state.preValue)) {
state = {
...state,
@@ -275,38 +177,47 @@ class TimePicker2 extends Component {
const { props } = this;
const { type, value, defaultValue } = props;
- let val = type === TIME_PICKER_TYPE.RANGE ? [null, null] : null;
+ let val: TimePickerProps['value'] = type === TIME_PICKER_TYPE.RANGE ? [null, null] : null;
- val = 'value' in props ? value : 'defaultValue' in props ? defaultValue : val;
+ val = 'value' in props ? value! : 'defaultValue' in props ? defaultValue! : val;
return this.checkValue(val);
};
/**
* 获取 RangePicker 输入框初始输入状态
- * @returns {Object} inputState
- * @returns {boolean} inputState.justBeginInput 是否初始输入
- * @returns {number} inputState.inputType 当前输入框
+ * @returns inputState - Object
+ * @returns inputState.justBeginInput 是否初始输入 - boolean
+ * @returns inputState.inputType 当前输入框 - number
*/
getInitRangeInputState = () => {
return {
justBeginInput: this.isEnabled(),
- inputType: this.isEnabled(0) ? TIME_INPUT_TYPE.BEGIN : TIME_INPUT_TYPE.END,
+ inputType: (this.isEnabled(0)
+ ? TIME_INPUT_TYPE.BEGIN
+ : TIME_INPUT_TYPE.END) as InputType,
};
};
- onKeyDown = e => {
- if (e.keyCode === KEYCODE.ENTER) {
+ onKeyDown = (e: KeyboardEvent) => {
+ if (e!.keyCode === KEYCODE.ENTER) {
const { inputValue } = this.state;
- this.handleChange(inputValue, 'KEYDOWN_ENTER');
+ this.handleChange(inputValue!, 'KEYDOWN_ENTER');
this.onClick();
return;
}
const { value, inputStr, inputType, isRange } = this.state;
- const { format, hourStep = 1, minuteStep = 1, secondStep = 1, disabledMinutes, disabledSeconds } = this.props;
+ const {
+ format,
+ hourStep = 1,
+ minuteStep = 1,
+ secondStep = 1,
+ disabledMinutes,
+ disabledSeconds,
+ } = this.props;
- let unit = 'second';
+ let unit: 'hour' | 'minute' | 'second' = 'second';
if (disabledSeconds) {
unit = disabledMinutes ? 'hour' : 'minute';
@@ -315,35 +226,36 @@ class TimePicker2 extends Component {
const timeStr = onTimeKeydown(
e,
{
- format,
- timeInputStr: isRange ? inputStr[inputType] : inputStr,
+ format: format!,
+ timeInputStr: isRange ? inputStr![inputType!] : (inputStr as string),
steps: {
hour: hourStep,
minute: minuteStep,
second: secondStep,
},
+ // @ts-expect-error 此处的 value 没有考虑到数组的情况
value,
},
unit
);
if (!timeStr) return;
- let newInputStr = timeStr;
+ let newInputStr: string | string[] | undefined = timeStr;
if (isRange) {
- newInputStr = inputStr;
- newInputStr[inputType] = timeStr;
+ newInputStr = inputStr as string[];
+ newInputStr[inputType!] = timeStr;
}
- this.handleChange(newInputStr, 'KEYDOWN_CODE');
+ this.handleChange(newInputStr!, 'KEYDOWN_CODE');
};
- onVisibleChange = (visible, type) => {
+ onVisibleChange = (visible: boolean, type?: string) => {
if (!('visible' in this.props)) {
this.setState({
visible,
});
}
- this.props.onVisibleChange(visible, type);
+ this.props.onVisibleChange!(visible, type);
};
onClick = () => {
@@ -351,37 +263,37 @@ class TimePicker2 extends Component {
if (!visible) {
this.onVisibleChange(true);
- this.handleInputFocus(inputType);
+ this.handleInputFocus(inputType!);
}
};
/**
* 处理点击文档区域导致的弹层收起逻辑
- * @param {boolean} visible 是否可见
- * @param {string} type 事件类型
+ * @param visible - 是否可见
+ * @param type - 事件类型
*/
- handleVisibleChange = (visible, targetType) => {
+ handleVisibleChange = (visible: boolean, targetType: string) => {
if (targetType === 'docClick') {
// 弹层收起 触发 Change 逻辑
if (!visible) {
- this.handleChange(this.state.curValue, 'VISIBLE_CHANGE');
+ this.handleChange(this.state.curValue!, 'VISIBLE_CHANGE');
}
this.onVisibleChange(visible);
}
};
- handleInputFocus = inputType => {
+ handleInputFocus = (inputType: number) => {
let inputEl = this.dateInput && this.dateInput.input;
if (this.state.isRange) {
- inputEl = inputEl && inputEl[inputType];
+ inputEl = inputEl && (inputEl as InstanceType[])[inputType];
}
- inputEl && inputEl.focus();
+ inputEl && (inputEl as InstanceType).focus();
};
onOk = () => {
const { curValue } = this.state;
- const checkedValue = this.checkValue(curValue);
+ const checkedValue = this.checkValue(curValue!);
func.invoke(this.props, 'onOk', this.getOutputArgs(checkedValue));
@@ -389,7 +301,7 @@ class TimePicker2 extends Component {
this.handleChange(checkedValue, 'CLICK_OK');
};
- onInputTypeChange = idx => {
+ onInputTypeChange = (idx: InputType) => {
const { inputType, visible } = this.state;
if (idx !== inputType) {
@@ -400,34 +312,40 @@ class TimePicker2 extends Component {
}
};
- checkValue = (value, strictly) => {
+ checkValue: (value: TimePickerProps['value'], strictly?: boolean) => ValueType = (
+ value,
+ strictly
+ ) => {
const { inputType } = this.state;
const { format, type, disabled } = this.props;
- const formatter = v => (typeof v === 'string' ? datejs(v, format) : v);
- const formattedValue = Array.isArray(value) ? value.map(v => formatter(v)) : formatter(value);
+ const formatter = (v: Dayjs | null | string) =>
+ typeof v === 'string' ? datejs(v, format) : v;
+ const formattedValue: ValueType = Array.isArray(value)
+ ? value.map(v => formatter(v))
+ : formatter(value!);
return type === TIME_PICKER_TYPE.RANGE
- ? checkRangeDate(formattedValue, inputType, disabled, strictly)
- : checkDate(formattedValue);
+ ? checkRangeDate(formattedValue, inputType!, disabled, strictly)
+ : checkDate(formattedValue as Dayjs | string | null);
};
/**
* 获取 `onChange` 和 `onOk` 方法的输出参数
- * @param {Dayjs} value
+ * @param value - Dayjs
* @returns 默认返回 `Dayjs` 实例和 `format` 格式化的值
* 如果传了了 `outputFormat` 属性则返回 `outputFormat` 格式化的值
*/
- getOutputArgs = value => {
+ getOutputArgs = (value: ValueType) => {
const { format } = this.props;
return [value, fmtValue(value, format)];
};
- onChange = v => {
+ onChange = (v: ValueType) => {
const { state, props } = this;
const { format } = props;
const nextValue = v === undefined ? state.value : v;
- const value = this.checkValue(nextValue);
+ const value = this.checkValue(nextValue!);
this.setState({
curValue: value,
@@ -436,15 +354,15 @@ class TimePicker2 extends Component {
inputValue: fmtValue(value, format),
});
- func.invoke(this.props, 'onChange', this.getOutputArgs(nextValue));
+ func.invoke(this.props, 'onChange', this.getOutputArgs(nextValue!));
};
- shouldSwitchInput = value => {
+ shouldSwitchInput = (value: (Dayjs | null)[]) => {
const { inputType, justBeginInput } = this.state;
const idx = justBeginInput ? switchInputType(inputType) : value.indexOf(null);
if (idx !== -1 && this.isEnabled(idx)) {
- this.onInputTypeChange(idx);
+ this.onInputTypeChange(idx as InputType);
this.handleInputFocus(idx);
return true;
}
@@ -452,19 +370,25 @@ class TimePicker2 extends Component {
return false;
};
- handleChange = (v, eventType) => {
+ handleChange = (v: ValueType | string | null | string[], eventType?: string) => {
const { format } = this.props;
const { isRange, value, preValue } = this.state;
- const forceEvents = ['KEYDOWN_ENTER', 'CLICK_PRESET', 'CLICK_OK', 'INPUT_CLEAR', 'VISIBLE_CHANGE'];
- const isTemporary = isRange && !forceEvents.includes(eventType);
+ const forceEvents = [
+ 'KEYDOWN_ENTER',
+ 'CLICK_PRESET',
+ 'CLICK_OK',
+ 'INPUT_CLEAR',
+ 'VISIBLE_CHANGE',
+ ];
+ const isTemporary = isRange && !forceEvents.includes(eventType!);
// 面板收起时候,将值设置为确认值
- v = eventType === 'VISIBLE_CHANGE' ? value : this.checkValue(v, !isTemporary);
+ v = eventType === 'VISIBLE_CHANGE' ? value! : this.checkValue(v, !isTemporary);
const stringV = fmtValue(v, format);
this.setState({
- curValue: v,
+ curValue: v as ValueType | null,
inputStr: stringV,
inputValue: stringV,
inputing: false,
@@ -482,16 +406,20 @@ class TimePicker2 extends Component {
// 2. 非 选择预设时间、面板收起、清空输入 操作
// 3. 不需要切换输入框
const shouldHidePanel =
- ['CLICK_PRESET', 'VISIBLE_CHANGE', 'KEYDOWN_ENTER', 'INPUT_CLEAR', 'CLICK_OK'].includes(
- eventType
- ) ||
- (isRange && !this.shouldSwitchInput(v));
+ [
+ 'CLICK_PRESET',
+ 'VISIBLE_CHANGE',
+ 'KEYDOWN_ENTER',
+ 'INPUT_CLEAR',
+ 'CLICK_OK',
+ ].includes(eventType!) ||
+ (isRange && !this.shouldSwitchInput(v as (Dayjs | null)[]));
if (shouldHidePanel) {
this.onVisibleChange(false);
}
if (isValueChanged(v, preValue)) {
- this.onChange(v);
+ this.onChange(v as ValueType);
}
}
);
@@ -500,10 +428,8 @@ class TimePicker2 extends Component {
/**
* 获取输入框的禁用状态
- * @param {Number} idx
- * @returns {Boolean}
*/
- isEnabled = idx => {
+ isEnabled = (idx?: number) => {
const { disabled } = this.props;
return Array.isArray(disabled)
@@ -518,17 +444,17 @@ class TimePicker2 extends Component {
* 清空输入之后 input 组件内部会让第二个输入框获得焦点
* 所以这里需要设置 setTimeout 才能让第一个 input 获得焦点
*/
- this.clearTimeoutId = setTimeout(() => {
+ this.clearTimeoutId = window.setTimeout(() => {
this.handleInputFocus(0);
});
this.setState({
- inputType: TIME_INPUT_TYPE.BEGIN,
+ inputType: TIME_INPUT_TYPE.BEGIN as InputType,
justBeginInput: this.isEnabled(),
});
};
- handleInput = (v, eventType) => {
+ handleInput = (v: string, eventType?: string) => {
const { isRange } = this.state;
if (eventType === 'clear') {
this.handleChange(v, 'INPUT_CLEAR');
@@ -547,7 +473,7 @@ class TimePicker2 extends Component {
}
};
- renderPreview(others) {
+ renderPreview(others: Omit) {
const { prefix, format, className, renderPreview } = this.props;
const { value } = this.state;
const previewCls = classnames(className, `${prefix}form-preview`);
@@ -560,7 +486,7 @@ class TimePicker2 extends Component {
if (typeof renderPreview === 'function') {
return (
- {renderPreview(value, this.props)}
+ {renderPreview(value!, this.props)}
);
}
@@ -607,7 +533,8 @@ class TimePicker2 extends Component {
...others
} = this.props;
- const { value, inputStr, inputValue, curValue, inputing, visible, isRange, inputType } = this.state;
+ const { value, inputStr, inputValue, curValue, inputing, visible, isRange, inputType } =
+ this.state;
const triggerCls = classnames({
[`${this.prefixCls}-trigger`]: true,
});
@@ -617,10 +544,11 @@ class TimePicker2 extends Component {
}
if (isPreview) {
+ // @ts-expect-error TimePicker2 上不存在 PropTypes 属性,应该是 propTypes
return this.renderPreview(obj.pickOthers(others, TimePicker2.PropTypes));
}
- const sharedInputProps = {
+ const sharedInputProps: SharedInputProps = {
prefix,
locale,
label,
@@ -638,7 +566,7 @@ class TimePicker2 extends Component {
onInputTypeChange: this.onInputTypeChange,
onInput: this.handleInput,
onKeyDown: this.onKeyDown,
- ref: el => (this.dateInput = el),
+ ref: (el: InstanceType) => (this.dateInput = el),
};
const triggerInput = (
@@ -649,7 +577,7 @@ class TimePicker2 extends Component {
state={state}
onClick={this.onClick}
hasBorder={hasBorder}
- placeholder={placeholder || locale.placeholder}
+ placeholder={placeholder || locale!.placeholder}
className={classnames(`${this.prefixCls}-input`)}
/>
@@ -658,13 +586,13 @@ class TimePicker2 extends Component {
const panelProps = {
prefix,
locale,
- value: inputing ? this.checkValue(inputStr) : curValue,
+ value: inputing ? this.checkValue(inputStr!) : curValue,
// value: curValue,
isRange,
disabled,
- showHour: format.indexOf('H') > -1,
- showSecond: format.indexOf('s') > -1,
- showMinute: format.indexOf('m') > -1,
+ showHour: format!.indexOf('H') > -1,
+ showSecond: format!.indexOf('s') > -1,
+ showMinute: format!.indexOf('m') > -1,
hourStep,
minuteStep,
secondStep,
@@ -685,7 +613,7 @@ class TimePicker2 extends Component {
);
const PopupComponent = popupComponent ? popupComponent : Popup;
- const oKable = !!(isRange ? inputValue && inputValue[inputType] : inputValue);
+ const oKable = !!(isRange ? inputValue && inputValue[inputType!] : inputValue);
return (
@@ -697,6 +625,7 @@ class TimePicker2 extends Component {
onVisibleChange={this.handleVisibleChange}
trigger={triggerInput}
container={popupContainer}
+ // @ts-expect-error disabled 为 boolean | boolean[],此处没有考虑到 boolean[] 这种情况
disabled={disabled}
triggerType={popupTriggerType}
style={popupStyle}
@@ -704,6 +633,7 @@ class TimePicker2 extends Component {
>
+ {/* @ts-expect-error disabled 为 boolean | boolean[],TimePickerPanel 没有考虑到 boolean[] 这种情况 */}
{preset || isRange ? (
,
+ Omit {
+ prefix?: string;
+ rtl?: boolean;
+ locale?: Locale['Input'];
+ value?: string | string[];
+ inputType?: InputType;
+ format?: string | ((v: Dayjs) => Dayjs);
+ isRange?: boolean;
+ hasClear?: boolean | null;
+ onInput?: (v: string | number | null | (string | number | null)[], evetType?: string) => void;
+ onInputTypeChange?: (v: number) => void;
+ autoFocus?: boolean;
+ readOnly?: boolean;
+ placeholder?: string;
+ size?: 'small' | 'medium' | 'large';
+ focus?: boolean;
+ hasBorder?: boolean;
+ onKeyDown?: InputProps['onKeyDown'];
+ onClick?: InputProps['onClick'];
+ separator?: ReactNode;
+ disabled?: boolean | boolean[];
+ inputProps?: InputProps;
+ label?: ReactNode;
+ state?: InputProps['state'];
+ defaultValue?: string | number | null | undefined;
+ onBlur?: InputProps['onBlur'];
+ className?: string;
+}
+export type InputType = 0 | 1;
+export interface PresetType extends Omit {
+ label?: string;
+ value?: ConfigType | ConfigType[] | (() => ConfigType | ConfigType[]);
+}
+interface InputCommonHTMLAttributes
+ extends Omit<
+ React.InputHTMLAttributes,
+ 'defaultValue' | 'onChange' | 'onKeyDown' | 'size' | 'maxLength' | 'value'
+ > {
+ [key: `data-${string}`]: string;
+}
+
+interface HTMLAttributesWeak
+ extends Omit, 'onChange' | 'defaultValue'> {}
+
+interface TimeMenuPropsCommonHTMLAttributes
+ extends Omit<
+ InputHTMLAttributes,
+ 'value' | 'onKeyDown' | 'size' | 'onInput' | 'disabled' | 'defaultValue'
+ > {}
+
+export interface TimeMenuProps
+ extends CommonProps,
+ Omit {
+ prefix?: string;
+ title?: ReactNode;
+ mode?: 'hour' | 'minute' | 'second';
+ step?: number;
+ activeIndex: number;
+ value?: Dayjs | null;
+ disabledItems?: (index: number) => boolean;
+ renderTimeMenuItems?: (
+ list: Array,
+ mode: TimeMenuProps['mode'],
+ value: TimeMenuProps['value']
+ ) => Array;
+ onSelect?: (value: TimeMenuListItem['value'], mode: TimeMenuProps['mode']) => void;
+ disabled?: boolean;
+}
+export interface TimeMenuListItem {
+ label: ReactNode;
+ value: number;
+}
+
+export interface PanelProps
+ extends Omit,
+ Pick<
+ TimePickerProps,
+ | 'prefix'
+ | 'locale'
+ | 'hourStep'
+ | 'minuteStep'
+ | 'secondStep'
+ | 'disabledHours'
+ | 'disabledMinutes'
+ | 'disabledSeconds'
+ | 'renderTimeMenuItems'
+ | 'locale'
+ > {
+ /**
+ * 是否显示小时
+ */
+ showHour: boolean;
+
+ /**
+ * 是否显示分钟
+ */
+ showSecond: boolean;
+
+ /**
+ * 是否显示秒
+ */
+ showMinute: boolean;
+ value?: ValueType;
+
+ /**
+ * 选择某个日期值时的回调
+ */
+ onSelect: (value: Dayjs | Dayjs[], type: PannelType) => void;
+ className?: string;
+ isRange: boolean;
+ disabled: boolean;
+}
+
+export type PannelType = 'start' | 'end' | 'panel';
+
+/**
+ * @api TimePicker
+ */
+export interface TimePickerProps extends HTMLAttributesWeak, CommonProps {
+ /**
+ * 按钮的文案
+ * @en Button text
+ */
+ label?: ReactNode;
+
+ /**
+ * @deprecated use Form.Item name instead
+ * @skip
+ */
+ name?: string;
+
+ /**
+ * 时间选择框的尺寸
+ * @en size of time picker
+ * @defaultValue 'medium'
+ */
+ size?: 'small' | 'medium' | 'large';
+
+ /**
+ * 输入框状态
+ * @en input state
+ */
+ state?: 'error' | 'success';
+
+ /**
+ * 是否允许清空时间
+ * @en whether to allow clearing time
+ * @defaultValue true
+ */
+ hasClear?: boolean;
+
+ /**
+ * 时间的格式
+ * @en time format
+ * @remarks see https://Dayjsjs.com/docs/#/parsing/string-format/
+ * @defaultValue 'HH:mm:ss'
+ */
+ format?: string;
+
+ /**
+ * 小时选项步长
+ * @en hour option step
+ */
+ hourStep?: number;
+
+ /**
+ * 分钟选项步长
+ * @en minute option step
+ */
+ minuteStep?: number;
+
+ /**
+ * 秒钟选项步长
+ * @en second option step
+ */
+ secondStep?: number;
+
+ /**
+ * 渲染的可选择时间列表 [\{ label: '01', value: 1 \}]
+ * @en render the selectable time list
+ * @param list - 默认渲染的列表
+ * @param mode - 渲染的菜单 hour, minute, second
+ * @param value - 当前时间,可能为 null
+ * @returns 返回需要渲染的数据
+ */
+ renderTimeMenuItems?: (
+ list: Array,
+ mode: TimeMenuProps['mode'],
+ value: TimeMenuProps['value']
+ ) => Array;
+
+ /**
+ * 弹层是否显示(受控)
+ * @en popup layer display status (controlled)
+ */
+ visible?: boolean;
+
+ /**
+ * 弹层默认是否显示(非受控)
+ * @en popup layer default display status (uncontrolled)
+ */
+ defaultVisible?: boolean;
+
+ /**
+ * 弹层容器
+ * @en popup layer container
+ */
+ popupContainer?: string | HTMLElement | ((target: HTMLElement) => HTMLElement);
+
+ /**
+ * 弹层对齐方式,详情见 Overlay 文档
+ * @en popup layer alignment, see Overlay documentation
+ * @defaultValue 'tl bl'
+ */
+ popupAlign?: string;
+
+ /**
+ * 弹层触发方式
+ * @en popup layer trigger type
+ * @defaultValue 'click'
+ */
+ popupTriggerType?: 'click' | 'hover';
+
+ /**
+ * 弹层展示状态变化时的回调
+ * @en callback when the popup layer display status changes
+ */
+ onVisibleChange?: (visible: boolean, reason?: string) => void;
+
+ /**
+ * 弹层自定义样式
+ * @en popup layer custom style
+ */
+ popupStyle?: CSSProperties;
+
+ /**
+ * 弹层自定义样式类
+ * @en popup layer custom style class
+ */
+ popupClassName?: string;
+
+ /**
+ * 弹层属性
+ * @en popup layer property
+ */
+ popupProps?: PopupProps;
+
+ /**
+ * 跟随触发元素
+ * @en follow trigger element
+ */
+ followTrigger?: boolean;
+
+ /**
+ * 是否有边框
+ * @en Whether the input has border
+ * @defaultValue true
+ */
+ hasBorder?: boolean;
+
+ /**
+ * 是否为预览态
+ * @en is preview
+ */
+ isPreview?: boolean;
+
+ /**
+ * 预览态模式下渲染的内容
+ * @en content of preview mode
+ */
+ renderPreview?: (value: ValueType, props: TimePickerProps) => ReactNode;
+
+ /**
+ * 自定义输入框属性
+ * @en custom input property
+ */
+ inputProps?: InputProps;
+
+ /**
+ * 国际化
+ * @en internationalization
+ * @skip
+ */
+ locale?: Locale['TimePicker'];
+
+ /**
+ * 弹层组件
+ * @en popup component
+ * @skip
+ */
+ popupComponent?: JSXElementConstructor;
+
+ /**
+ * 输入框提示
+ * @en input hint
+ */
+ placeholder?: string;
+
+ /**
+ * 时间值(Dayjs 对象或时间字符串,受控状态使用)
+ * @en time value (Dayjs object or time string, controlled state use)
+ */
+ value?: string | Dayjs | null | (Dayjs | null | string)[];
+
+ /**
+ * 时间初值(Dayjs 对象或时间字符串,非受控状态使用)
+ * @en time init value (Dayjs object or time string, uncontrolled state use)
+ */
+ defaultValue?: string | Dayjs | (Dayjs | null)[];
+
+ /**
+ * 禁用小时的函数,TimePicker.RangePicker 时,函数需要返回 number[],且函数中没有 index 入参,非 TimePicker.RangePicker 时,函数需要返回 boolean,函数中有 index 入参
+ * @en For the disabled hours function, if it's a TimePicker.RangePicker, the function should return a number[] and it shouldn't have an index parameter. If it's not a TimePicker.RangePicker, the function should return a boolean and it should have an index parameter.
+ */
+ disabledHours?: (index?: number) => boolean | number[];
+
+ /**
+ * 禁用分钟函数,TimePicker.RangePicker 时,函数需要返回 number[],且函数中没有 index 入参,非 TimePicker.RangePicker 时,函数需要返回 boolean,函数中有 index 入参
+ * @en For the disabled minutes function, if it's a TimePicker.RangePicker, the function should return a number[] and it shouldn't have an index parameter. If it's not a TimePicker.RangePicker, the function should return a boolean and it should have an index parameter.
+ */
+ disabledMinutes?: (index?: number) => boolean | number[];
+
+ /**
+ * 禁用秒钟函数,TimePicker.RangePicker 时,函数需要返回 number[],且函数中没有 index 入参,非 TimePicker.RangePicker 时,函数需要返回 boolean,函数中有 index 入参
+ * @en For the disabled seconds function, if it's a TimePicker.RangePicker, the function should return a number[] and it shouldn't have an index parameter. If it's not a TimePicker.RangePicker, the function should return a boolean and it should have an index parameter.
+ */
+ disabledSeconds?: (index?: number) => boolean | number[];
+
+ /**
+ * 时间值改变时的回调
+ * @en callback when the time value changes
+ */
+ onChange?: (value: ValueType) => void;
+
+ /**
+ * 时间类型
+ * @en time type
+ * @skip
+ * @defaultValue 'time'
+ */
+ type?: 'time' | 'range';
+
+ /**
+ * 预设值,会显示在时间面板下面
+ * @en Rreset values, shown below the time panel.
+ * Can be object or array of object, with the following properties.
+ * properties:
+ * label: shown text
+ * name: key of React element, can be empty, and index will become key instead
+ * value: date value
+ */
+ preset?: PresetType | PresetType[];
+
+ /**
+ * 禁用
+ * @en disable
+ * @defaultValue false
+ */
+ disabled?: boolean | boolean[];
+}
+
+export interface TimePickerState {
+ value?: ValueType;
+ visible?: boolean | undefined;
+ inputStr?: string | string[];
+ inputing?: boolean;
+ isRange?: boolean;
+ inputValue?: string | string[];
+ curValue?: ValueType;
+ preValue?: ValueType;
+ selecting?: boolean;
+ inputType?: InputType;
+ justBeginInput?: boolean;
+}
+
+export type ValueType = Dayjs | (Dayjs | null)[] | null;
+
+/**
+ * @api TimePicker.RangePicker
+ */
+export interface RangePickerProps extends Omit, CommonProps {
+ /**
+ * 按钮的文案
+ * @en Button text
+ */
+ label?: ReactNode;
+
+ /**
+ * @deprecated use Form.Item name instead
+ * @skip
+ */
+ name?: string;
+
+ /**
+ * 时间选择框的尺寸
+ * @en size of time picker
+ * @defaultValue 'medium'
+ */
+ size?: 'small' | 'medium' | 'large';
+
+ /**
+ * 输入框状态
+ * @en input state
+ */
+ state?: 'error' | 'success';
+
+ /**
+ * 是否允许清空时间
+ * @en whether to allow clearing time
+ * @defaultValue true
+ */
+ hasClear?: boolean;
+
+ /**
+ * 时间的格式
+ * @en time format
+ * @remarks see https://Dayjsjs.com/docs/#/parsing/string-format/
+ * @defaultValue 'HH:mm:ss'
+ */
+ format?: string;
+
+ /**
+ * 小时选项步长
+ * @en hour option step
+ */
+ hourStep?: number;
+
+ /**
+ * 分钟选项步长
+ * @en minute option step
+ */
+ minuteStep?: number;
+
+ /**
+ * 秒钟选项步长
+ * @en second option step
+ */
+ secondStep?: number;
+
+ /**
+ * 渲染的可选择时间列表 [\{ label: '01', value: 1 \}]
+ * @en render the selectable time list
+ * @param list - 默认渲染的列表
+ * @param mode - 渲染的菜单 hour, minute, second
+ * @param value - 当前时间,可能为 null
+ * @returns 返回需要渲染的数据
+ */
+ renderTimeMenuItems?: (
+ list: Array,
+ mode: TimeMenuProps['mode'],
+ value: TimeMenuProps['value']
+ ) => Array;
+
+ /**
+ * 弹层是否显示(受控)
+ * @en popup layer display status (controlled)
+ */
+ visible?: boolean;
+
+ /**
+ * 弹层默认是否显示(非受控)
+ * @en popup layer default display status (uncontrolled)
+ */
+ defaultVisible?: boolean;
+
+ /**
+ * 弹层容器
+ * @en popup layer container
+ */
+ popupContainer?: string | HTMLElement | ((target: HTMLElement) => HTMLElement);
+
+ /**
+ * 弹层对齐方式,详情见 Overlay 文档
+ * @en popup layer alignment, see Overlay documentation
+ * @defaultValue 'tl bl'
+ */
+ popupAlign?: string;
+
+ /**
+ * 弹层触发方式
+ * @en popup layer trigger type
+ * @defaultValue 'click'
+ */
+ popupTriggerType?: 'click' | 'hover';
+
+ /**
+ * 弹层展示状态变化时的回调
+ * @en callback when the popup layer display status changes
+ */
+ onVisibleChange?: (visible: boolean, reason?: string) => void;
+
+ /**
+ * 弹层自定义样式
+ * @en popup layer custom style
+ */
+ popupStyle?: CSSProperties;
+
+ /**
+ * 弹层自定义样式类
+ * @en popup layer custom style class
+ */
+ popupClassName?: string;
+
+ /**
+ * 弹层属性
+ * @en popup layer property
+ */
+ popupProps?: PopupProps;
+
+ /**
+ * 跟随触发元素
+ * @en follow trigger element
+ */
+ followTrigger?: boolean;
+
+ /**
+ * 是否有边框
+ * @en Whether the input has border
+ * @defaultValue true
+ */
+ hasBorder?: boolean;
+
+ /**
+ * 是否为预览态
+ * @en is preview
+ */
+ isPreview?: boolean;
+
+ /**
+ * 预览态模式下渲染的内容
+ * @en content of preview mode
+ */
+ renderPreview?: (value: ValueType, props: TimePickerProps) => ReactNode;
+
+ /**
+ * 自定义输入框属性
+ * @en custom input property
+ */
+ inputProps?: InputProps;
+
+ /**
+ * 国际化
+ * @en internationalization
+ * @skip
+ */
+ locale?: Locale['TimePicker'];
+
+ /**
+ * 弹层组件
+ * @en popup component
+ * @skip
+ */
+ popupComponent?: JSXElementConstructor;
+
+ /**
+ * 禁用小时的函数,TimePicker.RangePicker 时,函数需要返回 number[],且函数中没有 index 入参,非 TimePicker.RangePicker 时,函数需要返回 boolean,函数中有 index 入参
+ * @en For the disabled hours function, if it's a TimePicker.RangePicker, the function should return a number[] and it shouldn't have an index parameter. If it's not a TimePicker.RangePicker, the function should return a boolean and it should have an index parameter.
+ */
+ disabledHours?: (index?: number) => boolean | number[];
+
+ /**
+ * 禁用分钟函数,TimePicker.RangePicker 时,函数需要返回 number[],且函数中没有 index 入参,非 TimePicker.RangePicker 时,函数需要返回 boolean,函数中有 index 入参
+ * @en For the disabled minutes function, if it's a TimePicker.RangePicker, the function should return a number[] and it shouldn't have an index parameter. If it's not a TimePicker.RangePicker, the function should return a boolean and it should have an index parameter.
+ */
+ disabledMinutes?: (index?: number) => boolean | number[];
+
+ /**
+ * 禁用秒钟函数,TimePicker.RangePicker 时,函数需要返回 number[],且函数中没有 index 入参,非 TimePicker.RangePicker 时,函数需要返回 boolean,函数中有 index 入参
+ * @en For the disabled seconds function, if it's a TimePicker.RangePicker, the function should return a number[] and it shouldn't have an index parameter. If it's not a TimePicker.RangePicker, the function should return a boolean and it should have an index parameter.
+ */
+ disabledSeconds?: (index?: number) => boolean | number[];
+
+ /**
+ * 时间类型
+ * @en time type
+ * @skip
+ * @defaultValue 'time'
+ */
+ type?: 'time' | 'range';
+
+ /**
+ * 禁用
+ * @en disable
+ * @defaultValue false
+ */
+ disabled?: boolean | boolean[];
+ /**
+ * 输入框提示
+ * @en input hint
+ */
+ placeholder?: string | string[];
+
+ /**
+ * 时间值(Dayjs 对象或时间字符串,受控状态使用)
+ * @en time value (Dayjs object or time string, controlled state use)
+ */
+ value?: Array;
+
+ /**
+ * 时间初值(Dayjs 对象或时间字符串,非受控状态使用)
+ * @en time init value (Dayjs object or time string, uncontrolled state use)
+ */
+ defaultValue?: Array;
+
+ /**
+ * 时间值改变时的回调
+ * @en callback when the time value changes
+ */
+ onChange?: (value: Array) => void;
+
+ /**
+ * 确定按钮点击时的回调
+ * @en callback when the ok button is clicked
+ */
+ onOk?: (value: Array) => void;
+
+ /**
+ * 预设值,会显示在时间面板下面
+ * @en Rreset values, shown below the time panel.
+ * Can be object or array of object, with the following properties.
+ * properties:
+ * label: shown text
+ * name: key of React element, can be empty, and index will become key instead
+ * value: date value
+ */
+ preset?: PresetType[];
+}
+
+export interface DisabledItems {
+ newDisabledHours: ((index: number) => boolean)[];
+ newDisabledMinutes: ((index: number) => boolean)[];
+ newDisabledSeconds: ((index: number) => boolean)[];
+}
diff --git a/components/time-picker2/utils/index.js b/components/time-picker2/utils/index.ts
similarity index 53%
rename from components/time-picker2/utils/index.js
rename to components/time-picker2/utils/index.ts
index 0493c46eec..c2945804ea 100644
--- a/components/time-picker2/utils/index.js
+++ b/components/time-picker2/utils/index.ts
@@ -1,30 +1,58 @@
+import type { KeyboardEvent } from 'react';
+import type { Dayjs } from 'dayjs';
import { datejs, KEYCODE } from '../../util';
// 检查传入值是否为 dayjs 对象
-export function checkDayjsObj(props, propName, componentName) {
+export function checkDayjsObj(
+ props: Record,
+ propName: string,
+ componentName: string
+) {
if (props[propName] && !datejs.isSelf(props[propName])) {
- return new Error(`Invalid prop ${propName} supplied to ${componentName}. Required a dayjs object.`);
+ return new Error(
+ `Invalid prop ${propName} supplied to ${componentName}. Required a dayjs object.`
+ );
}
}
// 检查传入值是否为 dayjs 对象
-export function checkDateValue(props, propName, componentName) {
+export function checkDateValue(
+ props: Record,
+ propName: string,
+ componentName: string
+) {
if (props[propName] && !datejs.isSelf(props[propName]) && typeof props[propName] !== 'string') {
return new Error(
`Invalid prop ${propName} supplied to ${componentName}. Required a dayjs object or format date string.`
);
}
+ return null;
}
/**
* 监听键盘事件,操作时间
- * @param {KeyboardEvent} e
- * @param {Object} param1
- * @param {String} type second hour minute
+ * @param e - 键盘事件
+ * @param param1 - Object
+ * @param type - second hour minute
*/
-export function onTimeKeydown(e, { format, timeInputStr, steps, value }, type) {
- if ([KEYCODE.UP, KEYCODE.DOWN, KEYCODE.PAGE_UP, KEYCODE.PAGE_DOWN].indexOf(e.keyCode) === -1) return;
- if ((e.altKey && [KEYCODE.PAGE_UP, KEYCODE.PAGE_DOWN].indexOf(e.keyCode) === -1) || e.controlKey || e.shiftKey)
+export function onTimeKeydown(
+ e: KeyboardEvent,
+ {
+ format,
+ timeInputStr,
+ steps,
+ value,
+ }: { format: string; timeInputStr: string; steps: Record; value: Dayjs },
+ type: 'second' | 'hour' | 'minute'
+) {
+ if ([KEYCODE.UP, KEYCODE.DOWN, KEYCODE.PAGE_UP, KEYCODE.PAGE_DOWN].indexOf(e.keyCode) === -1)
+ return;
+ if (
+ (e.altKey && [KEYCODE.PAGE_UP, KEYCODE.PAGE_DOWN].indexOf(e.keyCode) === -1) ||
+ // @ts-expect-error e.controlKey 是旧标准的用法,新标准使用 e.ctrlKey 来代表 Control 键是否被按下
+ e.controlKey ||
+ e.shiftKey
+ )
return;
let time = datejs(timeInputStr, format, true);
@@ -49,10 +77,7 @@ export function onTimeKeydown(e, { format, timeInputStr, steps, value }, type) {
time = value.clone();
} else {
time = datejs();
- time = time
- .hour(0)
- .minute(0)
- .second(0);
+ time = time.hour(0).minute(0).second(0);
}
e.preventDefault();
diff --git a/components/util/func.ts b/components/util/func.ts
index d020a6ee54..467ec8c96e 100644
--- a/components/util/func.ts
+++ b/components/util/func.ts
@@ -168,9 +168,9 @@ export function checkDate(value: ConfigType, format?: OptionType): Dayjs | null
* @param strictly - 是否严格校验:严格模式下不允许开始时间大于结束时间,在显示确认按键的,用户输入过程可不严格校验
*/
export function checkRangeDate(
- value: ConfigType,
+ value: ConfigType | ConfigType[],
inputType: number,
- disabled?: boolean,
+ disabled?: boolean | boolean[],
strictly: boolean = true,
format?: OptionType
): [Dayjs | null, Dayjs | null] {