Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Picker): picker supports setting the sliding direction #12581

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/vant/src/composables/use-touch.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ref } from 'vue';
import { TAP_OFFSET } from '../utils';

type Direction = '' | 'vertical' | 'horizontal';
export type Direction = '' | 'vertical' | 'horizontal';

function getDirection(x: number, y: number) {
if (x > y) {
Expand Down
3 changes: 3 additions & 0 deletions packages/vant/src/picker/Picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import type {
PickerToolbarPosition,
} from './types';
import { PICKER_GROUP_KEY } from '../picker-group/PickerGroup';
import { type Direction } from '../composables/use-touch';

export const pickerSharedProps = extend(
{
Expand All @@ -65,6 +66,7 @@ export const pickerSharedProps = extend(
showToolbar: truthProp,
swipeDuration: makeNumericProp(1000),
visibleOptionNum: makeNumericProp(6),
direction: makeStringProp<Direction>('vertical'),
},
pickerToolbarProps,
);
Expand Down Expand Up @@ -209,6 +211,7 @@ export default defineComponent({
optionHeight={optionHeight.value}
swipeDuration={props.swipeDuration}
visibleOptionNum={props.visibleOptionNum}
direction={props.direction}
onChange={(value: Numeric) => onChange(value, columnIndex)}
onClickOption={(option: PickerOption) =>
onClickOption(option, columnIndex)
Expand Down
28 changes: 24 additions & 4 deletions packages/vant/src/picker/PickerColumn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ import {
preventDefault,
createNamespace,
makeRequiredProp,
makeStringProp,
type Numeric,
} from '../utils';
import { getElementTranslateY, findIndexOfEnabledOption } from './utils';

// Composables
import { useEventListener, useParent } from '@vant/use';
import { useTouch } from '../composables/use-touch';
import { useEventListener, useParent, useRect } from '@vant/use';
import { useTouch, type Direction } from '../composables/use-touch';
import { useExpose } from '../composables/use-expose';

// Types
Expand Down Expand Up @@ -55,6 +56,7 @@ export default defineComponent({
optionHeight: makeRequiredProp(Number),
swipeDuration: makeRequiredProp(numericProp),
visibleOptionNum: makeRequiredProp(numericProp),
direction: makeStringProp<Direction>('vertical'),
},

emits: ['change', 'clickOption', 'scrollInto'],
Expand All @@ -65,6 +67,7 @@ export default defineComponent({
let touchStartTime: number;
let momentumOffset: number;
let transitionEndTrigger: null | (() => void);
let horSign = 0; // 水平滑动的方向符号

const root = ref<HTMLElement>();
const wrapper = ref<HTMLElement>();
Expand Down Expand Up @@ -159,6 +162,17 @@ export default defineComponent({
touchStartTime = Date.now();
momentumOffset = startOffset;
transitionEndTrigger = null;

if (props.direction === 'horizontal') {
// 根据第一个和最后一个item的位置来判断旋转方向
const liNodeList = wrapper.value!.children;
horSign = liNodeList.length
? Math.sign(
useRect(liNodeList[liNodeList.length - 1]).x -
useRect(liNodeList[0]).x,
)
: 0;
}
};

const onTouchMove = (event: TouchEvent) => {
Expand All @@ -168,13 +182,19 @@ export default defineComponent({

touch.move(event);

if (touch.isVertical()) {
if (
(props.direction === 'vertical' && touch.isVertical()) ||
(props.direction === 'horizontal' && touch.isHorizontal())
) {
moving = true;
preventDefault(event, true);
}

const newOffset = clamp(
startOffset + touch.deltaY.value,
startOffset +
(props.direction === 'horizontal'
? touch.deltaX.value * horSign
: touch.deltaY.value),
-(count() * props.optionHeight),
props.optionHeight,
);
Expand Down
1 change: 1 addition & 0 deletions packages/vant/src/picker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@ export default {
| 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 animation, unit `ms` | _number \| string_ | `1000` |
| direction | Sliding direction, adapts to the sliding of options after the element is rotated, cat be set to `horizontal` | _string_ | `vertical` |

### Events

Expand Down
1 change: 1 addition & 0 deletions packages/vant/src/picker/README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ export default {
| option-height | 选项高度,支持 `px` `vw` `vh` `rem` 单位,默认 `px` | _number \| string_ | `44` |
| visible-option-num | 可见的选项个数 | _number \| string_ | `6` |
| swipe-duration | 快速滑动时惯性滚动的时长,单位 `ms` | _number \| string_ | `1000` |
| direction | 滑动方向,适配元素旋转以后选项的滑动,可选值为 `horizontal` | _string_ | `vertical` |

### Events

Expand Down
121 changes: 121 additions & 0 deletions packages/vant/src/picker/demo/WithPopupHorizontal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<script setup lang="ts">
import { reactive } from 'vue';
import VanPicker from '..';
import VanField from '../../field';
import VanPopup from '../../popup';
import { basicColumns } from './data';
import { useTranslate } from '../../../docs/site';
import type { PickerConfirmEventParams } from '../types';

const t = useTranslate({
'zh-CN': {
rotateLeft: '向左旋转',
rotateRight: '向右旋转',
withPopup: '旋转后水平滑动',
chooseCity: '选择城市',
basicColumns: basicColumns['zh-CN'],
},
'en-US': {
rotateLeft: 'Rotate Left',
rotateRight: 'Rotate Righ',
withPopup: 'Slide horizontally after rotation',
chooseCity: 'Choose City',
basicColumns: basicColumns['en-US'],
},
});

type LeftOrRight = 'left' | 'right';
const showPicker = reactive({
left: false,
right: false,
});
const fieldValue = reactive({
left: '',
right: '',
});

const onClickField = (lor: LeftOrRight) => {
showPicker[lor] = true;
};
const onCancel = (lor: LeftOrRight) => {
showPicker[lor] = false;
};
const onConfirm = (
lor: LeftOrRight,
{ selectedOptions }: PickerConfirmEventParams,
) => {
showPicker[lor] = false;
fieldValue[lor] = selectedOptions[0]!.text as string;
};
</script>

<template>
<demo-block card :title="t('withPopup')">
<van-field
v-model="fieldValue.left"
is-link
readonly
:label="t('rotateLeft')"
:placeholder="t('chooseCity')"
@click="onClickField('left')"
/>
<div
v-show="showPicker.left"
class="demo-picker__rotate demo-picker__rotate--left"
>
<van-popup v-model:show="showPicker.left" round position="bottom">
<van-picker
:title="t('title')"
:columns="t('basicColumns')"
direction="horizontal"
@cancel="onCancel('left')"
@confirm="onConfirm('left', $event)"
/>
</van-popup>
</div>

<van-field
v-model="fieldValue.right"
is-link
readonly
:label="t('rotateRight')"
:placeholder="t('chooseCity')"
@click="onClickField('right')"
/>
<div
v-show="showPicker.right"
class="demo-picker__rotate demo-picker__rotate--right"
>
<van-popup v-model:show="showPicker.right" round position="bottom">
<van-picker
:title="t('title')"
:columns="t('basicColumns')"
direction="horizontal"
@cancel="onCancel('right')"
@confirm="onConfirm('right', $event)"
/>
</van-popup>
</div>
</demo-block>
</template>

<style lang="less">
.demo-picker {
&__rotate {
position: fixed;
top: 0;
left: 0;
z-index: 10;
height: 100vw;
width: 100vh;
&--left {
transform-origin: right top;
transform: rotate(-90deg) translate(0, -100vh);
}
&--right {
transform-origin: left bottom;
transform: rotate(90deg) translate(-100vw, 0%);
}
}
}
</style>
3 changes: 3 additions & 0 deletions packages/vant/src/picker/demo/index.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { ref } from 'vue';
import WithPopup from './WithPopup.vue';
import WithPopupHorizontal from './WithPopupHorizontal.vue';
import VanPicker, {
PickerChangeEventParams,
PickerConfirmEventParams,
Expand Down Expand Up @@ -116,4 +117,6 @@ const onCancel = () => showToast(t('cancel'));
:columns-field-names="customFieldName"
/>
</demo-block>

<WithPopupHorizontal />
</template>
91 changes: 91 additions & 0 deletions packages/vant/src/picker/test/__snapshots__/demo-ssr.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -762,4 +762,95 @@ exports[`should render demo and match snapshot 1`] = `
</div>
</div>
</div>
<div>
<!--[-->
<div
class="van-cell van-cell--clickable van-field"
role="button"
tabindex="0"
>
<div
class="van-cell__title van-field__label"
style
>
<!--[-->
<label
id="van-field-label"
for="van-field-input"
style
>
Rotate Left
</label>
</div>
<div class="van-cell__value van-field__value">
<!--[-->
<div class="van-field__body">
<input
type="text"
id="van-field-input"
class="van-field__control"
readonly
placeholder="Choose City"
aria-labelledby="van-field-label"
>
</div>
</div>
<i
class="van-badge__wrapper van-icon van-icon-arrow van-cell__right-icon"
style
>
<!--[-->
</i>
</div>
<div
class="demo-picker__rotate demo-picker__rotate--left"
style="display:none;"
>
<!--[-->
</div>
<div
class="van-cell van-cell--clickable van-field"
role="button"
tabindex="0"
>
<div
class="van-cell__title van-field__label"
style
>
<!--[-->
<label
id="van-field-label"
for="van-field-input"
style
>
Rotate Righ
</label>
</div>
<div class="van-cell__value van-field__value">
<!--[-->
<div class="van-field__body">
<input
type="text"
id="van-field-input"
class="van-field__control"
readonly
placeholder="Choose City"
aria-labelledby="van-field-label"
>
</div>
</div>
<i
class="van-badge__wrapper van-icon van-icon-arrow van-cell__right-icon"
style
>
<!--[-->
</i>
</div>
<div
class="demo-picker__rotate demo-picker__rotate--right"
style="display:none;"
>
<!--[-->
</div>
</div>
`;
Loading