refactor(TimePicker): modelValue now is string array

This commit is contained in:
chenjiahan 2022-02-15 17:44:53 +08:00
parent e61bd487fa
commit fdcf9931be
9 changed files with 79 additions and 98 deletions

View File

@ -14,7 +14,6 @@ import {
isDate,
padZero,
isSameValue,
makeArrayProp,
createNamespace,
} from '../utils';
import { times, sharedProps, getMonthEndDay, pickerInheritKeys } from './utils';
@ -28,7 +27,6 @@ const [name] = createNamespace('date-picker');
export type DatePickerColumnType = 'year' | 'month' | 'day';
const datePickerProps = extend({}, sharedProps, {
modelValue: makeArrayProp<string>(),
columnsType: {
type: Array as PropType<DatePickerColumnType[]>,
default: () => ['year', 'month', 'day'],
@ -136,17 +134,11 @@ export default defineComponent({
})
);
watch(
currentValues,
(newValues) => {
if (isSameValue(newValues, props.modelValue)) {
emit('update:modelValue', newValues);
}
},
{
deep: true,
watch(currentValues, (newValues) => {
if (isSameValue(newValues, props.modelValue)) {
emit('update:modelValue', newValues);
}
);
});
watch(
() => props.modelValue,

View File

@ -1,9 +1,10 @@
import { extend } from '../utils';
import { extend, makeArrayProp } from '../utils';
import { pickerSharedProps } from '../picker/Picker';
import type { PropType } from 'vue';
import type { PickerOption } from '../picker';
export const sharedProps = extend({}, pickerSharedProps, {
modelValue: makeArrayProp<string>(),
filter: Function as PropType<
(columnType: string, options: PickerOption[]) => PickerOption[]
>,

View File

@ -113,9 +113,11 @@ export default defineComponent({
);
const setValue = (index: number, value: string | number) => {
const newValues = selectedValues.value.slice(0);
newValues[index] = value;
selectedValues.value = newValues;
if (selectedValues.value[index] !== value) {
const newValues = selectedValues.value.slice(0);
newValues[index] = value;
selectedValues.value = newValues;
}
};
const onChange = (value: number | string, columnIndex: number) => {
@ -277,7 +279,7 @@ export default defineComponent({
selectedValues,
(newValues) => {
if (!isSameValue(newValues, props.modelValue)) {
emit('update:modelValue', selectedValues.value);
emit('update:modelValue', newValues);
}
},
{ immediate: true }

View File

@ -94,16 +94,6 @@ export function getElementTranslateY(element: Element) {
return Number(translateY);
}
export function isValuesEqual(
valuesA: Array<string | number>,
valuesB: Array<string | number>
) {
return (
valuesA.length === valuesB.length &&
valuesA.every((value, index) => value === valuesB[index])
);
}
export function assignDefaultFields(
fields: PickerFieldNames | undefined
): Required<PickerFieldNames> {

View File

@ -29,7 +29,7 @@ import { ref } from 'vue';
export default {
setup() {
const currentTime = ref('12:00');
const currentTime = ref(['12', '00']);
return { currentTime };
},
};
@ -53,7 +53,7 @@ import { ref } from 'vue';
export default {
setup() {
const currentTime = ref('12:35');
const currentTime = ref(['12', '35']);
return { currentTime };
},
};
@ -76,7 +76,7 @@ import { ref } from 'vue';
export default {
setup() {
const currentTime = ref('12:00');
const currentTime = ref(['12', '00']);
const formatter = (type, option) => {
if (type === 'hour') {
option.text += 'h';
@ -108,7 +108,7 @@ import { ref } from 'vue';
export default {
setup() {
const currentTime = ref('12:00');
const currentTime = ref(['12', '00']);
const filter = (type, options) => {
if (type === 'minute') {
@ -131,7 +131,8 @@ export default {
| Attribute | Description | Type | Default |
| --- | --- | --- | --- |
| v-model | Current time | _string_ | - |
| v-model | Current time | _string[]_ | - |
| columns-type | Columns type | _string[]_ | `['hour', 'minute']` |
| min-hour | Min hour for `time` type | _number \| string_ | `0` |
| max-hour | Max hour for `time` type | _number \| string_ | `23` |
| min-minute | Max minute for `time` type | _number \| string_ | `0` |

View File

@ -29,7 +29,7 @@ import { ref } from 'vue';
export default {
setup() {
const currentTime = ref('12:00');
const currentTime = ref(['12', '00']);
return { currentTime };
},
};
@ -53,7 +53,7 @@ import { ref } from 'vue';
export default {
setup() {
const currentTime = ref('12:35');
const currentTime = ref(['12', '35']);
return { currentTime };
},
};
@ -76,7 +76,7 @@ import { ref } from 'vue';
export default {
setup() {
const currentTime = ref('12:00');
const currentTime = ref(['12', '00']);
const formatter = (type, option) => {
if (type === 'hour') {
option.text += '时';
@ -108,7 +108,7 @@ import { ref } from 'vue';
export default {
setup() {
const currentTime = ref('12:00');
const currentTime = ref(['12', '00']);
const filter = (type, options) => {
if (type === 'minute') {
return options.filter((option) => Number(option) % 10 === 0);
@ -130,7 +130,8 @@ export default {
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| v-model | 当前选中的时间 | _string_ | - |
| v-model | 当前选中的时间 | _string[]_ | - |
| columns-type | 选项类型,由 `hour``minute``second` 组成的数组 | _string[]_ | `['hour', 'minute']` |
| min-hour | 可选的最小小时 | _number \| string_ | `0` |
| max-hour | 可选的最大小时 | _number \| string_ | `23` |
| min-minute | 可选的最小分钟 | _number \| string_ | `0` |

View File

@ -3,17 +3,18 @@ import {
watch,
computed,
defineComponent,
type PropType,
type ExtractPropTypes,
} from 'vue';
// Utils
import {
pick,
clamp,
extend,
padZero,
createNamespace,
makeNumericProp,
isSameValue,
} from '../utils';
import { times, sharedProps, pickerInheritKeys } from '../date-picker/utils';
@ -22,12 +23,17 @@ import { Picker } from '../picker';
const [name] = createNamespace('time-picker');
export type TimePickerColumnType = 'hour' | 'minute';
const timePickerProps = extend({}, sharedProps, {
minHour: makeNumericProp(0),
maxHour: makeNumericProp(23),
minMinute: makeNumericProp(0),
maxMinute: makeNumericProp(59),
modelValue: String,
columnsType: {
type: Array as PropType<TimePickerColumnType[]>,
default: () => ['hour', 'minute'],
},
});
export type TimePickerProps = ExtractPropTypes<typeof timePickerProps>;
@ -40,66 +46,54 @@ export default defineComponent({
emits: ['confirm', 'cancel', 'change', 'update:modelValue'],
setup(props, { emit, slots }) {
const formatValues = (value?: string) => {
const { minHour, maxHour, maxMinute, minMinute } = props;
const currentValues = ref<string[]>(props.modelValue);
if (value) {
const [hour, minute] = value.split(':');
return [
padZero(clamp(+hour, +minHour, +maxHour)),
padZero(clamp(+minute, +minMinute, +maxMinute)),
];
}
return [padZero(minHour), padZero(minMinute)];
const genOptions = (
min: number,
max: number,
type: TimePickerColumnType
) => {
const options = times(max - min + 1, (index) => {
const value = padZero(min + index);
return props.formatter(type, {
text: value,
value,
});
});
return props.filter ? props.filter(type, options) : options;
};
const currentValues = ref<string[]>(formatValues(props.modelValue));
const ranges = computed(() => [
{
type: 'hour' as const,
range: [+props.minHour, +props.maxHour],
},
{
type: 'minute' as const,
range: [+props.minMinute, +props.maxMinute],
},
]);
const columns = computed(() =>
ranges.value.map(({ type, range }) => {
const options = times(range[1] - range[0] + 1, (index) => {
const value = padZero(range[0] + index);
return props.formatter(type, {
text: value,
value,
});
});
if (props.filter) {
return props.filter(type, options);
props.columnsType.map((type) => {
switch (type) {
case 'hour':
return genOptions(+props.minHour, +props.maxHour, 'hour');
case 'minute':
return genOptions(+props.minMinute, +props.maxMinute, 'minute');
default:
throw new Error(
`[Vant] DatePicker: unsupported columns type: ${type}`
);
}
return options;
})
);
watch(
currentValues,
(values) => {
const newValue = values.join(':');
if (newValue !== props.modelValue) {
emit('update:modelValue', newValue);
(newValues) => {
if (!isSameValue(newValues, props.modelValue)) {
emit('update:modelValue', newValues);
}
},
{ deep: true, immediate: true }
{ immediate: true }
);
watch(
() => props.modelValue,
(newValue) => {
currentValues.value = formatValues(newValue);
(newValues) => {
if (!isSameValue(newValues, currentValues.value)) {
currentValues.value = newValues;
}
}
);

View File

@ -23,10 +23,10 @@ const t = useTranslate({
},
});
const baseTime = ref('12:00');
const rangeTime = ref('12:35');
const filterTime = ref('12:00');
const formatterTime = ref('12:00');
const baseTime = ref(['12', '00']);
const rangeTime = ref(['12', '35']);
const filterTime = ref(['12', ' 00']);
const formatterTime = ref(['12', '00']);
const filter = (type: string, options: PickerOption[]) => {
if (type === 'minute') {

View File

@ -17,28 +17,28 @@ test('should format initial value correctly', () => {
},
});
expect(onUpdate.mock.calls[0]).toEqual(['22:58']);
expect(onUpdate.mock.calls[0]).toEqual([['22', '58']]);
});
test('should update modelValue correctly when using max-hour and max-minute prop', () => {
const onUpdate = jest.fn();
mount(TimePicker, {
props: {
modelValue: '23:59',
modelValue: ['23', '59'],
maxHour: 2,
maxMinute: 2,
'onUpdate:modelValue': onUpdate,
},
});
expect(onUpdate.mock.calls[0]).toEqual(['02:02']);
expect(onUpdate.mock.calls[0]).toEqual([['00', '00']]);
});
test('should filter options when using filter prop', () => {
const wrapper = mount(TimePicker, {
props: {
filter,
modelValue: '12:00',
modelValue: ['12', '00'],
},
});
@ -54,7 +54,7 @@ test('should format options correctly when using formatter prop', async () => {
props: {
filter,
formatter,
modelValue: '12:00',
modelValue: ['12', '00'],
},
});
@ -64,7 +64,7 @@ test('should format options correctly when using formatter prop', async () => {
test('should emit confirm event after clicking the confirm button', () => {
const wrapper = mount(TimePicker, {
props: {
modelValue: '12:00',
modelValue: ['12', '00'],
},
});
@ -92,9 +92,9 @@ test('should emit cancel event after clicking the cancel button', () => {
test('should emit confirm event correctly after setting values', async () => {
const wrapper = mount(TimePicker);
await wrapper.setProps({ modelValue: '00:00' });
await wrapper.setProps({ modelValue: ['00', '00'] });
await wrapper.find('.van-picker__confirm').trigger('click');
await wrapper.setProps({ modelValue: '22:30' });
await wrapper.setProps({ modelValue: ['22', '30'] });
await wrapper.find('.van-picker__confirm').trigger('click');
expect(wrapper.emitted('confirm')).toEqual([
@ -125,14 +125,14 @@ test('should emit confirm event correctly after setting range', async () => {
props: {
minHour: 0,
minMinute: 0,
modelValue: '12:00',
modelValue: ['12', '00'],
'onUpdate:modelValue': onUpdate,
},
});
await wrapper.setProps({ minHour: 20, minMinute: 30 });
await wrapper.find('.van-picker__confirm').trigger('click');
expect(onUpdate.mock.calls[0]).toEqual(['20:30']);
expect(onUpdate.mock.calls[0]).toEqual([['20', '30']]);
expect(wrapper.emitted('confirm')).toEqual([
[
{
@ -149,7 +149,7 @@ test('should emit confirm event correctly after setting range', async () => {
test('should emit confirm event correctly after setting smaller max-hour and max-minute', async () => {
const wrapper = mount(TimePicker, {
props: {
modelValue: '23:59',
modelValue: ['23', '59'],
},
});