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', 'title',
'loading', 'loading',
'readonly', 'readonly',
'itemHeight', 'optionHeight',
'swipeDuration', 'swipeDuration',
'visibleItemCount', 'visibleOptionNum',
'cancelButtonText', 'cancelButtonText',
'confirmButtonText', 'confirmButtonText',
] as const; ] 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[]_ | `[]` | | columns-placeholder | Placeholder of columns | _string[]_ | `[]` |
| loading | Whether to show loading prompt | _boolean_ | `false` | | loading | Whether to show loading prompt | _boolean_ | `false` |
| readonly | Whether to be readonly | _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` | | 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` | | swipe-duration | Duration of the momentum animationunit `ms` | _number \| string_ | `1000` |
| is-oversea-code | The method to validate oversea code | _() => boolean_ | - | | is-oversea-code | The method to validate oversea code | _() => boolean_ | - |

View File

@ -119,9 +119,9 @@ export default {
| columns-placeholder | 列占位提示文字 | _string[]_ | `[]` | | columns-placeholder | 列占位提示文字 | _string[]_ | `[]` |
| loading | 是否显示加载状态 | _boolean_ | `false` | | loading | 是否显示加载状态 | _boolean_ | `false` |
| readonly | 是否为只读状态,只读状态下无法切换选项 | _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` | | 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` | | swipe-duration | 快速滑动时惯性滚动的时长,单位 `ms` | _number \| string_ | `1000` |
| is-oversea-code | 根据地区码校验海外地址,海外地址会划分至单独的分类 | _() => boolean_ | - | | is-oversea-code | 根据地区码校验海外地址,海外地址会划分至单独的分类 | _() => boolean_ | - |

View File

@ -291,8 +291,8 @@ export default {
| filter | Option filter | _(type: string, values: string[]) => string[]_ | - | | filter | Option filter | _(type: string, values: string[]) => string[]_ | - |
| formatter | Option text formatter | _(type: string, value: 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[]_ | - | | 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` | | option-height | Option height, supports `px` `vw` `vh` `rem` unit, default `px` | _number \| string_ | `44` |
| 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` | | swipe-duration | Duration of the momentum animationunit `ms` | _number \| string_ | `1000` |
### DatePicker Props ### DatePicker Props

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -126,8 +126,8 @@ test('column watch default index', async () => {
props: { props: {
initialOptions: [disabled, ...simpleColumn], initialOptions: [disabled, ...simpleColumn],
textKey: 'text', textKey: 'text',
itemHeight: 50, optionHeight: 50,
visibleItemCount: 5, visibleOptionNum: 5,
swipeDuration: 1000, swipeDuration: 1000,
}, },
} as any); } 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]); 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; const originGetComputedStyle = window.getComputedStyle;
window.getComputedStyle = () => ({ fontSize: '16px' } as CSSStyleDeclaration); window.getComputedStyle = () => ({ fontSize: '16px' } as CSSStyleDeclaration);
@ -321,7 +321,7 @@ test('set rem item-height', async () => {
const wrapper = mount(Picker, { const wrapper = mount(Picker, {
props: { props: {
columns: simpleColumn.slice(0, 2), columns: simpleColumn.slice(0, 2),
itemHeight: '10rem', optionHeight: '10rem',
}, },
}); });