refactor(Picker): rename item-height to option-height

This commit is contained in:
chenjiahan 2022-01-19 10:13:22 +08:00 committed by neverland
parent 89b029aa42
commit c061412138
11 changed files with 69 additions and 73 deletions

View File

@ -45,9 +45,9 @@ const INHERIT_PROPS = [
'title',
'loading',
'readonly',
'itemHeight',
'optionHeight',
'swipeDuration',
'visibleItemCount',
'visibleOptionNum',
'cancelButtonText',
'confirmButtonText',
] as const;

View File

@ -117,9 +117,9 @@ To have a selected valuesimply pass the `code` of target area to `value` prop
| columns-placeholder | Placeholder of columns | _string[]_ | `[]` |
| loading | Whether to show loading prompt | _boolean_ | `false` |
| readonly | Whether to be readonly | _boolean_ | `false` |
| item-height | Option height, supports `px` `vw` `vh` `rem` unit, default `px` | _number \| string_ | `44` |
| option-height | Option height, supports `px` `vw` `vh` `rem` unit, default `px` | _number \| string_ | `44` |
| columns-num | Level of picker | _number \| string_ | `3` |
| visible-item-count | Count of visible columns | _number \| string_ | `6` |
| visible-option-num | Count of visible columns | _number \| string_ | `6` |
| swipe-duration | Duration of the momentum animationunit `ms` | _number \| string_ | `1000` |
| is-oversea-code | The method to validate oversea code | _() => boolean_ | - |

View File

@ -119,9 +119,9 @@ export default {
| columns-placeholder | 列占位提示文字 | _string[]_ | `[]` |
| loading | 是否显示加载状态 | _boolean_ | `false` |
| readonly | 是否为只读状态,只读状态下无法切换选项 | _boolean_ | `false` |
| item-height | 选项高度,支持 `px` `vw` `vh` `rem` 单位,默认 `px` | _number \| string_ | `44` |
| option-height | 选项高度,支持 `px` `vw` `vh` `rem` 单位,默认 `px` | _number \| string_ | `44` |
| columns-num | 显示列数3-省市区2-省市1-省 | _number \| string_ | `3` |
| visible-item-count | 可见的选项个数 | _number \| string_ | `6` |
| visible-option-num | 可见的选项个数 | _number \| string_ | `6` |
| swipe-duration | 快速滑动时惯性滚动的时长,单位 `ms` | _number \| string_ | `1000` |
| is-oversea-code | 根据地区码校验海外地址,海外地址会划分至单独的分类 | _() => boolean_ | - |

View File

@ -291,8 +291,8 @@ export default {
| filter | Option filter | _(type: string, values: string[]) => string[]_ | - |
| formatter | Option text formatter | _(type: string, value: string) => string_ | - |
| columns-order | Array for ordering columns, where item can be set to<br> `year`, `month`, `day`, `hour` and `minute` | _string[]_ | - |
| item-height | Option height, supports `px` `vw` `vh` `rem` unit, default `px` | _number \| string_ | `44` |
| visible-item-count | Count of visible columns | _number \| string_ | `6` |
| 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` |
| swipe-duration | Duration of the momentum animationunit `ms` | _number \| string_ | `1000` |
### DatePicker Props

View File

@ -300,8 +300,8 @@ export default {
| filter | 选项过滤函数 | _(type: string, values: string[]) => string[]_ | - |
| formatter | 选项格式化函数 | _(type: string, value: string) => string_ | - |
| columns-order | 自定义列排序数组, 子项可选值为<br> `year``month``day``hour``minute` | _string[]_ | - |
| item-height | 选项高度,支持 `px` `vw` `vh` `rem` 单位,默认 `px` | _number \| string_ | `44` |
| visible-item-count | 可见的选项个数 | _number \| string_ | `6` |
| option-height | 选项高度,支持 `px` `vw` `vh` `rem` 单位,默认 `px` | _number \| string_ | `44` |
| visible-option-num | 可见的选项个数 | _number \| string_ | `6` |
| swipe-duration | 快速滑动时惯性滚动的时长,单位`ms` | _number \| string_ | `1000` |
### DatePicker Props

View File

@ -34,10 +34,10 @@ import Column, { PICKER_KEY } from './PickerColumn';
import type {
PickerColumn,
PickerExpose,
PickerOption,
PickerFieldNames,
PickerToolbarPosition,
} from './types';
import { PickerOption } from '.';
const [name, bem, t] = createNamespace('picker');
@ -46,10 +46,10 @@ export const pickerSharedProps = {
loading: Boolean,
readonly: Boolean,
allowHtml: Boolean,
itemHeight: makeNumericProp(44),
optionHeight: makeNumericProp(44),
showToolbar: truthProp,
swipeDuration: makeNumericProp(1000),
visibleItemCount: makeNumericProp(6),
visibleOptionNum: makeNumericProp(6),
cancelButtonText: String,
confirmButtonText: String,
};
@ -93,7 +93,7 @@ export default defineComponent({
linkChildren();
const itemHeight = computed(() => unitToPx(props.itemHeight));
const optionHeight = computed(() => unitToPx(props.optionHeight));
const dataType = computed(() => {
const firstColumn = props.columns[0];
@ -227,18 +227,18 @@ export default defineComponent({
readonly={props.readonly}
valueKey={valueKey}
allowHtml={props.allowHtml}
itemHeight={itemHeight.value}
optionHeight={optionHeight.value}
swipeDuration={props.swipeDuration}
visibleItemCount={props.visibleItemCount}
visibleOptionNum={props.visibleOptionNum}
onChange={(value: number | string) => onChange(value, columnIndex)}
/>
));
const renderMask = (wrapHeight: number) => {
if (hasOptions.value) {
const frameStyle = { height: `${itemHeight.value}px` };
const frameStyle = { height: `${optionHeight.value}px` };
const maskStyle = {
backgroundSize: `100% ${(wrapHeight - itemHeight.value) / 2}px`,
backgroundSize: `100% ${(wrapHeight - optionHeight.value) / 2}px`,
};
return [
<div class={bem('mask')} style={maskStyle} />,
@ -251,7 +251,7 @@ export default defineComponent({
};
const renderColumns = () => {
const wrapHeight = itemHeight.value * +props.visibleItemCount;
const wrapHeight = optionHeight.value * +props.visibleOptionNum;
const columnsStyle = { height: `${wrapHeight}px` };
return (
<div

View File

@ -1,4 +1,4 @@
import { ref, reactive, defineComponent, type InjectionKey, watch } from 'vue';
import { ref, watch, defineComponent, type InjectionKey } from 'vue';
// Utils
import {
@ -21,10 +21,10 @@ import type { PickerOption, PickerColumnProvide } from './types';
const DEFAULT_DURATION = 200;
// 惯性滑动思路:
// 在手指离开屏幕时,如果和上一次 move 时的间隔小于 `MOMENTUM_LIMIT_TIME` 且 move
// 距离大于 `MOMENTUM_LIMIT_DISTANCE` 时,执行惯性滑动
const MOMENTUM_LIMIT_TIME = 300;
const MOMENTUM_LIMIT_DISTANCE = 15;
// 在手指离开屏幕时,如果和上一次 move 时的间隔小于 `MOMENTUM_TIME` 且 move
// 距离大于 `MOMENTUM_DISTANCE` 时,执行惯性滑动
const MOMENTUM_TIME = 300;
const MOMENTUM_DISTANCE = 15;
const [name, bem] = createNamespace('picker-column');
@ -46,9 +46,9 @@ export default defineComponent({
readonly: Boolean,
valueKey: makeRequiredProp(String),
allowHtml: Boolean,
itemHeight: makeRequiredProp(Number),
optionHeight: makeRequiredProp(Number),
swipeDuration: makeRequiredProp(numericProp),
visibleItemCount: makeRequiredProp(numericProp),
visibleOptionNum: makeRequiredProp(numericProp),
},
emits: ['change'],
@ -61,18 +61,14 @@ export default defineComponent({
let transitionEndTrigger: null | (() => void);
const wrapper = ref<HTMLElement>();
const state = reactive({
offset: 0,
duration: 0,
});
const currentOffset = ref(0);
const currentDuration = ref(0);
const touch = useTouch();
const count = () => props.options.length;
const baseOffset = () =>
(props.itemHeight * (+props.visibleItemCount - 1)) / 2;
(props.optionHeight * (+props.visibleOptionNum - 1)) / 2;
const adjustIndex = (index: number) => {
index = clamp(index, 0, count());
@ -89,7 +85,7 @@ export default defineComponent({
const updateValueByIndex = (index: number) => {
index = adjustIndex(index);
const offset = -index * props.itemHeight;
const offset = -index * props.optionHeight;
const trigger = () => {
const { value } = props.options[index];
if (value !== props.value) {
@ -98,13 +94,13 @@ export default defineComponent({
};
// trigger the change event after transitionend when moving
if (moving && offset !== state.offset) {
if (moving && offset !== currentOffset.value) {
transitionEndTrigger = trigger;
} else {
trigger();
}
state.offset = offset;
currentOffset.value = offset;
};
const onClickItem = (index: number) => {
@ -113,27 +109,28 @@ export default defineComponent({
}
transitionEndTrigger = null;
state.duration = DEFAULT_DURATION;
currentDuration.value = DEFAULT_DURATION;
updateValueByIndex(index);
};
const getIndexByOffset = (offset: number) =>
clamp(Math.round(-offset / props.itemHeight), 0, count() - 1);
clamp(Math.round(-offset / props.optionHeight), 0, count() - 1);
const momentum = (distance: number, duration: number) => {
const speed = Math.abs(distance / duration);
distance = state.offset + (speed / 0.003) * (distance < 0 ? -1 : 1);
distance =
currentOffset.value + (speed / 0.003) * (distance < 0 ? -1 : 1);
const index = getIndexByOffset(distance);
state.duration = +props.swipeDuration;
currentDuration.value = +props.swipeDuration;
updateValueByIndex(index);
};
const stopMomentum = () => {
moving = false;
state.duration = 0;
currentDuration.value = 0;
if (transitionEndTrigger) {
transitionEndTrigger();
@ -150,13 +147,11 @@ export default defineComponent({
if (moving) {
const translateY = getElementTranslateY(wrapper.value!);
state.offset = Math.min(0, translateY - baseOffset());
startOffset = state.offset;
} else {
startOffset = state.offset;
currentOffset.value = Math.min(0, translateY - baseOffset());
}
state.duration = 0;
currentDuration.value = 0;
startOffset = currentOffset.value;
touchStartTime = Date.now();
momentumOffset = startOffset;
transitionEndTrigger = null;
@ -174,16 +169,16 @@ export default defineComponent({
preventDefault(event, true);
}
state.offset = clamp(
currentOffset.value = clamp(
startOffset + touch.deltaY.value,
-(count() * props.itemHeight),
props.itemHeight
-(count() * props.optionHeight),
props.optionHeight
);
const now = Date.now();
if (now - touchStartTime > MOMENTUM_LIMIT_TIME) {
if (now - touchStartTime > MOMENTUM_TIME) {
touchStartTime = now;
momentumOffset = state.offset;
momentumOffset = currentOffset.value;
}
};
@ -192,19 +187,18 @@ export default defineComponent({
return;
}
const distance = state.offset - momentumOffset;
const distance = currentOffset.value - momentumOffset;
const duration = Date.now() - touchStartTime;
const allowMomentum =
duration < MOMENTUM_LIMIT_TIME &&
Math.abs(distance) > MOMENTUM_LIMIT_DISTANCE;
const startMomentum =
duration < MOMENTUM_TIME && Math.abs(distance) > MOMENTUM_DISTANCE;
if (allowMomentum) {
if (startMomentum) {
momentum(distance, duration);
return;
}
const index = getIndexByOffset(state.offset);
state.duration = DEFAULT_DURATION;
const index = getIndexByOffset(currentOffset.value);
currentDuration.value = DEFAULT_DURATION;
updateValueByIndex(index);
// compatible with desktop scenario
@ -216,7 +210,7 @@ export default defineComponent({
const renderOptions = () => {
const optionStyle = {
height: `${props.itemHeight}px`,
height: `${props.optionHeight}px`,
};
return props.options.map((option, index) => {
@ -256,8 +250,8 @@ export default defineComponent({
const index = props.options.findIndex(
(option) => option[props.valueKey] === value
);
const offset = -adjustIndex(index) * props.itemHeight;
state.offset = offset;
const offset = -adjustIndex(index) * props.optionHeight;
currentOffset.value = offset;
}
);
@ -272,9 +266,11 @@ export default defineComponent({
<ul
ref={wrapper}
style={{
transform: `translate3d(0, ${state.offset + baseOffset()}px, 0)`,
transitionDuration: `${state.duration}ms`,
transitionProperty: state.duration ? 'all' : 'none',
transform: `translate3d(0, ${
currentOffset.value + baseOffset()
}px, 0)`,
transitionDuration: `${currentDuration.value}ms`,
transitionProperty: currentDuration.value ? 'all' : 'none',
}}
class={bem('wrapper')}
onTransitionend={stopMomentum}

View File

@ -329,8 +329,8 @@ export default {
| show-toolbar | Whether to show toolbar | _boolean_ | `true` |
| allow-html | Whether to allow HTML in option text | _boolean_ | `false` |
| default-index | Default value index of single column picker | _number \| string_ | `0` |
| item-height | Option height, supports `px` `vw` `vh` `rem` unit, default `px` | _number \| string_ | `44` |
| visible-item-count | Count of visible columns | _number \| string_ | `6` |
| 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` |
| swipe-duration | Duration of the momentum animationunit `ms` | _number \| string_ | `1000` |
### Events

View File

@ -353,8 +353,8 @@ export default {
| show-toolbar | 是否显示顶部栏 | _boolean_ | `true` |
| allow-html | 是否允许选项内容中渲染 HTML | _boolean_ | `false` |
| default-index | 单列选择时,默认选中项的索引 | _number \| string_ | `0` |
| item-height | 选项高度,支持 `px` `vw` `vh` `rem` 单位,默认 `px` | _number \| string_ | `44` |
| visible-item-count | 可见的选项个数 | _number \| string_ | `6` |
| option-height | 选项高度,支持 `px` `vw` `vh` `rem` 单位,默认 `px` | _number \| string_ | `44` |
| visible-option-num | 可见的选项个数 | _number \| string_ | `6` |
| swipe-duration | 快速滑动时惯性滚动的时长,单位 `ms` | _number \| string_ | `1000` |
### Events

View File

@ -319,7 +319,7 @@ exports[`render option slot with simple columns 1`] = `
</div>
`;
exports[`set rem item-height 1`] = `
exports[`set rem option-height 1`] = `
<div class="van-picker">
<div class="van-picker__toolbar">
<button type="button"

View File

@ -126,8 +126,8 @@ test('column watch default index', async () => {
props: {
initialOptions: [disabled, ...simpleColumn],
textKey: 'text',
itemHeight: 50,
visibleItemCount: 5,
optionHeight: 50,
visibleOptionNum: 5,
swipeDuration: 1000,
},
} as any);
@ -313,7 +313,7 @@ test('should not reset index when columns unchanged', async () => {
expect(wrapper.emitted<[string, number]>('confirm')![0]).toEqual(['2', 1]);
});
test('set rem item-height', async () => {
test('set rem option-height', async () => {
const originGetComputedStyle = window.getComputedStyle;
window.getComputedStyle = () => ({ fontSize: '16px' } as CSSStyleDeclaration);
@ -321,7 +321,7 @@ test('set rem item-height', async () => {
const wrapper = mount(Picker, {
props: {
columns: simpleColumn.slice(0, 2),
itemHeight: '10rem',
optionHeight: '10rem',
},
});