feat(TimePicker): filter support values param to help get current values (#11916)

* feat(TimePicker): filter support values param to help get current values

* docs: update demo
This commit is contained in:
inottn 2023-06-03 09:06:38 +08:00 committed by GitHub
parent 71c0cc71f2
commit 890ad11822
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 560 additions and 9 deletions

View File

@ -1,9 +1,20 @@
import { extend, padZero, makeArrayProp, clamp } from '../utils';
import {
extend,
padZero,
makeArrayProp,
clamp,
type RequiredParams,
} from '../utils';
import { pickerSharedProps } from '../picker/Picker';
import type { PropType } from 'vue';
import type { PickerOption } from '../picker';
type Filter = (columnType: string, options: PickerOption[]) => PickerOption[];
type Filter = (
columnType: string,
options: PickerOption[],
values?: string[]
) => PickerOption[];
export type TimeFilter = RequiredParams<Filter>;
type Formatter = (type: string, option: PickerOption) => PickerOption;
export const sharedProps = extend({}, pickerSharedProps, {
@ -42,7 +53,8 @@ export const genOptions = <T extends string>(
max: number,
type: T,
formatter: Formatter,
filter?: Filter
filter?: Filter | TimeFilter,
values?: string[]
) => {
const options = times(max - min + 1, (index) => {
const value = padZero(min + index);
@ -51,7 +63,7 @@ export const genOptions = <T extends string>(
value,
});
});
return filter ? filter(type, options) : options;
return filter ? filter(type, options, values!) : options;
};
export const formatValueRange = (values: string[], columns: PickerOption[][]) =>

View File

@ -159,6 +159,48 @@ export default {
};
```
### Advanced Usage
The third parameter of the `filter` function can get the currently selected time, which can be used to filter unwanted times more flexibly when using the uncontrolled mode.
```html
<van-time-picker title="Choose Time" :filter="filter" />
```
```js
export default {
setup() {
const filter = (type, options, values) => {
const hour = +values[0];
if (type === 'hour') {
return options.filter(
(option) => Number(option.value) >= 8 && Number(option.value) <= 18
);
}
if (type === 'minute') {
options = options.filter((option) => Number(option.value) % 10 === 0);
if (hour === 8) {
return options.filter((option) => Number(option.value) >= 40);
}
if (hour === 18) {
return options.filter((option) => Number(option.value) <= 20);
}
}
return options;
};
return {
filter,
};
},
};
```
## API
### Props
@ -179,7 +221,7 @@ export default {
| show-toolbar | Whether to show toolbar | _boolean_ | `true` |
| loading | Whether to show loading prompt | _boolean_ | `false` |
| readonly | Whether to be readonly | _boolean_ | `false` |
| filter | Option filter | _(type: string, options: PickerOption[]) => PickerOption[]_ | - |
| filter | Option filter | _(type: string, options: PickerOption[], values: string[]) => PickerOption[]_ | - |
| formatter | Option text formatter | _(type: string, option: PickerOption) => PickerOption_ | - |
| option-height | Option height, supports `px` `vw` `vh` `rem` unit, default `px` | _number \| string_ | `44` |
| visible-option-num | Count of visible columns | _number \| string_ | `6` |

View File

@ -160,6 +160,48 @@ export default {
};
```
### 高级用法
`filter` 函数的第三个参数能获取到当前选择的时间,这在使用非受控模式时,可以更灵活地过滤掉不需要的时间。
```html
<van-time-picker title="选择时间" :filter="filter" />
```
```js
export default {
setup() {
const filter = (type, options, values) => {
const hour = +values[0];
if (type === 'hour') {
return options.filter(
(option) => Number(option.value) >= 8 && Number(option.value) <= 18
);
}
if (type === 'minute') {
options = options.filter((option) => Number(option.value) % 10 === 0);
if (hour === 8) {
return options.filter((option) => Number(option.value) >= 40);
}
if (hour === 18) {
return options.filter((option) => Number(option.value) <= 20);
}
}
return options;
};
return {
filter,
};
},
};
```
## API
### Props
@ -180,7 +222,7 @@ export default {
| show-toolbar | 是否显示顶部栏 | _boolean_ | `true` |
| loading | 是否显示加载状态 | _boolean_ | `false` |
| readonly | 是否为只读状态,只读状态下无法切换选项 | _boolean_ | `false` |
| filter | 选项过滤函数 | _(type: string, options: PickerOption[]) => PickerOption[]_ | - |
| filter | 选项过滤函数 | _(type: string, options: PickerOption[], values: string[]) => PickerOption[]_ | - |
| formatter | 选项格式化函数 | _(type: string, option: PickerOption) => PickerOption_ | - |
| option-height | 选项高度,支持 `px` `vw` `vh` `rem` 单位,默认 `px` | _number \| string_ | `44` |
| visible-option-num | 可见的选项个数 | _number \| string_ | `6` |

View File

@ -20,6 +20,7 @@ import {
sharedProps,
pickerInheritKeys,
formatValueRange,
type TimeFilter,
} from '../date-picker/utils';
// Components
@ -40,6 +41,7 @@ export const timePickerProps = extend({}, sharedProps, {
type: Array as PropType<TimePickerColumnType[]>,
default: () => ['hour', 'minute'],
},
filter: Function as PropType<TimeFilter>,
});
export type TimePickerProps = ExtractPropTypes<typeof timePickerProps>;
@ -64,7 +66,8 @@ export default defineComponent({
+props.maxHour,
type,
formatter,
filter
filter,
currentValues.value
);
case 'minute':
return genOptions(
@ -72,7 +75,8 @@ export default defineComponent({
+props.maxMinute,
type,
formatter,
filter
filter,
currentValues.value
);
case 'second':
return genOptions(
@ -80,7 +84,8 @@ export default defineComponent({
+props.maxSecond,
type,
formatter,
filter
filter,
currentValues.value
);
default:
if (process.env.NODE_ENV !== 'production') {

View File

@ -41,6 +41,34 @@ const filter = (type: string, options: PickerOption[]) => {
return options;
};
const timeFilter = (
type: string,
options: PickerOption[],
values: string[]
) => {
const hour = +values[0];
if (type === 'hour') {
return options.filter(
(option) => Number(option.value) >= 8 && Number(option.value) <= 18
);
}
if (type === 'minute') {
options = options.filter((option) => Number(option.value) % 10 === 0);
if (hour === 8) {
return options.filter((option) => Number(option.value) >= 40);
}
if (hour === 18) {
return options.filter((option) => Number(option.value) <= 20);
}
}
return options;
};
const formatter = (type: string, option: PickerOption) => {
if (type === 'hour') {
option.text += t('hour');
@ -91,4 +119,8 @@ const formatter = (type: string, option: PickerOption) => {
:filter="filter"
/>
</demo-block>
<demo-block card :title="t('advancedUsage')">
<van-time-picker :title="t('chooseTime')" :filter="timeFilter" />
</demo-block>
</template>

View File

@ -3530,4 +3530,206 @@ exports[`should render demo and match snapshot 1`] = `
</div>
</div>
</div>
<div>
<!--[-->
<div class="van-picker">
<div class="van-picker__toolbar">
<!--[-->
<button type="button"
class="van-picker__cancel van-haptics-feedback"
>
Cancel
</button>
<div class="van-picker__title van-ellipsis">
Choose Time
</div>
<button type="button"
class="van-picker__confirm van-haptics-feedback"
>
Confirm
</button>
</div>
<div class="van-picker__columns"
style="height:264px;"
>
<!--[-->
<div class="van-picker-column">
<ul style="transform:translate3d(0, 110px, 0);transition-duration:0ms;transition-property:none;"
class="van-picker-column__wrapper"
>
<!--[-->
<li role="button"
style="height:44px;"
tabindex="0"
class="van-picker-column__item van-picker-column__item--selected"
>
<div class="van-ellipsis">
08
</div>
</li>
<li role="button"
style="height:44px;"
tabindex="0"
class="van-picker-column__item"
>
<div class="van-ellipsis">
09
</div>
</li>
<li role="button"
style="height:44px;"
tabindex="0"
class="van-picker-column__item"
>
<div class="van-ellipsis">
10
</div>
</li>
<li role="button"
style="height:44px;"
tabindex="0"
class="van-picker-column__item"
>
<div class="van-ellipsis">
11
</div>
</li>
<li role="button"
style="height:44px;"
tabindex="0"
class="van-picker-column__item"
>
<div class="van-ellipsis">
12
</div>
</li>
<li role="button"
style="height:44px;"
tabindex="0"
class="van-picker-column__item"
>
<div class="van-ellipsis">
13
</div>
</li>
<li role="button"
style="height:44px;"
tabindex="0"
class="van-picker-column__item"
>
<div class="van-ellipsis">
14
</div>
</li>
<li role="button"
style="height:44px;"
tabindex="0"
class="van-picker-column__item"
>
<div class="van-ellipsis">
15
</div>
</li>
<li role="button"
style="height:44px;"
tabindex="0"
class="van-picker-column__item"
>
<div class="van-ellipsis">
16
</div>
</li>
<li role="button"
style="height:44px;"
tabindex="0"
class="van-picker-column__item"
>
<div class="van-ellipsis">
17
</div>
</li>
<li role="button"
style="height:44px;"
tabindex="0"
class="van-picker-column__item"
>
<div class="van-ellipsis">
18
</div>
</li>
</ul>
</div>
<div class="van-picker-column">
<ul style="transform:translate3d(0, 110px, 0);transition-duration:0ms;transition-property:none;"
class="van-picker-column__wrapper"
>
<!--[-->
<li role="button"
style="height:44px;"
tabindex="0"
class="van-picker-column__item van-picker-column__item--selected"
>
<div class="van-ellipsis">
00
</div>
</li>
<li role="button"
style="height:44px;"
tabindex="0"
class="van-picker-column__item"
>
<div class="van-ellipsis">
10
</div>
</li>
<li role="button"
style="height:44px;"
tabindex="0"
class="van-picker-column__item"
>
<div class="van-ellipsis">
20
</div>
</li>
<li role="button"
style="height:44px;"
tabindex="0"
class="van-picker-column__item"
>
<div class="van-ellipsis">
30
</div>
</li>
<li role="button"
style="height:44px;"
tabindex="0"
class="van-picker-column__item"
>
<div class="van-ellipsis">
40
</div>
</li>
<li role="button"
style="height:44px;"
tabindex="0"
class="van-picker-column__item"
>
<div class="van-ellipsis">
50
</div>
</li>
</ul>
</div>
<!--[-->
<div class="van-picker__mask"
style="background-size:100% 110px;"
>
</div>
<div class="van-hairline-unset--top-bottom van-picker__frame"
style="height:44px;"
>
</div>
</div>
</div>
</div>
`;

View File

@ -3498,4 +3498,164 @@ exports[`should render demo and match snapshot 1`] = `
</div>
</div>
</div>
<div>
<div class="van-picker">
<div class="van-picker__toolbar">
<button type="button"
class="van-picker__cancel van-haptics-feedback"
>
Cancel
</button>
<div class="van-picker__title van-ellipsis">
Choose Time
</div>
<button type="button"
class="van-picker__confirm van-haptics-feedback"
>
Confirm
</button>
</div>
<div class="van-picker__columns"
style="height: 264px;"
>
<div class="van-picker-column">
<ul style="transform: translate3d(0, 110px, 0); transition-duration: 0ms; transition-property: none;"
class="van-picker-column__wrapper"
>
<li role="button"
style="height: 44px;"
tabindex="0"
class="van-picker-column__item van-picker-column__item--selected"
>
<div class="van-ellipsis">
08
</div>
</li>
<li role="button"
style="height: 44px;"
tabindex="0"
class="van-picker-column__item"
>
<div class="van-ellipsis">
09
</div>
</li>
<li role="button"
style="height: 44px;"
tabindex="0"
class="van-picker-column__item"
>
<div class="van-ellipsis">
10
</div>
</li>
<li role="button"
style="height: 44px;"
tabindex="0"
class="van-picker-column__item"
>
<div class="van-ellipsis">
11
</div>
</li>
<li role="button"
style="height: 44px;"
tabindex="0"
class="van-picker-column__item"
>
<div class="van-ellipsis">
12
</div>
</li>
<li role="button"
style="height: 44px;"
tabindex="0"
class="van-picker-column__item"
>
<div class="van-ellipsis">
13
</div>
</li>
<li role="button"
style="height: 44px;"
tabindex="0"
class="van-picker-column__item"
>
<div class="van-ellipsis">
14
</div>
</li>
<li role="button"
style="height: 44px;"
tabindex="0"
class="van-picker-column__item"
>
<div class="van-ellipsis">
15
</div>
</li>
<li role="button"
style="height: 44px;"
tabindex="0"
class="van-picker-column__item"
>
<div class="van-ellipsis">
16
</div>
</li>
<li role="button"
style="height: 44px;"
tabindex="0"
class="van-picker-column__item"
>
<div class="van-ellipsis">
17
</div>
</li>
<li role="button"
style="height: 44px;"
tabindex="0"
class="van-picker-column__item"
>
<div class="van-ellipsis">
18
</div>
</li>
</ul>
</div>
<div class="van-picker-column">
<ul style="transform: translate3d(0, 110px, 0); transition-duration: 0ms; transition-property: none;"
class="van-picker-column__wrapper"
>
<li role="button"
style="height: 44px;"
tabindex="0"
class="van-picker-column__item van-picker-column__item--selected"
>
<div class="van-ellipsis">
40
</div>
</li>
<li role="button"
style="height: 44px;"
tabindex="0"
class="van-picker-column__item"
>
<div class="van-ellipsis">
50
</div>
</li>
</ul>
</div>
<div class="van-picker__mask"
style="background-size: 100% 110px;"
>
</div>
<div class="van-hairline-unset--top-bottom van-picker__frame"
style="height: 44px;"
>
</div>
</div>
</div>
</div>
`;

View File

@ -1,4 +1,5 @@
import TimePicker from '../TimePicker';
import { Picker } from '../../picker';
import { mount } from '../../../test';
import type { PickerOption } from '../../picker';
@ -7,6 +8,32 @@ function filter(type: string, options: PickerOption[]): PickerOption[] {
return options.filter((option) => Number(option.value) % mod === 0);
}
function timeRangeFilter(
type: string,
options: PickerOption[],
values: string[]
): PickerOption[] {
const hour = +values[0];
if (type === 'hour') {
return options.filter(
(option) => Number(option.value) >= 8 && Number(option.value) <= 18
);
}
if (type === 'minute') {
if (hour === 8) {
return options.filter((option) => Number(option.value) >= 40);
}
if (hour === 18) {
return options.filter((option) => Number(option.value) <= 20);
}
}
return options;
}
test('should format initial value correctly', () => {
const onUpdate = jest.fn();
mount(TimePicker, {
@ -86,6 +113,31 @@ test('should filter options when using filter prop', () => {
expect(wrapper.html()).toMatchSnapshot();
});
test('should filter options when using filter prop to filter a time range', async () => {
const wrapper = mount(TimePicker, {
props: {
filter: timeRangeFilter,
modelValue: ['08', '40'],
},
});
const picker = wrapper.findComponent(Picker);
let columns = picker.props('columns');
expect(columns[0].length).toEqual(11);
expect(columns[1].length).toEqual(20);
expect(columns[0][0].value).toEqual('08');
expect(columns[1][0].value).toEqual('40');
await wrapper.setProps({ modelValue: ['09', '00'] });
columns = picker.props('columns');
expect(columns[1].length).toEqual(60);
await wrapper.setProps({ modelValue: ['18', '00'] });
columns = picker.props('columns');
expect(columns[1].length).toEqual(21);
expect(columns[1][20].value).toEqual('20');
});
test('should format options correctly when using formatter prop', async () => {
const formatter = (type: string, option: PickerOption): PickerOption => {
option.text = `${option.text} ${type}`;

View File

@ -25,6 +25,10 @@ export function get(object: any, path: string): any {
export type Writeable<T> = { -readonly [P in keyof T]: T[P] };
export type RequiredParams<T> = T extends (...args: infer P) => infer R
? (...args: { [K in keyof P]-?: NonNullable<P[K]> }) => R
: never;
export function pick<T, U extends keyof T>(
obj: T,
keys: ReadonlyArray<U>,