mirror of
https://gitee.com/vant-contrib/vant.git
synced 2025-04-06 03:57:59 +08:00
Merge branch 'next' of github.com:youzan/vant into next
This commit is contained in:
commit
3c3f58a7be
@ -4,22 +4,47 @@
|
||||
|
||||
本文档提供了从 Vant 3 到 Vant 4 的升级指南。
|
||||
|
||||
### 为什么会有 Vant 4.0 ?
|
||||
## API 调整
|
||||
|
||||
为了支持 **暗色模式**,我们对 Vant 中的 **样式变量** 进行了一些不兼容更新,因此发布了新的大版本。
|
||||
### Picker 组件重构
|
||||
|
||||
如果你的项目没有使用主题定制,那样式变量的调整对你没有任何影响,只需要花几分钟去适配 API 调整,即可完成升级。
|
||||
在之前的版本中,Picker 组件的 API 设计存在较大问题,比如:
|
||||
|
||||
如果你的项目使用了主题定制,请完整阅读此文档,并进行迁移。
|
||||
- columns 数据格式定义不合理,容易产生误解
|
||||
- 数据流不清晰,暴露了过多的实例方法来对数据进行操作
|
||||
|
||||
### API 调整
|
||||
为了解决上述问题,我们在 v4 版本中对 Picker 组件进行了重构。
|
||||
|
||||
4.0 版本对少量 API 进行了不兼容调整:
|
||||
#### 主要变更
|
||||
|
||||
#### Picker
|
||||
- 支持通过 `v-model` 绑定当前选中的值,移除 `default-index` 属性
|
||||
- 重新定义了 `columns` 属性的结构
|
||||
- 移除了操作内部数据的实例方法,仅保留 `confirm` 方法
|
||||
- 调整了 `confirm`、`cancel`、`change` 事件的参数
|
||||
- 重命名 `item-height` 属性为 `option-height`
|
||||
- 重命名 `visible-item-count` 属性为 `visible-option-num`
|
||||
|
||||
- `default` 插槽重命名为 `toolbar`
|
||||
- 移除了 `value-key` 属性,使用 `columnsFieldNames` 属性代替
|
||||
详细用法请参见 [Picker 组件文档](#/zh-CN/picker)。
|
||||
|
||||
### Area 组件重构
|
||||
|
||||
Area 组件是基于 Picker 组件进行封装的,因此本次升级也对 Area 组件进行了内部逻辑的重构,并优化了部分 API 设计。
|
||||
|
||||
#### 主要变更
|
||||
|
||||
- 支持通过 `v-model` 绑定当前选中的值
|
||||
- 移除 `reset` 方法,现在可以通过修改 `v-model` 来进行重置
|
||||
- 移除 `is-oversea-code` 属性
|
||||
- 调整所有事件的参数,与 Picker 组件保持一致
|
||||
- 重命名 `value` 属性我 `modelValue`
|
||||
- 重命名 `item-height` 属性为 `option-height`
|
||||
- 重命名 `visible-item-count` 属性为 `visible-option-num`
|
||||
|
||||
详细用法请参见 [Area 组件文档](#/zh-CN/area)。
|
||||
|
||||
### 其他 API 调整
|
||||
|
||||
4.0 版本中,以下 API 进行了不兼容更新:
|
||||
|
||||
#### Tabs
|
||||
|
||||
|
@ -2,16 +2,12 @@ import {
|
||||
ref,
|
||||
watch,
|
||||
computed,
|
||||
reactive,
|
||||
nextTick,
|
||||
onMounted,
|
||||
defineComponent,
|
||||
type PropType,
|
||||
type ExtractPropTypes,
|
||||
} from 'vue';
|
||||
|
||||
// Utils
|
||||
import { deepClone } from '../utils/deep-clone';
|
||||
import {
|
||||
pick,
|
||||
extend,
|
||||
@ -20,52 +16,24 @@ import {
|
||||
createNamespace,
|
||||
} from '../utils';
|
||||
import { pickerSharedProps } from '../picker/Picker';
|
||||
|
||||
// Composables
|
||||
import { useExpose } from '../composables/use-expose';
|
||||
import { INHERIT_PROPS, INHERIT_SLOTS, formatDataForCascade } from './utils';
|
||||
|
||||
// Components
|
||||
import { Picker, PickerInstance } from '../picker';
|
||||
import { Picker } from '../picker';
|
||||
|
||||
// Types
|
||||
import type { AreaList, AreaColumnType, AreaColumnOption } from './types';
|
||||
import type { AreaList } from './types';
|
||||
|
||||
const [name, bem] = createNamespace('area');
|
||||
|
||||
const EMPTY_CODE = '000000';
|
||||
const INHERIT_SLOTS = [
|
||||
'title',
|
||||
'cancel',
|
||||
'confirm',
|
||||
'toolbar',
|
||||
'columns-top',
|
||||
'columns-bottom',
|
||||
] as const;
|
||||
const INHERIT_PROPS = [
|
||||
'title',
|
||||
'loading',
|
||||
'readonly',
|
||||
'itemHeight',
|
||||
'swipeDuration',
|
||||
'visibleItemCount',
|
||||
'cancelButtonText',
|
||||
'confirmButtonText',
|
||||
] as const;
|
||||
|
||||
const isOverseaCode = (code: string) => code[0] === '9';
|
||||
|
||||
const areaProps = extend({}, pickerSharedProps, {
|
||||
value: String,
|
||||
modelValue: String,
|
||||
columnsNum: makeNumericProp(3),
|
||||
columnsPlaceholder: makeArrayProp<string>(),
|
||||
areaList: {
|
||||
type: Object as PropType<AreaList>,
|
||||
default: () => ({}),
|
||||
},
|
||||
isOverseaCode: {
|
||||
type: Function as PropType<(code: string) => boolean>,
|
||||
default: isOverseaCode,
|
||||
},
|
||||
});
|
||||
|
||||
export type AreaProps = ExtractPropTypes<typeof areaProps>;
|
||||
@ -75,262 +43,54 @@ export default defineComponent({
|
||||
|
||||
props: areaProps,
|
||||
|
||||
emits: ['change', 'confirm', 'cancel'],
|
||||
emits: ['change', 'confirm', 'cancel', 'update:modelValue'],
|
||||
|
||||
setup(props, { emit, slots }) {
|
||||
const pickerRef = ref<PickerInstance>();
|
||||
|
||||
const state = reactive({
|
||||
code: props.value,
|
||||
columns: [{ values: [] }, { values: [] }, { values: [] }],
|
||||
});
|
||||
|
||||
const areaList = computed(() => {
|
||||
const { areaList } = props;
|
||||
return {
|
||||
province: areaList.province_list || {},
|
||||
city: areaList.city_list || {},
|
||||
county: areaList.county_list || {},
|
||||
};
|
||||
});
|
||||
|
||||
const placeholderMap = computed(() => {
|
||||
const { columnsPlaceholder } = props;
|
||||
return {
|
||||
province: columnsPlaceholder[0] || '',
|
||||
city: columnsPlaceholder[1] || '',
|
||||
county: columnsPlaceholder[2] || '',
|
||||
};
|
||||
});
|
||||
|
||||
const getDefaultCode = () => {
|
||||
if (props.columnsPlaceholder.length) {
|
||||
return EMPTY_CODE;
|
||||
}
|
||||
|
||||
const { county, city } = areaList.value;
|
||||
|
||||
const countyCodes = Object.keys(county);
|
||||
if (countyCodes[0]) {
|
||||
return countyCodes[0];
|
||||
}
|
||||
|
||||
const cityCodes = Object.keys(city);
|
||||
if (cityCodes[0]) {
|
||||
return cityCodes[0];
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
const getColumnValues = (type: AreaColumnType, code?: string) => {
|
||||
let column: AreaColumnOption[] = [];
|
||||
if (type !== 'province' && !code) {
|
||||
return column;
|
||||
}
|
||||
|
||||
const list = areaList.value[type];
|
||||
column = Object.keys(list).map((listCode) => ({
|
||||
code: listCode,
|
||||
name: list[listCode],
|
||||
}));
|
||||
|
||||
if (code) {
|
||||
// oversea code
|
||||
if (type === 'city' && props.isOverseaCode(code)) {
|
||||
code = '9';
|
||||
}
|
||||
column = column.filter((item) => item.code.indexOf(code!) === 0);
|
||||
}
|
||||
|
||||
if (placeholderMap.value[type] && column.length) {
|
||||
// set columns placeholder
|
||||
let codeFill = '';
|
||||
if (type === 'city') {
|
||||
codeFill = EMPTY_CODE.slice(2, 4);
|
||||
} else if (type === 'county') {
|
||||
codeFill = EMPTY_CODE.slice(4, 6);
|
||||
}
|
||||
|
||||
column.unshift({
|
||||
code: code + codeFill,
|
||||
name: placeholderMap.value[type],
|
||||
});
|
||||
}
|
||||
|
||||
return column;
|
||||
};
|
||||
|
||||
// get index by code
|
||||
const getIndex = (type: AreaColumnType, code: string) => {
|
||||
let compareNum = code.length;
|
||||
if (type === 'province') {
|
||||
compareNum = props.isOverseaCode(code) ? 1 : 2;
|
||||
}
|
||||
if (type === 'city') {
|
||||
compareNum = 4;
|
||||
}
|
||||
|
||||
code = code.slice(0, compareNum);
|
||||
|
||||
const list = getColumnValues(
|
||||
type,
|
||||
compareNum > 2 ? code.slice(0, compareNum - 2) : ''
|
||||
);
|
||||
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
if (list[i].code.slice(0, compareNum) === code) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
const setValues = () => {
|
||||
const picker = pickerRef.value;
|
||||
|
||||
if (!picker) {
|
||||
return;
|
||||
}
|
||||
|
||||
let code = state.code || getDefaultCode();
|
||||
const province = getColumnValues('province');
|
||||
const city = getColumnValues('city', code.slice(0, 2));
|
||||
picker.setColumnValues(0, province);
|
||||
picker.setColumnValues(1, city);
|
||||
|
||||
if (
|
||||
city.length &&
|
||||
code.slice(2, 4) === '00' &&
|
||||
!props.isOverseaCode(code)
|
||||
) {
|
||||
[{ code }] = city;
|
||||
}
|
||||
|
||||
picker.setColumnValues(2, getColumnValues('county', code.slice(0, 4)));
|
||||
picker.setIndexes([
|
||||
getIndex('province', code),
|
||||
getIndex('city', code),
|
||||
getIndex('county', code),
|
||||
]);
|
||||
};
|
||||
|
||||
// parse output columns data
|
||||
const parseValues = (values: AreaColumnOption[]) =>
|
||||
values.map((value, index) => {
|
||||
if (value) {
|
||||
value = deepClone(value);
|
||||
|
||||
if (!value.code || value.name === props.columnsPlaceholder[index]) {
|
||||
value.code = '';
|
||||
value.name = '';
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
});
|
||||
|
||||
const getValues = () => {
|
||||
if (pickerRef.value) {
|
||||
const values = pickerRef.value
|
||||
.getValues<AreaColumnOption>()
|
||||
.filter(Boolean);
|
||||
return parseValues(values);
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
const getArea = () => {
|
||||
const values = getValues();
|
||||
const area = {
|
||||
code: '',
|
||||
country: '',
|
||||
province: '',
|
||||
city: '',
|
||||
county: '',
|
||||
};
|
||||
|
||||
if (!values.length) {
|
||||
return area;
|
||||
}
|
||||
|
||||
const names = values.map((item) => item.name);
|
||||
const validValues = values.filter((value) => value.code);
|
||||
|
||||
area.code = validValues.length
|
||||
? validValues[validValues.length - 1].code
|
||||
: '';
|
||||
|
||||
if (props.isOverseaCode(area.code)) {
|
||||
area.country = names[1] || '';
|
||||
area.province = names[2] || '';
|
||||
} else {
|
||||
area.province = names[0] || '';
|
||||
area.city = names[1] || '';
|
||||
area.county = names[2] || '';
|
||||
}
|
||||
|
||||
return area;
|
||||
};
|
||||
|
||||
const reset = (newCode = '') => {
|
||||
state.code = newCode;
|
||||
setValues();
|
||||
};
|
||||
|
||||
const onChange = (values: AreaColumnOption[], index: number) => {
|
||||
state.code = values[index].code;
|
||||
setValues();
|
||||
|
||||
if (pickerRef.value) {
|
||||
const parsedValues = parseValues(pickerRef.value.getValues());
|
||||
emit('change', parsedValues, index);
|
||||
}
|
||||
};
|
||||
|
||||
const onConfirm = (values: AreaColumnOption[], index: number) => {
|
||||
setValues();
|
||||
emit('confirm', parseValues(values), index);
|
||||
};
|
||||
|
||||
const codes = ref<string[]>([]);
|
||||
const columns = computed(() => formatDataForCascade(props));
|
||||
const onChange = (...args: unknown[]) => emit('change', ...args);
|
||||
const onCancel = (...args: unknown[]) => emit('cancel', ...args);
|
||||
|
||||
onMounted(setValues);
|
||||
const onConfirm = (...args: unknown[]) => emit('confirm', ...args);
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
(value) => {
|
||||
state.code = value;
|
||||
setValues();
|
||||
}
|
||||
codes,
|
||||
(newCodes) => {
|
||||
const lastCode = newCodes.length ? newCodes[newCodes.length - 1] : '';
|
||||
if (lastCode && lastCode !== props.modelValue) {
|
||||
emit('update:modelValue', lastCode);
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
watch(() => props.areaList, setValues, { deep: true });
|
||||
|
||||
watch(
|
||||
() => props.columnsNum,
|
||||
() => nextTick(setValues)
|
||||
() => props.modelValue,
|
||||
(newCode) => {
|
||||
const lastCode = codes.value.length
|
||||
? codes.value[codes.value.length - 1]
|
||||
: '';
|
||||
if (newCode && newCode !== lastCode) {
|
||||
codes.value = [
|
||||
`${newCode.slice(0, 2)}0000`,
|
||||
`${newCode.slice(0, 4)}00`,
|
||||
newCode,
|
||||
].slice(0, +props.columnsNum);
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
useExpose({ reset, getArea, getValues });
|
||||
|
||||
return () => {
|
||||
const columns = state.columns.slice(0, +props.columnsNum);
|
||||
|
||||
return (
|
||||
<Picker
|
||||
v-slots={pick(slots, INHERIT_SLOTS)}
|
||||
ref={pickerRef}
|
||||
class={bem()}
|
||||
columns={columns}
|
||||
columnsFieldNames={{ text: 'name' }}
|
||||
onChange={onChange}
|
||||
onCancel={onCancel}
|
||||
onConfirm={onConfirm}
|
||||
{...pick(props, INHERIT_PROPS)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
return () => (
|
||||
<Picker
|
||||
v-model={codes.value}
|
||||
v-slots={pick(slots, INHERIT_SLOTS)}
|
||||
class={bem()}
|
||||
columns={columns.value}
|
||||
onChange={onChange}
|
||||
onCancel={onCancel}
|
||||
onConfirm={onConfirm}
|
||||
{...pick(props, INHERIT_PROPS)}
|
||||
/>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
@ -75,12 +75,23 @@ export default {
|
||||
};
|
||||
```
|
||||
|
||||
### Initial Value
|
||||
### Model Value
|
||||
|
||||
To have a selected value,simply pass the `code` of target area to `value` property.
|
||||
Bind the currently selected area code via `v-model`.
|
||||
|
||||
```html
|
||||
<van-area title="Title" :area-list="areaList" value="110101" />
|
||||
<van-area v-model="value" title="Title" :area-list="areaList" />
|
||||
```
|
||||
|
||||
```js
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const value = ref('330302');
|
||||
return { value };
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Columns Number
|
||||
@ -109,7 +120,7 @@ To have a selected value,simply pass the `code` of target area to `value` prop
|
||||
|
||||
| Attribute | Description | Type | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| value | the `code` of selected area | _string_ | - |
|
||||
| v-model | the `code` of selected area | _string_ | - |
|
||||
| title | Toolbar title | _string_ | - |
|
||||
| confirm-button-text | Text of confirm button | _string_ | `Confirm` |
|
||||
| cancel-button-text | Text of cancel button | _string_ | `Cancel` |
|
||||
@ -117,40 +128,18 @@ To have a selected value,simply 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 animation,unit `ms` | _number \| string_ | `1000` |
|
||||
| is-oversea-code | The method to validate oversea code | _() => boolean_ | - |
|
||||
|
||||
### Events
|
||||
|
||||
| Event | Description | Arguments |
|
||||
| --- | --- | --- |
|
||||
| confirm | Emitted when the confirm button is clicked | _result: ConfirmResult_ |
|
||||
| cancel | Emitted when the cancel button is clicked | - |
|
||||
| change | Emitted when current option changed | current values,column index |
|
||||
|
||||
### ConfirmResult
|
||||
|
||||
An array that contains selected area objects.
|
||||
|
||||
```js
|
||||
[
|
||||
{
|
||||
code: '330000',
|
||||
name: 'Zhejiang Province',
|
||||
},
|
||||
{
|
||||
code: '330100',
|
||||
name: 'Hangzhou',
|
||||
},
|
||||
{
|
||||
code: '330105',
|
||||
name: 'Xihu District',
|
||||
},
|
||||
];
|
||||
```
|
||||
| confirm | Emitted when the confirm button is clicked | _{ selectedValues, selectedOptions }_ |
|
||||
| cancel | Emitted when the cancel button is clicked | _{ selectedValues, selectedOptions }_ |
|
||||
| change | Emitted when current option is changed | _{ selectedValues, selectedOptions, columnIndex }_ |
|
||||
|
||||
### Slots
|
||||
|
||||
@ -163,14 +152,6 @@ An array that contains selected area objects.
|
||||
| columns-top | Custom content above columns | - |
|
||||
| columns-bottom | Custom content below columns | - |
|
||||
|
||||
### Methods
|
||||
|
||||
Use [ref](https://v3.vuejs.org/guide/component-template-refs.html) to get Area instance and call instance methods.
|
||||
|
||||
| Name | Description | Attribute | Return value |
|
||||
| ----- | ------------------------- | --------------- | ------------ |
|
||||
| reset | Reset all options by code | _code?: string_ | - |
|
||||
|
||||
### Types
|
||||
|
||||
The component exports the following type definitions:
|
||||
|
@ -77,12 +77,23 @@ export default {
|
||||
};
|
||||
```
|
||||
|
||||
### 选中省市区
|
||||
### 控制选中项
|
||||
|
||||
如果想选中某个省市区,需要传入一个 `value` 属性,绑定对应的地区码。
|
||||
通过 `v-model` 绑定当前选中的地区码。
|
||||
|
||||
```html
|
||||
<van-area title="标题" :area-list="areaList" value="110101" />
|
||||
<van-area v-model="value" title="标题" :area-list="areaList" />
|
||||
```
|
||||
|
||||
```js
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const value = ref('330302');
|
||||
return { value };
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### 配置显示列
|
||||
@ -111,7 +122,7 @@ export default {
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| --- | --- | --- | --- |
|
||||
| value | 当前选中项对应的地区码 | _string_ | - |
|
||||
| v-model | 当前选中项对应的地区码 | _string_ | - |
|
||||
| title | 顶部栏标题 | _string_ | - |
|
||||
| confirm-button-text | 确认按钮文字 | _string_ | `确认` |
|
||||
| cancel-button-text | 取消按钮文字 | _string_ | `取消` |
|
||||
@ -119,40 +130,18 @@ 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_ | - |
|
||||
|
||||
### Events
|
||||
|
||||
| 事件 | 说明 | 回调参数 |
|
||||
| ------- | ------------------ | ------------------------------ |
|
||||
| confirm | 点击完成按钮时触发 | _result: ConfirmResult_ |
|
||||
| cancel | 点击取消按钮时触发 | - |
|
||||
| change | 选项改变时触发 | 所有列选中值,当前列对应的索引 |
|
||||
|
||||
### ConfirmResult 格式
|
||||
|
||||
confirm 事件返回的数据整体为一个数组,数组每一项对应一列选项中被选中的数据。
|
||||
|
||||
```js
|
||||
[
|
||||
{
|
||||
code: '110000',
|
||||
name: '北京市',
|
||||
},
|
||||
{
|
||||
code: '110100',
|
||||
name: '北京市',
|
||||
},
|
||||
{
|
||||
code: '110101',
|
||||
name: '东城区',
|
||||
},
|
||||
];
|
||||
```
|
||||
| 事件 | 说明 | 回调参数 |
|
||||
| --- | --- | --- |
|
||||
| confirm | 点击完成按钮时触发 | _{ selectedValues, selectedOptions }_ |
|
||||
| cancel | 点击取消按钮时触发 | _{ selectedValues, selectedOptions }_ |
|
||||
| change | 选项改变时触发 | _{ selectedValues, selectedOptions, columnIndex }_ |
|
||||
|
||||
### Slots
|
||||
|
||||
@ -165,20 +154,12 @@ confirm 事件返回的数据整体为一个数组,数组每一项对应一列
|
||||
| columns-top | 自定义选项上方内容 | - |
|
||||
| columns-bottom | 自定义选项下方内容 | - |
|
||||
|
||||
### 方法
|
||||
|
||||
通过 ref 可以获取到 Area 实例并调用实例方法,详见[组件实例方法](#/zh-CN/advanced-usage#zu-jian-shi-li-fang-fa)。
|
||||
|
||||
| 方法名 | 说明 | 参数 | 返回值 |
|
||||
| --- | --- | --- | --- |
|
||||
| reset | 根据地区码重置所有选项,若不传地区码,则重置到第一项 | _code?: string_ | - |
|
||||
|
||||
### 类型定义
|
||||
|
||||
组件导出以下类型定义:
|
||||
|
||||
```ts
|
||||
import type { AreaProps, AreaList, AreaInstance, AreaColumnOption } from 'vant';
|
||||
import type { AreaProps, AreaList, AreaInstance } from 'vant';
|
||||
```
|
||||
|
||||
`AreaInstance` 是组件实例的类型,用法如下:
|
||||
|
@ -1 +0,0 @@
|
||||
// 已迁移至 https://github.com/youzan/vant/tree/dev/packages/vant-area-data
|
@ -7,14 +7,14 @@ import { useTranslate } from '../../../docs/site/use-translate';
|
||||
|
||||
const t = useTranslate({
|
||||
'zh-CN': {
|
||||
title2: '选中省市区',
|
||||
title2: '控制选中项',
|
||||
title3: '配置显示列',
|
||||
title4: '配置列占位提示文字',
|
||||
columnsPlaceholder: ['请选择', '请选择', '请选择'],
|
||||
areaList,
|
||||
},
|
||||
'en-US': {
|
||||
title2: 'Initial Value',
|
||||
title2: 'Model Value',
|
||||
title3: 'Columns Number',
|
||||
title4: 'Columns Placeholder',
|
||||
columnsPlaceholder: ['Choose', 'Choose', 'Choose'],
|
||||
@ -31,7 +31,7 @@ const value = ref('330302');
|
||||
</demo-block>
|
||||
|
||||
<demo-block card :title="t('title2')">
|
||||
<van-area :title="t('title')" :area-list="t('areaList')" :value="value" />
|
||||
<van-area v-model="value" :title="t('title')" :area-list="t('areaList')" />
|
||||
</demo-block>
|
||||
|
||||
<demo-block card :title="t('title3')">
|
||||
|
@ -4,7 +4,7 @@ import _Area from './Area';
|
||||
export const Area = withInstall(_Area);
|
||||
export default Area;
|
||||
export type { AreaProps } from './Area';
|
||||
export type { AreaList, AreaInstance, AreaColumnOption } from './types';
|
||||
export type { AreaList, AreaInstance } from './types';
|
||||
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
|
@ -8,23 +8,4 @@ export type AreaList = {
|
||||
province_list: Record<string, string>;
|
||||
};
|
||||
|
||||
export type AreaColumnOption = {
|
||||
name: string;
|
||||
code: string;
|
||||
};
|
||||
|
||||
export type AreaColumnType = 'province' | 'county' | 'city';
|
||||
|
||||
export type AreaExpose = {
|
||||
reset: (newCode?: string) => void;
|
||||
getArea: () => {
|
||||
code: string;
|
||||
country: string;
|
||||
province: string;
|
||||
city: string;
|
||||
county: string;
|
||||
};
|
||||
getValues: () => AreaColumnOption[];
|
||||
};
|
||||
|
||||
export type AreaInstance = ComponentPublicInstance<AreaProps, AreaExpose>;
|
||||
export type AreaInstance = ComponentPublicInstance<AreaProps>;
|
||||
|
103
packages/vant/src/area/utils.ts
Normal file
103
packages/vant/src/area/utils.ts
Normal file
@ -0,0 +1,103 @@
|
||||
import type { AreaProps } from '.';
|
||||
import type { PickerOption } from '../picker';
|
||||
|
||||
const EMPTY_CODE = '000000';
|
||||
|
||||
export const INHERIT_SLOTS = [
|
||||
'title',
|
||||
'cancel',
|
||||
'confirm',
|
||||
'toolbar',
|
||||
'columns-top',
|
||||
'columns-bottom',
|
||||
] as const;
|
||||
export const INHERIT_PROPS = [
|
||||
'title',
|
||||
'loading',
|
||||
'readonly',
|
||||
'optionHeight',
|
||||
'swipeDuration',
|
||||
'visibleOptionNum',
|
||||
'cancelButtonText',
|
||||
'confirmButtonText',
|
||||
] as const;
|
||||
|
||||
const makeOption = (
|
||||
text = '',
|
||||
value = EMPTY_CODE,
|
||||
children?: PickerOption[]
|
||||
): PickerOption => ({
|
||||
text,
|
||||
value,
|
||||
children,
|
||||
});
|
||||
|
||||
export function formatDataForCascade({
|
||||
areaList,
|
||||
columnsNum,
|
||||
columnsPlaceholder: placeholder,
|
||||
}: AreaProps) {
|
||||
const {
|
||||
city_list: city,
|
||||
county_list: county,
|
||||
province_list: province,
|
||||
} = areaList;
|
||||
const showCity = columnsNum > 1;
|
||||
const showCounty = columnsNum > 2;
|
||||
|
||||
const getProvinceChildren = () => {
|
||||
if (showCity) {
|
||||
return placeholder.length
|
||||
? [makeOption(placeholder[0], EMPTY_CODE, showCounty ? [] : undefined)]
|
||||
: [];
|
||||
}
|
||||
};
|
||||
|
||||
const provinceMap = new Map<string, PickerOption>();
|
||||
Object.keys(province).forEach((code) => {
|
||||
provinceMap.set(
|
||||
code.slice(0, 2),
|
||||
makeOption(province[code], code, getProvinceChildren())
|
||||
);
|
||||
});
|
||||
|
||||
const cityMap = new Map<string, PickerOption>();
|
||||
if (showCity) {
|
||||
const getCityChildren = () => {
|
||||
if (showCounty) {
|
||||
return placeholder.length ? [makeOption(placeholder[1])] : [];
|
||||
}
|
||||
};
|
||||
|
||||
Object.keys(city).forEach((code) => {
|
||||
const option = makeOption(city[code], code, getCityChildren());
|
||||
cityMap.set(code.slice(0, 4), option);
|
||||
|
||||
const province = provinceMap.get(code.slice(0, 2));
|
||||
if (province) {
|
||||
province.children!.push(option);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (showCounty) {
|
||||
Object.keys(county).forEach((code) => {
|
||||
const city = cityMap.get(code.slice(0, 4));
|
||||
if (city) {
|
||||
city.children!.push(makeOption(county[code], code));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const options = Array.from(provinceMap.values()) as PickerOption[];
|
||||
|
||||
if (placeholder.length) {
|
||||
const county = showCounty ? [makeOption(placeholder[2])] : undefined;
|
||||
const city = showCity
|
||||
? [makeOption(placeholder[1], EMPTY_CODE, county)]
|
||||
: undefined;
|
||||
options.unshift(makeOption(placeholder[0], EMPTY_CODE, city));
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
@ -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 animation,unit `ms` | _number \| string_ | `1000` |
|
||||
|
||||
### DatePicker Props
|
||||
|
@ -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
|
||||
|
@ -20,6 +20,14 @@ import {
|
||||
HAPTICS_FEEDBACK,
|
||||
BORDER_UNSET_TOP_BOTTOM,
|
||||
} from '../utils';
|
||||
import {
|
||||
isValuesEqual,
|
||||
getColumnsType,
|
||||
findOptionByValue,
|
||||
formatCascadeColumns,
|
||||
getFirstEnabledOption,
|
||||
assignDefaultFields,
|
||||
} from './utils';
|
||||
|
||||
// Composables
|
||||
import { useChildren } from '@vant/use';
|
||||
@ -32,10 +40,9 @@ import Column, { PICKER_KEY } from './PickerColumn';
|
||||
// Types
|
||||
import type {
|
||||
PickerColumn,
|
||||
PickerOption,
|
||||
PickerExpose,
|
||||
PickerOption,
|
||||
PickerFieldNames,
|
||||
PickerObjectColumn,
|
||||
PickerToolbarPosition,
|
||||
} from './types';
|
||||
|
||||
@ -46,17 +53,17 @@ 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,
|
||||
};
|
||||
|
||||
const pickerProps = extend({}, pickerSharedProps, {
|
||||
columns: makeArrayProp<PickerOption | PickerColumn>(),
|
||||
defaultIndex: makeNumericProp(0),
|
||||
modelValue: makeArrayProp<number | string>(),
|
||||
toolbarPosition: makeStringProp<PickerToolbarPosition>('top'),
|
||||
columnsFieldNames: Object as PropType<PickerFieldNames>,
|
||||
});
|
||||
@ -68,213 +75,77 @@ export default defineComponent({
|
||||
|
||||
props: pickerProps,
|
||||
|
||||
emits: ['confirm', 'cancel', 'change'],
|
||||
emits: ['confirm', 'cancel', 'change', 'update:modelValue'],
|
||||
|
||||
setup(props, { emit, slots }) {
|
||||
const hasOptions = ref(false);
|
||||
const formattedColumns = ref<PickerObjectColumn[]>([]);
|
||||
|
||||
const {
|
||||
text: textKey,
|
||||
values: valuesKey,
|
||||
children: childrenKey,
|
||||
} = extend(
|
||||
{
|
||||
text: 'text',
|
||||
values: 'values',
|
||||
children: 'children',
|
||||
},
|
||||
props.columnsFieldNames
|
||||
);
|
||||
|
||||
const selectedValues = ref(props.modelValue);
|
||||
const { children, linkChildren } = useChildren(PICKER_KEY);
|
||||
|
||||
linkChildren();
|
||||
|
||||
const itemHeight = computed(() => unitToPx(props.itemHeight));
|
||||
const fields = computed(() => assignDefaultFields(props.columnsFieldNames));
|
||||
const optionHeight = computed(() => unitToPx(props.optionHeight));
|
||||
const columnsType = computed(() =>
|
||||
getColumnsType(props.columns, fields.value)
|
||||
);
|
||||
|
||||
const dataType = computed(() => {
|
||||
const firstColumn = props.columns[0];
|
||||
if (typeof firstColumn === 'object') {
|
||||
if (childrenKey in firstColumn) {
|
||||
return 'cascade';
|
||||
}
|
||||
if (valuesKey in firstColumn) {
|
||||
return 'object';
|
||||
}
|
||||
const currentColumns = computed<PickerColumn[]>(() => {
|
||||
const { columns } = props;
|
||||
switch (columnsType.value) {
|
||||
case 'multiple':
|
||||
return columns as PickerColumn[];
|
||||
case 'cascade':
|
||||
return formatCascadeColumns(columns, fields.value, selectedValues);
|
||||
default:
|
||||
return [columns];
|
||||
}
|
||||
return 'plain';
|
||||
});
|
||||
|
||||
const formatCascade = () => {
|
||||
const formatted: PickerObjectColumn[] = [];
|
||||
const hasOptions = computed(() =>
|
||||
currentColumns.value.some((options) => options.length)
|
||||
);
|
||||
|
||||
let cursor: PickerObjectColumn = {
|
||||
[childrenKey]: props.columns,
|
||||
};
|
||||
const selectedOptions = computed(() =>
|
||||
currentColumns.value.map((options, index) =>
|
||||
findOptionByValue(options, selectedValues.value[index], fields.value)
|
||||
)
|
||||
);
|
||||
|
||||
while (cursor && cursor[childrenKey]) {
|
||||
const children = cursor[childrenKey];
|
||||
let defaultIndex = cursor.defaultIndex ?? +props.defaultIndex;
|
||||
const onChange = (value: number | string, columnIndex: number) => {
|
||||
selectedValues.value[columnIndex] = value;
|
||||
|
||||
while (children[defaultIndex] && children[defaultIndex].disabled) {
|
||||
if (defaultIndex < children.length - 1) {
|
||||
defaultIndex++;
|
||||
} else {
|
||||
defaultIndex = 0;
|
||||
break;
|
||||
if (columnsType.value === 'cascade') {
|
||||
// reset values after cascading
|
||||
selectedValues.value.forEach((value, index) => {
|
||||
const options = currentColumns.value[index];
|
||||
if (!options.find((option) => option[fields.value.value] === value)) {
|
||||
selectedValues.value[index] = options.length
|
||||
? options[0][fields.value.value]
|
||||
: undefined;
|
||||
}
|
||||
}
|
||||
|
||||
formatted.push({
|
||||
[valuesKey]: cursor[childrenKey],
|
||||
className: cursor.className,
|
||||
defaultIndex,
|
||||
});
|
||||
|
||||
cursor = children[defaultIndex];
|
||||
}
|
||||
|
||||
formattedColumns.value = formatted;
|
||||
};
|
||||
|
||||
const format = () => {
|
||||
const { columns } = props;
|
||||
|
||||
if (dataType.value === 'plain') {
|
||||
formattedColumns.value = [{ [valuesKey]: columns }];
|
||||
} else if (dataType.value === 'cascade') {
|
||||
formatCascade();
|
||||
} else {
|
||||
formattedColumns.value = columns as PickerObjectColumn[];
|
||||
}
|
||||
|
||||
hasOptions.value = formattedColumns.value.some(
|
||||
(item) => item[valuesKey] && item[valuesKey].length !== 0
|
||||
);
|
||||
};
|
||||
|
||||
// get indexes of all columns
|
||||
const getIndexes = () => children.map((child) => child.state.index);
|
||||
|
||||
// set options of column by index
|
||||
const setColumnValues = (index: number, options: PickerOption[]) => {
|
||||
const column = children[index];
|
||||
if (column) {
|
||||
column.setOptions(options);
|
||||
hasOptions.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
const onCascadeChange = (columnIndex: number) => {
|
||||
let cursor: PickerObjectColumn = {
|
||||
[childrenKey]: props.columns,
|
||||
};
|
||||
const indexes = getIndexes();
|
||||
|
||||
for (let i = 0; i <= columnIndex; i++) {
|
||||
cursor = cursor[childrenKey][indexes[i]];
|
||||
}
|
||||
|
||||
while (cursor && cursor[childrenKey]) {
|
||||
columnIndex++;
|
||||
setColumnValues(columnIndex, cursor[childrenKey]);
|
||||
cursor = cursor[childrenKey][cursor.defaultIndex || 0];
|
||||
}
|
||||
};
|
||||
|
||||
// get column instance by index
|
||||
const getChild = (index: number) => children[index];
|
||||
|
||||
// get column value by index
|
||||
const getColumnValue = (index: number) => {
|
||||
const column = getChild(index);
|
||||
if (column) {
|
||||
return column.getValue();
|
||||
}
|
||||
};
|
||||
|
||||
// set column value by index
|
||||
const setColumnValue = (index: number, value: string) => {
|
||||
const column = getChild(index);
|
||||
if (column) {
|
||||
column.setValue(value);
|
||||
if (dataType.value === 'cascade') {
|
||||
onCascadeChange(index);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// get column option index by column index
|
||||
const getColumnIndex = (index: number) => {
|
||||
const column = getChild(index);
|
||||
if (column) {
|
||||
return column.state.index;
|
||||
}
|
||||
};
|
||||
|
||||
// set column option index by column index
|
||||
const setColumnIndex = (columnIndex: number, optionIndex: number) => {
|
||||
const column = getChild(columnIndex);
|
||||
if (column) {
|
||||
column.setIndex(optionIndex);
|
||||
if (dataType.value === 'cascade') {
|
||||
onCascadeChange(columnIndex);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// get options of column by index
|
||||
const getColumnValues = (index: number) => {
|
||||
const column = getChild(index);
|
||||
if (column) {
|
||||
return column.state.options;
|
||||
}
|
||||
};
|
||||
|
||||
// get values of all columns
|
||||
const getValues = () => children.map((child) => child.getValue());
|
||||
|
||||
// set values of all columns
|
||||
const setValues = (values: string[]) => {
|
||||
values.forEach((value, index) => {
|
||||
setColumnValue(index, value);
|
||||
emit('change', {
|
||||
columnIndex,
|
||||
selectedValues: selectedValues.value,
|
||||
selectedOptions: selectedOptions.value,
|
||||
});
|
||||
};
|
||||
|
||||
// set indexes of all columns
|
||||
const setIndexes = (indexes: number[]) => {
|
||||
indexes.forEach((optionIndex, columnIndex) => {
|
||||
setColumnIndex(columnIndex, optionIndex);
|
||||
});
|
||||
};
|
||||
|
||||
const emitAction = (event: 'confirm' | 'cancel') => {
|
||||
if (dataType.value === 'plain') {
|
||||
emit(event, getColumnValue(0), getColumnIndex(0));
|
||||
} else {
|
||||
emit(event, getValues(), getIndexes());
|
||||
}
|
||||
};
|
||||
|
||||
const onChange = (columnIndex: number) => {
|
||||
if (dataType.value === 'cascade') {
|
||||
onCascadeChange(columnIndex);
|
||||
}
|
||||
|
||||
if (dataType.value === 'plain') {
|
||||
emit('change', getColumnValue(0), getColumnIndex(0));
|
||||
} else {
|
||||
emit('change', getValues(), columnIndex);
|
||||
}
|
||||
};
|
||||
|
||||
const confirm = () => {
|
||||
children.forEach((child) => child.stopMomentum());
|
||||
emitAction('confirm');
|
||||
emit('confirm', {
|
||||
selectedValues: selectedValues.value,
|
||||
selectedOptions: selectedOptions.value,
|
||||
});
|
||||
};
|
||||
|
||||
const cancel = () => emitAction('cancel');
|
||||
const cancel = () =>
|
||||
emit('cancel', {
|
||||
selectedValues: selectedValues.value,
|
||||
selectedOptions: selectedOptions.value,
|
||||
});
|
||||
|
||||
const renderTitle = () => {
|
||||
if (slots.title) {
|
||||
@ -324,27 +195,26 @@ export default defineComponent({
|
||||
};
|
||||
|
||||
const renderColumnItems = () =>
|
||||
formattedColumns.value.map((item, columnIndex) => (
|
||||
currentColumns.value.map((options, columnIndex) => (
|
||||
<Column
|
||||
v-slots={{ option: slots.option }}
|
||||
textKey={textKey}
|
||||
value={selectedValues.value[columnIndex]}
|
||||
fields={fields.value}
|
||||
options={options}
|
||||
readonly={props.readonly}
|
||||
allowHtml={props.allowHtml}
|
||||
className={item.className}
|
||||
itemHeight={itemHeight.value}
|
||||
defaultIndex={item.defaultIndex ?? +props.defaultIndex}
|
||||
optionHeight={optionHeight.value}
|
||||
swipeDuration={props.swipeDuration}
|
||||
initialOptions={item[valuesKey]}
|
||||
visibleItemCount={props.visibleItemCount}
|
||||
onChange={() => onChange(columnIndex)}
|
||||
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} />,
|
||||
@ -357,7 +227,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
|
||||
@ -371,21 +241,39 @@ export default defineComponent({
|
||||
);
|
||||
};
|
||||
|
||||
watch(() => props.columns, format, { immediate: true });
|
||||
watch(
|
||||
currentColumns,
|
||||
(columns) => {
|
||||
columns.forEach((options, index) => {
|
||||
if (selectedValues.value[index] === undefined && options.length) {
|
||||
selectedValues.value[index] =
|
||||
getFirstEnabledOption(options)[fields.value.value];
|
||||
}
|
||||
});
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
useExpose<PickerExpose>({
|
||||
confirm,
|
||||
getValues,
|
||||
setValues,
|
||||
getIndexes,
|
||||
setIndexes,
|
||||
getColumnIndex,
|
||||
setColumnIndex,
|
||||
getColumnValue,
|
||||
setColumnValue,
|
||||
getColumnValues,
|
||||
setColumnValues,
|
||||
});
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newValues) => {
|
||||
if (!isValuesEqual(newValues, selectedValues.value)) {
|
||||
selectedValues.value = newValues;
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
watch(
|
||||
selectedValues,
|
||||
(newValues) => {
|
||||
if (!isValuesEqual(newValues, props.modelValue)) {
|
||||
emit('update:modelValue', selectedValues.value);
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
useExpose<PickerExpose>({ confirm });
|
||||
|
||||
return () => (
|
||||
<div class={bem()}>
|
||||
|
@ -1,18 +1,21 @@
|
||||
import { ref, watch, reactive, defineComponent, type InjectionKey } from 'vue';
|
||||
import {
|
||||
ref,
|
||||
watchEffect,
|
||||
defineComponent,
|
||||
type PropType,
|
||||
type InjectionKey,
|
||||
} from 'vue';
|
||||
|
||||
// Utils
|
||||
import { deepClone } from '../utils/deep-clone';
|
||||
import {
|
||||
clamp,
|
||||
isObject,
|
||||
unknownProp,
|
||||
numericProp,
|
||||
makeArrayProp,
|
||||
makeNumberProp,
|
||||
preventDefault,
|
||||
createNamespace,
|
||||
makeRequiredProp,
|
||||
} from '../utils';
|
||||
import { getElementTranslateY, findIndexOfEnabledOption } from './utils';
|
||||
|
||||
// Composables
|
||||
import { useParent } from '@vant/use';
|
||||
@ -20,42 +23,36 @@ import { useTouch } from '../composables/use-touch';
|
||||
import { useExpose } from '../composables/use-expose';
|
||||
|
||||
// Types
|
||||
import type { PickerOption, PickerColumnProvide } from './types';
|
||||
import type {
|
||||
PickerOption,
|
||||
PickerFieldNames,
|
||||
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');
|
||||
|
||||
function getElementTranslateY(element: Element) {
|
||||
const { transform } = window.getComputedStyle(element);
|
||||
const translateY = transform.slice(7, transform.length - 1).split(', ')[5];
|
||||
return Number(translateY);
|
||||
}
|
||||
|
||||
export const PICKER_KEY: InjectionKey<PickerColumnProvide> = Symbol(name);
|
||||
|
||||
const isOptionDisabled = (option: PickerOption) =>
|
||||
isObject(option) && option.disabled;
|
||||
|
||||
export default defineComponent({
|
||||
name,
|
||||
|
||||
props: {
|
||||
textKey: makeRequiredProp(String),
|
||||
value: numericProp,
|
||||
fields: makeRequiredProp(Object as PropType<Required<PickerFieldNames>>),
|
||||
options: makeArrayProp<PickerOption>(),
|
||||
readonly: Boolean,
|
||||
allowHtml: Boolean,
|
||||
className: unknownProp,
|
||||
itemHeight: makeRequiredProp(Number),
|
||||
defaultIndex: makeNumberProp(0),
|
||||
optionHeight: makeRequiredProp(Number),
|
||||
swipeDuration: makeRequiredProp(numericProp),
|
||||
initialOptions: makeArrayProp<PickerOption>(),
|
||||
visibleItemCount: makeRequiredProp(numericProp),
|
||||
visibleOptionNum: makeRequiredProp(numericProp),
|
||||
},
|
||||
|
||||
emits: ['change'],
|
||||
@ -68,61 +65,34 @@ export default defineComponent({
|
||||
let transitionEndTrigger: null | (() => void);
|
||||
|
||||
const wrapper = ref<HTMLElement>();
|
||||
|
||||
const state = reactive({
|
||||
index: props.defaultIndex,
|
||||
offset: 0,
|
||||
duration: 0,
|
||||
options: deepClone(props.initialOptions),
|
||||
});
|
||||
|
||||
const currentOffset = ref(0);
|
||||
const currentDuration = ref(0);
|
||||
const touch = useTouch();
|
||||
|
||||
const count = () => state.options.length;
|
||||
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());
|
||||
const updateValueByIndex = (index: number) => {
|
||||
const enabledIndex = findIndexOfEnabledOption(props.options, index);
|
||||
const offset = -enabledIndex * props.optionHeight;
|
||||
|
||||
for (let i = index; i < count(); i++) {
|
||||
if (!isOptionDisabled(state.options[i])) return i;
|
||||
}
|
||||
for (let i = index - 1; i >= 0; i--) {
|
||||
if (!isOptionDisabled(state.options[i])) return i;
|
||||
}
|
||||
};
|
||||
|
||||
const setIndex = (index: number, emitChange?: boolean) => {
|
||||
index = adjustIndex(index) || 0;
|
||||
|
||||
const offset = -index * props.itemHeight;
|
||||
const trigger = () => {
|
||||
if (index !== state.index) {
|
||||
state.index = index;
|
||||
|
||||
if (emitChange) {
|
||||
emit('change', index);
|
||||
}
|
||||
const value = props.options[enabledIndex][props.fields.value];
|
||||
if (value !== props.value) {
|
||||
emit('change', value);
|
||||
}
|
||||
};
|
||||
|
||||
// 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;
|
||||
};
|
||||
|
||||
const setOptions = (options: PickerOption[]) => {
|
||||
if (JSON.stringify(options) !== JSON.stringify(state.options)) {
|
||||
state.options = deepClone(options);
|
||||
setIndex(props.defaultIndex);
|
||||
}
|
||||
currentOffset.value = offset;
|
||||
};
|
||||
|
||||
const onClickItem = (index: number) => {
|
||||
@ -131,34 +101,28 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
transitionEndTrigger = null;
|
||||
state.duration = DEFAULT_DURATION;
|
||||
setIndex(index, true);
|
||||
};
|
||||
|
||||
const getOptionText = (option: PickerOption) => {
|
||||
if (isObject(option) && props.textKey in option) {
|
||||
return option[props.textKey];
|
||||
}
|
||||
return option;
|
||||
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;
|
||||
setIndex(index, true);
|
||||
currentDuration.value = +props.swipeDuration;
|
||||
updateValueByIndex(index);
|
||||
};
|
||||
|
||||
const stopMomentum = () => {
|
||||
moving = false;
|
||||
state.duration = 0;
|
||||
currentDuration.value = 0;
|
||||
|
||||
if (transitionEndTrigger) {
|
||||
transitionEndTrigger();
|
||||
@ -175,13 +139,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;
|
||||
@ -199,16 +161,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;
|
||||
}
|
||||
};
|
||||
|
||||
@ -217,23 +179,22 @@ 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;
|
||||
setIndex(index, true);
|
||||
const index = getIndexByOffset(currentOffset.value);
|
||||
currentDuration.value = DEFAULT_DURATION;
|
||||
updateValueByIndex(index);
|
||||
|
||||
// compatible with desktop scenario
|
||||
// use setTimeout to skip the click event Emitted after touchstart
|
||||
// use setTimeout to skip the click event emitted after touchstart
|
||||
setTimeout(() => {
|
||||
moving = false;
|
||||
}, 0);
|
||||
@ -241,21 +202,24 @@ export default defineComponent({
|
||||
|
||||
const renderOptions = () => {
|
||||
const optionStyle = {
|
||||
height: `${props.itemHeight}px`,
|
||||
height: `${props.optionHeight}px`,
|
||||
};
|
||||
|
||||
return state.options.map((option, index: number) => {
|
||||
const text = getOptionText(option);
|
||||
const disabled = isOptionDisabled(option);
|
||||
|
||||
return props.options.map((option, index) => {
|
||||
const text = option[props.fields.text];
|
||||
const { disabled } = option;
|
||||
const value: string | number = option[props.fields.value];
|
||||
const data = {
|
||||
role: 'button',
|
||||
style: optionStyle,
|
||||
tabindex: disabled ? -1 : 0,
|
||||
class: bem('item', {
|
||||
disabled,
|
||||
selected: index === state.index,
|
||||
}),
|
||||
class: [
|
||||
bem('item', {
|
||||
disabled,
|
||||
selected: value === props.value,
|
||||
}),
|
||||
option.className,
|
||||
],
|
||||
onClick: () => onClickItem(index),
|
||||
};
|
||||
|
||||
@ -272,39 +236,21 @@ export default defineComponent({
|
||||
});
|
||||
};
|
||||
|
||||
const setValue = (value: string) => {
|
||||
const { options } = state;
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
if (getOptionText(options[i]) === value) {
|
||||
return setIndex(i);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getValue = (): PickerOption => state.options[state.index];
|
||||
|
||||
setIndex(state.index);
|
||||
|
||||
useParent(PICKER_KEY);
|
||||
useExpose({
|
||||
state,
|
||||
setIndex,
|
||||
getValue,
|
||||
setValue,
|
||||
setOptions,
|
||||
stopMomentum,
|
||||
useExpose({ stopMomentum });
|
||||
|
||||
watchEffect(() => {
|
||||
const index = props.options.findIndex(
|
||||
(option) => option[props.fields.value] === props.value
|
||||
);
|
||||
const enabledIndex = findIndexOfEnabledOption(props.options, index);
|
||||
const offset = -enabledIndex * props.optionHeight;
|
||||
currentOffset.value = offset;
|
||||
});
|
||||
|
||||
watch(() => props.initialOptions, setOptions);
|
||||
|
||||
watch(
|
||||
() => props.defaultIndex,
|
||||
(value) => setIndex(value)
|
||||
);
|
||||
|
||||
return () => (
|
||||
<div
|
||||
class={[bem(), props.className]}
|
||||
class={bem()}
|
||||
onTouchstart={onTouchStart}
|
||||
onTouchmove={onTouchMove}
|
||||
onTouchend={onTouchEnd}
|
||||
@ -313,9 +259,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}
|
||||
|
@ -35,13 +35,18 @@ import { Toast } from 'vant';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const columns = ['Delaware', 'Florida', 'Georqia', 'Indiana', 'Maine'];
|
||||
|
||||
const onConfirm = (value, index) => {
|
||||
Toast(`Value: ${value}, Index: ${index}`);
|
||||
const columns = [
|
||||
{ text: 'Delaware', value: 'Delaware' },
|
||||
{ text: 'Florida', value: 'Florida' },
|
||||
{ text: 'Georqia', value: 'Georqia' },
|
||||
{ text: 'Indiana', value: 'Indiana' },
|
||||
{ text: 'Maine', value: 'Maine' },
|
||||
];
|
||||
const onConfirm = ({ selectedValues }) => {
|
||||
Toast(`Value: ${selectedValues.join(',')}`);
|
||||
};
|
||||
const onChange = (value, index) => {
|
||||
Toast(`Value: ${value}, Index: ${index}`);
|
||||
const onChange = ({ selectedValues }) => {
|
||||
Toast(`Value: ${selectedValues.join(',')}`);
|
||||
};
|
||||
const onCancel = () => Toast('Cancel');
|
||||
|
||||
@ -55,161 +60,6 @@ export default {
|
||||
};
|
||||
```
|
||||
|
||||
### Default Index
|
||||
|
||||
```html
|
||||
<van-picker title="Title" :columns="columns" :default-index="2" />
|
||||
```
|
||||
|
||||
### Multiple Columns
|
||||
|
||||
```html
|
||||
<van-picker title="Title" :columns="columns" />
|
||||
```
|
||||
|
||||
```js
|
||||
export default {
|
||||
setup() {
|
||||
const columns = [
|
||||
{
|
||||
values: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
|
||||
defaultIndex: 2,
|
||||
},
|
||||
{
|
||||
values: ['Morning', 'Afternoon', 'Evening'],
|
||||
defaultIndex: 1,
|
||||
},
|
||||
];
|
||||
|
||||
return { columns };
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Cascade
|
||||
|
||||
```html
|
||||
<van-picker title="Title" :columns="columns" />
|
||||
```
|
||||
|
||||
```js
|
||||
export default {
|
||||
setup() {
|
||||
const columns = [
|
||||
{
|
||||
text: 'Zhejiang',
|
||||
children: [
|
||||
{
|
||||
text: 'Hangzhou',
|
||||
children: [{ text: 'Xihu' }, { text: 'Yuhang' }],
|
||||
},
|
||||
{
|
||||
text: 'Wenzhou',
|
||||
children: [{ text: 'Lucheng' }, { text: 'Ouhai' }],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Fujian',
|
||||
children: [
|
||||
{
|
||||
text: 'Fuzhou',
|
||||
children: [{ text: 'Gulou' }, { text: 'Taijiang' }],
|
||||
},
|
||||
{
|
||||
text: 'Xiamen',
|
||||
children: [{ text: 'Siming' }, { text: 'Haicang' }],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return { columns };
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Disable option
|
||||
|
||||
```html
|
||||
<van-picker :columns="columns" />
|
||||
```
|
||||
|
||||
```js
|
||||
export default {
|
||||
setup() {
|
||||
const columns = [
|
||||
{ text: 'Delaware', disabled: true },
|
||||
{ text: 'Florida' },
|
||||
{ text: 'Georqia' },
|
||||
];
|
||||
|
||||
return { columns };
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Set Column Values
|
||||
|
||||
```html
|
||||
<van-picker ref="picker" title="Title" :columns="columns" @change="onChange" />
|
||||
```
|
||||
|
||||
```js
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const picker = ref(null);
|
||||
|
||||
const states = {
|
||||
Group1: ['Delaware', 'Florida', 'Georqia', 'Indiana', 'Maine'],
|
||||
Group2: ['Alabama', 'Kansas', 'Louisiana', 'Texas'],
|
||||
};
|
||||
const columns = [
|
||||
{ values: Object.keys(states) },
|
||||
{ values: states.Group1 },
|
||||
];
|
||||
|
||||
const onChange = (values) => {
|
||||
picker.value.setColumnValues(1, states[values[0]]);
|
||||
};
|
||||
|
||||
return {
|
||||
picker,
|
||||
columns,
|
||||
onChange,
|
||||
};
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Loading
|
||||
|
||||
When Picker columns data is acquired asynchronously, use `loading` prop to show loading prompt.
|
||||
|
||||
```html
|
||||
<van-picker title="Title" :columns="columns" :loading="loading" />
|
||||
```
|
||||
|
||||
```js
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const columns = ref([]);
|
||||
const loading = ref(true);
|
||||
|
||||
setTimeout(() => {
|
||||
columns.value = ['Option'];
|
||||
loading.value = false;
|
||||
}, 1000);
|
||||
|
||||
return { columns, loading };
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### With Popup
|
||||
|
||||
```html
|
||||
@ -236,13 +86,19 @@ import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const columns = ['Delaware', 'Florida', 'Georqia', 'Indiana', 'Maine'];
|
||||
const columns = [
|
||||
{ text: 'Delaware', value: 'Delaware' },
|
||||
{ text: 'Florida', value: 'Florida' },
|
||||
{ text: 'Georqia', value: 'Georqia' },
|
||||
{ text: 'Indiana', value: 'Indiana' },
|
||||
{ text: 'Maine', value: 'Maine' },
|
||||
];
|
||||
const result = ref('');
|
||||
const showPicker = ref(false);
|
||||
|
||||
const onConfirm = (value) => {
|
||||
result.value = value;
|
||||
const onConfirm = ({ selectedOptions }) => {
|
||||
showPicker.value = false;
|
||||
fieldValue.value = selectedOptions[0].text;
|
||||
};
|
||||
|
||||
return {
|
||||
@ -255,6 +111,141 @@ export default {
|
||||
};
|
||||
```
|
||||
|
||||
### Multiple Columns
|
||||
|
||||
```html
|
||||
<van-picker title="Title" :columns="columns" />
|
||||
```
|
||||
|
||||
```js
|
||||
export default {
|
||||
setup() {
|
||||
const columns = [
|
||||
[
|
||||
{ text: 'Monday', value: 'Monday' },
|
||||
{ text: 'Tuesday', value: 'Tuesday' },
|
||||
{ text: 'Wednesday', value: 'Wednesday' },
|
||||
{ text: 'Thursday', value: 'Thursday' },
|
||||
{ text: 'Friday', value: 'Friday' },
|
||||
],
|
||||
[
|
||||
{ text: 'Morning', value: 'Morning' },
|
||||
{ text: 'Afternoon', value: 'Afternoon' },
|
||||
{ text: 'Evening', value: 'Evening' },
|
||||
],
|
||||
];
|
||||
|
||||
return { columns };
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Cascade
|
||||
|
||||
```html
|
||||
<van-picker title="Title" :columns="columns" />
|
||||
```
|
||||
|
||||
```js
|
||||
export default {
|
||||
setup() {
|
||||
const columns = [
|
||||
{
|
||||
text: 'Zhejiang',
|
||||
value: 'Zhejiang',
|
||||
children: [
|
||||
{
|
||||
text: 'Hangzhou',
|
||||
value: 'Hangzhou',
|
||||
children: [
|
||||
{ text: 'Xihu', value: 'Xihu' },
|
||||
{ text: 'Yuhang', value: 'Yuhang' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Wenzhou',
|
||||
value: 'Wenzhou',
|
||||
children: [
|
||||
{ text: 'Lucheng', value: 'Lucheng' },
|
||||
{ text: 'Ouhai', value: 'Ouhai' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Fujian',
|
||||
value: 'Fujian',
|
||||
children: [
|
||||
{
|
||||
text: 'Fuzhou',
|
||||
value: 'Fuzhou',
|
||||
children: [
|
||||
{ text: 'Gulou', value: 'Gulou' },
|
||||
{ text: 'Taijiang', value: 'Taijiang' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Xiamen',
|
||||
value: 'Xiamen',
|
||||
children: [
|
||||
{ text: 'Siming', value: 'Siming' },
|
||||
{ text: 'Haicang', value: 'Haicang' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return { columns };
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Disable option
|
||||
|
||||
```html
|
||||
<van-picker :columns="columns" />
|
||||
```
|
||||
|
||||
```js
|
||||
export default {
|
||||
setup() {
|
||||
const columns = [
|
||||
{ text: 'Delaware', value: 'Delaware', disabled: true },
|
||||
{ text: 'Florida', value: 'Florida' },
|
||||
{ text: 'Georqia', value: 'Georqia' },
|
||||
];
|
||||
return { columns };
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Loading
|
||||
|
||||
When Picker columns data is acquired asynchronously, use `loading` prop to show loading prompt.
|
||||
|
||||
```html
|
||||
<van-picker title="Title" :columns="columns" :loading="loading" />
|
||||
```
|
||||
|
||||
```js
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const columns = ref([]);
|
||||
const loading = ref(true);
|
||||
|
||||
setTimeout(() => {
|
||||
columns.value = [{ text: 'Option', value: 'option' }];
|
||||
loading.value = false;
|
||||
}, 1000);
|
||||
|
||||
return { columns, loading };
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Custom Columns Field
|
||||
|
||||
```html
|
||||
@ -299,6 +290,7 @@ export default {
|
||||
|
||||
const customFieldName = {
|
||||
text: 'cityName',
|
||||
value: 'cityName',
|
||||
children: 'cities',
|
||||
};
|
||||
|
||||
@ -316,8 +308,8 @@ export default {
|
||||
|
||||
| Attribute | Description | Type | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| columns | Columns data | _Column[]_ | `[]` |
|
||||
| columns-field-names | custom columns field | _object_ | `{ text: 'text', values: 'values', children: 'children' }` |
|
||||
| columns | Columns data | _PickerOption[] \| PickerOption[][]_ | `[]` |
|
||||
| columns-field-names | custom columns field | _object_ | `{ text: 'text', value: 'value', children: 'children' }` |
|
||||
| title | Toolbar title | _string_ | - |
|
||||
| confirm-button-text | Text of confirm button | _string_ | `Confirm` |
|
||||
| cancel-button-text | Text of cancel button | _string_ | `Cancel` |
|
||||
@ -325,59 +317,47 @@ export default {
|
||||
| loading | Whether to show loading prompt | _boolean_ | `false` |
|
||||
| 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 animation,unit `ms` | _number \| string_ | `1000` |
|
||||
|
||||
### Events
|
||||
|
||||
Picker events will pass different parameters according to the columns are single or multiple
|
||||
|
||||
| Event | Description | Arguments |
|
||||
| --- | --- | --- |
|
||||
| confirm | Emitted when click confirm button | Single column:current value,current index<br>Multiple columns:current values,current indexes |
|
||||
| cancel | Emitted when click cancel button | Single column:current value,current index<br>Multiple columns:current values,current indexes |
|
||||
| change | Emitted when current option changed | Single column:Picker instance, current value,current index<br>Multiple columns:Picker instance, current values,column index |
|
||||
| confirm | Emitted when the confirm button is clicked | _{ selectedValues, selectedOptions }_ |
|
||||
| cancel | Emitted when the cancel button is clicked | _{ selectedValues, selectedOptions }_ |
|
||||
| change | Emitted when current option is changed | _{ selectedValues, selectedOptions, columnIndex }_ |
|
||||
|
||||
### Slots
|
||||
|
||||
| Name | Description | SlotProps |
|
||||
| --------------- | ---------------------------- | -------------------------- |
|
||||
| toolbar `3.1.2` | Custom toolbar content | - |
|
||||
| title | Custom title | - |
|
||||
| confirm | Custom confirm button text | - |
|
||||
| cancel | Custom cancel button text | - |
|
||||
| option | Custom option content | _option: string \| object_ |
|
||||
| columns-top | Custom content above columns | - |
|
||||
| columns-bottom | Custom content below columns | - |
|
||||
| Name | Description | SlotProps |
|
||||
| --------------- | ---------------------------- | ---------------------- |
|
||||
| toolbar `3.1.2` | Custom toolbar content | - |
|
||||
| title | Custom title | - |
|
||||
| confirm | Custom confirm button text | - |
|
||||
| cancel | Custom cancel button text | - |
|
||||
| option | Custom option content | _option: PickerOption_ |
|
||||
| columns-top | Custom content above columns | - |
|
||||
| columns-bottom | Custom content below columns | - |
|
||||
|
||||
### Data Structure of Column
|
||||
### Data Structure of PickerOption
|
||||
|
||||
| Key | Description | Type |
|
||||
| ------------ | ------------------------- | --------------------------- |
|
||||
| values | Value of column | _Array<string \| number>_ |
|
||||
| defaultIndex | Default value index | _number_ |
|
||||
| className | ClassName for this column | _string \| Array \| object_ |
|
||||
| children | Cascade children | _Column_ |
|
||||
| Key | Description | Type |
|
||||
| --------- | ------------------------- | --------------------------- |
|
||||
| text | Text | _string \| number_ |
|
||||
| value | Value of option | _string \| number_ |
|
||||
| disabled | Whether to disable option | _boolean_ |
|
||||
| children | Cascade children options | _PickerOption[]_ |
|
||||
| className | ClassName for this option | _string \| Array \| object_ |
|
||||
|
||||
### Methods
|
||||
|
||||
Use [ref](https://v3.vuejs.org/guide/component-template-refs.html) to get Picker instance and call instance methods.
|
||||
|
||||
| Name | Description | Attribute | Return value |
|
||||
| --- | --- | --- | --- |
|
||||
| getValues | Get current values of all columns | - | values |
|
||||
| setValues | Set current values of all columns | values | - |
|
||||
| getIndexes | Get current indexes of all columns | - | indexes |
|
||||
| setIndexes | Set current indexes of all columns | indexes | - |
|
||||
| getColumnValue | Get current value of the column | columnIndex | value |
|
||||
| setColumnValue | Set current value of the column | columnIndex, value | - |
|
||||
| getColumnIndex | Get current index of the column | columnIndex | optionIndex |
|
||||
| setColumnIndex | Set current index of the column | columnIndex, optionIndex | - |
|
||||
| getColumnValues | Get columns data of the column | columnIndex | values |
|
||||
| setColumnValues | Set columns data of the column | columnIndex, values | - |
|
||||
| confirm | Stop scrolling and emit confirm event | - | - |
|
||||
| Name | Description | Attribute | Return value |
|
||||
| ------- | ------------------------------------- | --------- | ------------ |
|
||||
| confirm | Stop scrolling and emit confirm event | - | - |
|
||||
|
||||
### Types
|
||||
|
||||
@ -390,9 +370,10 @@ import type {
|
||||
PickerOption,
|
||||
PickerInstance,
|
||||
PickerFieldNames,
|
||||
PickerObjectColumn,
|
||||
PickerObjectOption,
|
||||
PickerToolbarPosition,
|
||||
PickerCancelEventParams,
|
||||
PickerChangeEventParams,
|
||||
PickerConfirmEventParams,
|
||||
} from 'vant';
|
||||
```
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
### 介绍
|
||||
|
||||
提供多个选项集合供用户选择,支持单列选择和多列级联,通常与[弹出层](#/zh-CN/popup)组件配合使用。
|
||||
提供多个选项集合供用户选择,支持单列选择、多列选择和级联选择,通常与[弹出层](#/zh-CN/popup)组件配合使用。
|
||||
|
||||
### 引入
|
||||
|
||||
@ -43,13 +43,18 @@ import { Toast } from 'vant';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const columns = ['杭州', '宁波', '温州', '绍兴', '湖州', '嘉兴', '金华'];
|
||||
|
||||
const onConfirm = (value, index) => {
|
||||
Toast(`当前值: ${value}, 当前索引: ${index}`);
|
||||
const columns = [
|
||||
{ text: '杭州', value: 'Hangzhou' },
|
||||
{ text: '宁波', value: 'Ningbo' },
|
||||
{ text: '温州', value: 'Wenzhou' },
|
||||
{ text: '绍兴', value: 'Shaoxing' },
|
||||
{ text: '湖州', value: 'Huzhou' },
|
||||
];
|
||||
const onConfirm = ({ selectedValues }) => {
|
||||
Toast(`当前值: ${selectedValues.join(',')}`);
|
||||
};
|
||||
const onChange = (value, index) => {
|
||||
Toast(`当前值: ${value}, 当前索引: ${index}`);
|
||||
const onChange = ({ selectedValues }) => {
|
||||
Toast(`当前值: ${selectedValues.join(',')}`);
|
||||
};
|
||||
const onCancel = () => Toast('取消');
|
||||
|
||||
@ -63,175 +68,6 @@ export default {
|
||||
};
|
||||
```
|
||||
|
||||
### 默认选中项
|
||||
|
||||
单列选择时,可以通过 `default-index` 属性设置初始选中项的索引。
|
||||
|
||||
```html
|
||||
<van-picker title="标题" :columns="columns" :default-index="2" />
|
||||
```
|
||||
|
||||
### 多列选择
|
||||
|
||||
`columns` 属性可以通过对象数组的形式配置多列选择,对象中可以配置选项数据、初始选中项等,详细格式见[下方表格](#/zh-CN/picker#column-shu-ju-jie-gou)。
|
||||
|
||||
```html
|
||||
<van-picker title="标题" :columns="columns" />
|
||||
```
|
||||
|
||||
```js
|
||||
export default {
|
||||
setup() {
|
||||
const columns = [
|
||||
// 第一列
|
||||
{
|
||||
values: ['周一', '周二', '周三', '周四', '周五'],
|
||||
defaultIndex: 2,
|
||||
},
|
||||
// 第二列
|
||||
{
|
||||
values: ['上午', '下午', '晚上'],
|
||||
defaultIndex: 1,
|
||||
},
|
||||
];
|
||||
|
||||
return { columns };
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### 级联选择
|
||||
|
||||
使用 `columns` 的 `children` 字段可以实现选项级联的效果。如果级联层级较多,推荐使用 [Cascader 级联选项组件](#/zh-CN/cascader)。
|
||||
|
||||
```html
|
||||
<van-picker title="标题" :columns="columns" />
|
||||
```
|
||||
|
||||
```js
|
||||
export default {
|
||||
setup() {
|
||||
const columns = [
|
||||
{
|
||||
text: '浙江',
|
||||
children: [
|
||||
{
|
||||
text: '杭州',
|
||||
children: [{ text: '西湖区' }, { text: '余杭区' }],
|
||||
},
|
||||
{
|
||||
text: '温州',
|
||||
children: [{ text: '鹿城区' }, { text: '瓯海区' }],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '福建',
|
||||
children: [
|
||||
{
|
||||
text: '福州',
|
||||
children: [{ text: '鼓楼区' }, { text: '台江区' }],
|
||||
},
|
||||
{
|
||||
text: '厦门',
|
||||
children: [{ text: '思明区' }, { text: '海沧区' }],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return { columns };
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
> 级联选择的数据嵌套深度需要保持一致,如果部分选项没有子选项,可以使用空字符串进行占位。
|
||||
|
||||
### 禁用选项
|
||||
|
||||
选项可以为对象结构,通过设置 `disabled` 来禁用该选项。
|
||||
|
||||
```html
|
||||
<van-picker :columns="columns" />
|
||||
```
|
||||
|
||||
```js
|
||||
export default {
|
||||
setup() {
|
||||
const columns = [
|
||||
{ text: '杭州', disabled: true },
|
||||
{ text: '宁波' },
|
||||
{ text: '温州' },
|
||||
];
|
||||
|
||||
return { columns };
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### 动态设置选项
|
||||
|
||||
通过 Picker 上的实例方法可以更灵活地控制选择器,比如使用 `setColumnValues` 方法实现多列联动。
|
||||
|
||||
```html
|
||||
<van-picker ref="picker" :columns="columns" @change="onChange" />
|
||||
```
|
||||
|
||||
```js
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const picker = ref(null);
|
||||
|
||||
const cities = {
|
||||
浙江: ['杭州', '宁波', '温州', '嘉兴', '湖州'],
|
||||
福建: ['福州', '厦门', '莆田', '三明', '泉州'],
|
||||
};
|
||||
const columns = [
|
||||
{ values: Object.keys(cities) },
|
||||
{ values: cities['浙江'] },
|
||||
];
|
||||
|
||||
const onChange = (values) => {
|
||||
picker.value.setColumnValues(1, cities[values[0]]);
|
||||
};
|
||||
|
||||
return {
|
||||
picker,
|
||||
columns,
|
||||
onChange,
|
||||
};
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### 加载状态
|
||||
|
||||
若选择器数据是异步获取的,可以通过 `loading` 属性显示加载提示。
|
||||
|
||||
```html
|
||||
<van-picker :columns="columns" :loading="loading" />
|
||||
```
|
||||
|
||||
```js
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const columns = ref([]);
|
||||
const loading = ref(true);
|
||||
|
||||
setTimeout(() => {
|
||||
columns.value = ['选项'];
|
||||
loading.value = false;
|
||||
}, 1000);
|
||||
|
||||
return { columns, loading };
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### 搭配弹出层使用
|
||||
|
||||
在实际场景中,Picker 通常作为用于辅助表单填写,可以搭配 Popup 和 Field 实现该效果。
|
||||
@ -259,13 +95,19 @@ import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const columns = ['杭州', '宁波', '温州', '绍兴', '湖州', '嘉兴', '金华'];
|
||||
const columns = [
|
||||
{ text: '杭州', value: 'Hangzhou' },
|
||||
{ text: '宁波', value: 'Ningbo' },
|
||||
{ text: '温州', value: 'Wenzhou' },
|
||||
{ text: '绍兴', value: 'Shaoxing' },
|
||||
{ text: '湖州', value: 'Huzhou' },
|
||||
];
|
||||
const result = ref('');
|
||||
const showPicker = ref(false);
|
||||
|
||||
const onConfirm = (value) => {
|
||||
result.value = value;
|
||||
const onConfirm = ({ selectedOptions }) => {
|
||||
showPicker.value = false;
|
||||
fieldValue.value = selectedOptions[0].text;
|
||||
};
|
||||
|
||||
return {
|
||||
@ -278,6 +120,151 @@ export default {
|
||||
};
|
||||
```
|
||||
|
||||
### 多列选择
|
||||
|
||||
`columns` 属性可以通过二维数组的形式配置多列选择。
|
||||
|
||||
```html
|
||||
<van-picker title="标题" :columns="columns" />
|
||||
```
|
||||
|
||||
```js
|
||||
export default {
|
||||
setup() {
|
||||
const columns = [
|
||||
// 第一列
|
||||
[
|
||||
{ text: '周一', value: 'Monday' },
|
||||
{ text: '周二', value: 'Tuesday' },
|
||||
{ text: '周三', value: 'Wednesday' },
|
||||
{ text: '周四', value: 'Thursday' },
|
||||
{ text: '周五', value: 'Friday' },
|
||||
],
|
||||
// 第二列
|
||||
[
|
||||
{ text: '上午', value: 'Morning' },
|
||||
{ text: '下午', value: 'Afternoon' },
|
||||
{ text: '晚上', value: 'Evening' },
|
||||
],
|
||||
];
|
||||
|
||||
return { columns };
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### 级联选择
|
||||
|
||||
使用 `columns` 的 `children` 字段可以实现选项级联的效果。如果级联层级较多,推荐使用 [Cascader 级联选项组件](#/zh-CN/cascader)。
|
||||
|
||||
```html
|
||||
<van-picker title="标题" :columns="columns" />
|
||||
```
|
||||
|
||||
```js
|
||||
export default {
|
||||
setup() {
|
||||
const columns = [
|
||||
{
|
||||
text: '浙江',
|
||||
value: 'Zhejiang',
|
||||
children: [
|
||||
{
|
||||
text: '杭州',
|
||||
value: 'Hangzhou',
|
||||
children: [
|
||||
{ text: '西湖区', value: 'Xihu' },
|
||||
{ text: '余杭区', value: 'Yuhang' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '温州',
|
||||
value: 'Wenzhou',
|
||||
children: [
|
||||
{ text: '鹿城区', value: 'Lucheng' },
|
||||
{ text: '瓯海区', value: 'Ouhai' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '福建',
|
||||
value: 'Fujian',
|
||||
children: [
|
||||
{
|
||||
text: '福州',
|
||||
value: 'Fuzhou',
|
||||
children: [
|
||||
{ text: '鼓楼区', value: 'Gulou' },
|
||||
{ text: '台江区', value: 'Taijiang' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '厦门',
|
||||
value: 'Xiamen',
|
||||
children: [
|
||||
{ text: '思明区', value: 'Siming' },
|
||||
{ text: '海沧区', value: 'Haicang' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return { columns };
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
> 级联选择的数据嵌套深度需要保持一致,如果部分选项没有子选项,可以使用空字符串进行占位。
|
||||
|
||||
### 禁用选项
|
||||
|
||||
选项可以为对象结构,通过设置 `disabled` 来禁用该选项。
|
||||
|
||||
```html
|
||||
<van-picker :columns="columns" />
|
||||
```
|
||||
|
||||
```js
|
||||
export default {
|
||||
setup() {
|
||||
const columns = [
|
||||
{ text: '杭州', value: 'Hangzhou', disabled: true },
|
||||
{ text: '宁波', value: 'Ningbo' },
|
||||
{ text: '温州', value: 'Wenzhou' },
|
||||
];
|
||||
return { columns };
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### 加载状态
|
||||
|
||||
若选择器数据是异步获取的,可以通过 `loading` 属性显示加载提示。
|
||||
|
||||
```html
|
||||
<van-picker :columns="columns" :loading="loading" />
|
||||
```
|
||||
|
||||
```js
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const columns = ref([]);
|
||||
const loading = ref(true);
|
||||
|
||||
setTimeout(() => {
|
||||
columns.value = [{ text: '选项', value: 'option' }];
|
||||
loading.value = false;
|
||||
}, 1000);
|
||||
|
||||
return { columns, loading };
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### 自定义 Columns 的结构
|
||||
|
||||
```html
|
||||
@ -322,6 +309,7 @@ export default {
|
||||
|
||||
const customFieldName = {
|
||||
text: 'cityName',
|
||||
value: 'cityName',
|
||||
children: 'cities',
|
||||
};
|
||||
|
||||
@ -339,8 +327,8 @@ export default {
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| --- | --- | --- | --- |
|
||||
| columns | 对象数组,配置每一列显示的数据 | _Column[]_ | `[]` |
|
||||
| columns-field-names | 自定义 `columns` 结构中的字段 | _object_ | `{ text: 'text', values: 'values', children: 'children' }` |
|
||||
| columns | 对象数组,配置每一列显示的数据 | _PickerOption[] \| PickerOption[][]_ | `[]` |
|
||||
| columns-field-names | 自定义 `columns` 结构中的字段 | _object_ | `{ text: 'text', value: 'value', children: 'children' }` |
|
||||
| title | 顶部栏标题 | _string_ | - |
|
||||
| confirm-button-text | 确认按钮文字 | _string_ | `确认` |
|
||||
| cancel-button-text | 取消按钮文字 | _string_ | `取消` |
|
||||
@ -348,61 +336,47 @@ export default {
|
||||
| loading | 是否显示加载状态 | _boolean_ | `false` |
|
||||
| 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
|
||||
|
||||
当选择器有多列时,事件回调参数会返回数组。
|
||||
|
||||
| 事件名 | 说明 | 回调参数 |
|
||||
| --- | --- | --- |
|
||||
| confirm | 点击完成按钮时触发 | 单列:选中值,选中值对应的索引<br>多列:所有列选中值,所有列选中值对应的索引 |
|
||||
| cancel | 点击取消按钮时触发 | 单列:选中值,选中值对应的索引<br>多列:所有列选中值,所有列选中值对应的索引 |
|
||||
| change | 选项改变时触发 | 单列:选中值,选中值对应的索引<br>多列:所有列选中值,当前列对应的索引 |
|
||||
| confirm | 点击完成按钮时触发 | _{ selectedValues, selectedOptions }_ |
|
||||
| cancel | 点击取消按钮时触发 | _{ selectedValues, selectedOptions }_ |
|
||||
| change | 选项改变时触发 | _{ selectedValues, selectedOptions, columnIndex }_ |
|
||||
|
||||
### Slots
|
||||
|
||||
| 名称 | 说明 | 参数 |
|
||||
| ---------------- | ---------------------- | -------------------------- |
|
||||
| toolbar `v3.1.2` | 自定义整个顶部栏的内容 | - |
|
||||
| title | 自定义标题内容 | - |
|
||||
| confirm | 自定义确认按钮内容 | - |
|
||||
| cancel | 自定义取消按钮内容 | - |
|
||||
| option | 自定义选项内容 | _option: string \| object_ |
|
||||
| columns-top | 自定义选项上方内容 | - |
|
||||
| columns-bottom | 自定义选项下方内容 | - |
|
||||
| 名称 | 说明 | 参数 |
|
||||
| ---------------- | ---------------------- | ---------------------- |
|
||||
| toolbar `v3.1.2` | 自定义整个顶部栏的内容 | - |
|
||||
| title | 自定义标题内容 | - |
|
||||
| confirm | 自定义确认按钮内容 | - |
|
||||
| cancel | 自定义取消按钮内容 | - |
|
||||
| option | 自定义选项内容 | _option: PickerOption_ |
|
||||
| columns-top | 自定义选项上方内容 | - |
|
||||
| columns-bottom | 自定义选项下方内容 | - |
|
||||
|
||||
### Column 数据结构
|
||||
### PickerOption 数据结构
|
||||
|
||||
当传入多列数据时,`columns` 为一个对象数组,数组中的每一个对象配置每一列,每一列有以下 `key`:
|
||||
|
||||
| 键名 | 说明 | 类型 |
|
||||
| ------------ | -------------------------- | --------------------------- |
|
||||
| values | 列中对应的备选值 | _Array<string \| number>_ |
|
||||
| defaultIndex | 初始选中项的索引,默认为 0 | _number_ |
|
||||
| className | 为对应列添加额外的类名 | _string \| Array \| object_ |
|
||||
| children | 级联选项 | _Column_ |
|
||||
| 键名 | 说明 | 类型 |
|
||||
| --------- | ------------ | --------------------------- |
|
||||
| text | 选项文字内容 | _string \| number_ |
|
||||
| value | 选项对应的值 | _string \| number_ |
|
||||
| disabled | 是否禁用选项 | _boolean_ |
|
||||
| children | 级联选项 | _PickerOption[]_ |
|
||||
| className | 选项额外类名 | _string \| Array \| object_ |
|
||||
|
||||
### 方法
|
||||
|
||||
通过 ref 可以获取到 Picker 实例并调用实例方法,详见[组件实例方法](#/zh-CN/advanced-usage#zu-jian-shi-li-fang-fa)。
|
||||
|
||||
| 方法名 | 说明 | 参数 | 返回值 |
|
||||
| --- | --- | --- | --- |
|
||||
| getValues | 获取所有列选中的值 | - | values |
|
||||
| setValues | 设置所有列选中的值 | values | - |
|
||||
| getIndexes | 获取所有列选中值对应的索引 | - | indexes |
|
||||
| setIndexes | 设置所有列选中值对应的索引 | indexes | - |
|
||||
| getColumnValue | 获取对应列选中的值 | columnIndex | value |
|
||||
| setColumnValue | 设置对应列选中的值 | columnIndex, value | - |
|
||||
| getColumnIndex | 获取对应列选中项的索引 | columnIndex | optionIndex |
|
||||
| setColumnIndex | 设置对应列选中项的索引 | columnIndex, optionIndex | - |
|
||||
| getColumnValues | 获取对应列中所有选项 | columnIndex | values |
|
||||
| setColumnValues | 设置对应列中所有选项 | columnIndex, values | - |
|
||||
| confirm | 停止惯性滚动并触发 confirm 事件 | - | - |
|
||||
| 方法名 | 说明 | 参数 | 返回值 |
|
||||
| ------- | --------------------------------- | ---- | ------ |
|
||||
| confirm | 停止惯性滚动并触发 `confirm` 事件 | - | - |
|
||||
|
||||
### 类型定义
|
||||
|
||||
@ -415,9 +389,10 @@ import type {
|
||||
PickerOption,
|
||||
PickerInstance,
|
||||
PickerFieldNames,
|
||||
PickerObjectColumn,
|
||||
PickerObjectOption,
|
||||
PickerToolbarPosition,
|
||||
PickerCancelEventParams,
|
||||
PickerChangeEventParams,
|
||||
PickerConfirmEventParams,
|
||||
} from 'vant';
|
||||
```
|
||||
|
||||
|
59
packages/vant/src/picker/demo/WithPopup.vue
Normal file
59
packages/vant/src/picker/demo/WithPopup.vue
Normal file
@ -0,0 +1,59 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import VanPicker from '..';
|
||||
import VanField from '../../field';
|
||||
import VanPopup from '../../popup';
|
||||
import { basicColumns } from './data';
|
||||
import { useTranslate } from '../../../docs/site/use-translate';
|
||||
import type { PickerConfirmEventParams } from '../types';
|
||||
|
||||
const t = useTranslate({
|
||||
'zh-CN': {
|
||||
city: '城市',
|
||||
withPopup: '搭配弹出层使用',
|
||||
chooseCity: '选择城市',
|
||||
basicColumns: basicColumns['zh-CN'],
|
||||
},
|
||||
'en-US': {
|
||||
city: 'City',
|
||||
withPopup: 'With Popup',
|
||||
chooseCity: 'Choose City',
|
||||
basicColumns: basicColumns['en-US'],
|
||||
},
|
||||
});
|
||||
|
||||
const showPicker = ref(false);
|
||||
const fieldValue = ref('');
|
||||
|
||||
const onClickField = () => {
|
||||
showPicker.value = true;
|
||||
};
|
||||
const onCancel = () => {
|
||||
showPicker.value = false;
|
||||
};
|
||||
const onConfirm = ({ selectedOptions }: PickerConfirmEventParams) => {
|
||||
showPicker.value = false;
|
||||
fieldValue.value = selectedOptions[0].text as string;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<demo-block card :title="t('withPopup')">
|
||||
<van-field
|
||||
v-model="fieldValue"
|
||||
is-link
|
||||
readonly
|
||||
:label="t('city')"
|
||||
:placeholder="t('chooseCity')"
|
||||
@click="onClickField"
|
||||
/>
|
||||
<van-popup v-model:show="showPicker" round position="bottom">
|
||||
<van-picker
|
||||
:title="t('title')"
|
||||
:columns="t('basicColumns')"
|
||||
@cancel="onCancel"
|
||||
@confirm="onConfirm"
|
||||
/>
|
||||
</van-popup>
|
||||
</demo-block>
|
||||
</template>
|
@ -1,23 +1,48 @@
|
||||
export const dateColumns = {
|
||||
export const basicColumns = {
|
||||
'zh-CN': [
|
||||
{
|
||||
values: ['周一', '周二', '周三', '周四', '周五'],
|
||||
defaultIndex: 2,
|
||||
},
|
||||
{
|
||||
values: ['上午', '下午', '晚上'],
|
||||
defaultIndex: 1,
|
||||
},
|
||||
{ text: '杭州', value: 'Hangzhou' },
|
||||
{ text: '宁波', value: 'Ningbo' },
|
||||
{ text: '温州', value: 'Wenzhou' },
|
||||
{ text: '绍兴', value: 'Shaoxing' },
|
||||
{ text: '湖州', value: 'Huzhou' },
|
||||
],
|
||||
'en-US': [
|
||||
{
|
||||
values: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
|
||||
defaultIndex: 2,
|
||||
},
|
||||
{
|
||||
values: ['Morning', 'Afternoon', 'Evening'],
|
||||
defaultIndex: 1,
|
||||
},
|
||||
{ text: 'Delaware', value: 'Delaware' },
|
||||
{ text: 'Florida', value: 'Florida' },
|
||||
{ text: 'Georqia', value: 'Georqia' },
|
||||
{ text: 'Indiana', value: 'Indiana' },
|
||||
{ text: 'Maine', value: 'Maine' },
|
||||
],
|
||||
};
|
||||
|
||||
export const dateColumns = {
|
||||
'zh-CN': [
|
||||
[
|
||||
{ text: '周一', value: 'Monday' },
|
||||
{ text: '周二', value: 'Tuesday' },
|
||||
{ text: '周三', value: 'Wednesday' },
|
||||
{ text: '周四', value: 'Thursday' },
|
||||
{ text: '周五', value: 'Friday' },
|
||||
],
|
||||
[
|
||||
{ text: '上午', value: 'Morning' },
|
||||
{ text: '下午', value: 'Afternoon' },
|
||||
{ text: '晚上', value: 'Evening' },
|
||||
],
|
||||
],
|
||||
'en-US': [
|
||||
[
|
||||
{ text: 'Monday', value: 'Monday' },
|
||||
{ text: 'Tuesday', value: 'Tuesday' },
|
||||
{ text: 'Wednesday', value: 'Wednesday' },
|
||||
{ text: 'Thursday', value: 'Thursday' },
|
||||
{ text: 'Friday', value: 'Friday' },
|
||||
],
|
||||
[
|
||||
{ text: 'Morning', value: 'Morning' },
|
||||
{ text: 'Afternoon', value: 'Afternoon' },
|
||||
{ text: 'Evening', value: 'Evening' },
|
||||
],
|
||||
],
|
||||
};
|
||||
|
||||
@ -25,27 +50,45 @@ export const cascadeColumns = {
|
||||
'zh-CN': [
|
||||
{
|
||||
text: '浙江',
|
||||
value: 'Zhejiang',
|
||||
children: [
|
||||
{
|
||||
text: '杭州',
|
||||
children: [{ text: '西湖区' }, { text: '余杭区' }],
|
||||
value: 'Hangzhou',
|
||||
children: [
|
||||
{ text: '西湖区', value: 'Xihu' },
|
||||
{ text: '余杭区', value: 'Yuhang' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '温州',
|
||||
children: [{ text: '鹿城区' }, { text: '瓯海区' }],
|
||||
value: 'Wenzhou',
|
||||
children: [
|
||||
{ text: '鹿城区', value: 'Lucheng' },
|
||||
{ text: '瓯海区', value: 'Ouhai' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '福建',
|
||||
value: 'Fujian',
|
||||
children: [
|
||||
{
|
||||
text: '福州',
|
||||
children: [{ text: '鼓楼区' }, { text: '台江区' }],
|
||||
value: 'Fuzhou',
|
||||
children: [
|
||||
{ text: '鼓楼区', value: 'Gulou' },
|
||||
{ text: '台江区', value: 'Taijiang' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '厦门',
|
||||
children: [{ text: '思明区' }, { text: '海沧区' }],
|
||||
value: 'Xiamen',
|
||||
children: [
|
||||
{ text: '思明区', value: 'Siming' },
|
||||
{ text: '海沧区', value: 'Haicang' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -53,34 +96,52 @@ export const cascadeColumns = {
|
||||
'en-US': [
|
||||
{
|
||||
text: 'Zhejiang',
|
||||
value: 'Zhejiang',
|
||||
children: [
|
||||
{
|
||||
text: 'Hangzhou',
|
||||
children: [{ text: 'Xihu' }, { text: 'Yuhang' }],
|
||||
value: 'Hangzhou',
|
||||
children: [
|
||||
{ text: 'Xihu', value: 'Xihu' },
|
||||
{ text: 'Yuhang', value: 'Yuhang' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Wenzhou',
|
||||
children: [{ text: 'Lucheng' }, { text: 'Ouhai' }],
|
||||
value: 'Wenzhou',
|
||||
children: [
|
||||
{ text: 'Lucheng', value: 'Lucheng' },
|
||||
{ text: 'Ouhai', value: 'Ouhai' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Fujian',
|
||||
value: 'Fujian',
|
||||
children: [
|
||||
{
|
||||
text: 'Fuzhou',
|
||||
children: [{ text: 'Gulou' }, { text: 'Taijiang' }],
|
||||
value: 'Fuzhou',
|
||||
children: [
|
||||
{ text: 'Gulou', value: 'Gulou' },
|
||||
{ text: 'Taijiang', value: 'Taijiang' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Xiamen',
|
||||
children: [{ text: 'Siming' }, { text: 'Haicang' }],
|
||||
value: 'Xiamen',
|
||||
children: [
|
||||
{ text: 'Siming', value: 'Siming' },
|
||||
{ text: 'Haicang', value: 'Haicang' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const cascadeColumnsCustomKey = {
|
||||
export const customKeyColumns = {
|
||||
'zh-CN': [
|
||||
{
|
||||
cityName: '浙江',
|
||||
@ -138,3 +199,16 @@ export const cascadeColumnsCustomKey = {
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const disabledColumns = {
|
||||
'zh-CN': [
|
||||
{ text: '杭州', value: 'Hangzhou', disabled: true },
|
||||
{ text: '宁波', value: 'Ningbo' },
|
||||
{ text: '温州', value: 'Wenzhou' },
|
||||
],
|
||||
'en-US': [
|
||||
{ text: 'Delaware', value: 'Delaware', disabled: true },
|
||||
{ text: 'Florida', value: 'Florida' },
|
||||
{ text: 'Georqia', value: 'Georqia' },
|
||||
],
|
||||
};
|
||||
|
@ -1,146 +1,79 @@
|
||||
<script setup lang="ts">
|
||||
import VanPicker from '..';
|
||||
import VanField from '../../field';
|
||||
import VanPopup from '../../popup';
|
||||
import { ref, computed } from 'vue';
|
||||
import { dateColumns, cascadeColumns, cascadeColumnsCustomKey } from './data';
|
||||
import { useTranslate } from '../../../docs/site/use-translate';
|
||||
import WithPopup from './WithPopup.vue';
|
||||
import VanPicker, {
|
||||
PickerChangeEventParams,
|
||||
PickerConfirmEventParams,
|
||||
} from '..';
|
||||
import {
|
||||
dateColumns,
|
||||
basicColumns,
|
||||
cascadeColumns,
|
||||
disabledColumns,
|
||||
customKeyColumns,
|
||||
} from './data';
|
||||
import { Toast } from '../../toast';
|
||||
import { useTranslate } from '../../../docs/site/use-translate';
|
||||
|
||||
const t = useTranslate({
|
||||
'zh-CN': {
|
||||
city: '城市',
|
||||
cascade: '级联选择',
|
||||
withPopup: '搭配弹出层使用',
|
||||
chooseCity: '选择城市',
|
||||
showToolbar: '展示顶部栏',
|
||||
dateColumns: dateColumns['zh-CN'],
|
||||
basicColumns: basicColumns['zh-CN'],
|
||||
defaultIndex: '默认选中项',
|
||||
disableOption: '禁用选项',
|
||||
cascadeColumns: cascadeColumns['zh-CN'],
|
||||
disabledColumns: disabledColumns['zh-CN'],
|
||||
multipleColumns: '多列选择',
|
||||
setColumnValues: '动态设置选项',
|
||||
customChildrenKey: '自定义 Columns 结构',
|
||||
customChildrenColumns: cascadeColumnsCustomKey['zh-CN'],
|
||||
textColumns: [
|
||||
'杭州',
|
||||
'宁波',
|
||||
'温州',
|
||||
'绍兴',
|
||||
'湖州',
|
||||
'嘉兴',
|
||||
'金华',
|
||||
'衢州',
|
||||
],
|
||||
disabledColumns: [
|
||||
{ text: '杭州', disabled: true },
|
||||
{ text: '宁波' },
|
||||
{ text: '温州' },
|
||||
],
|
||||
column3: {
|
||||
浙江: ['杭州', '宁波', '温州', '嘉兴', '湖州'],
|
||||
福建: ['福州', '厦门', '莆田', '三明', '泉州'],
|
||||
},
|
||||
toastContent: (value: string, index: number) =>
|
||||
`当前值:${value}, 当前索引:${index}`,
|
||||
customChildrenColumns: customKeyColumns['zh-CN'],
|
||||
toastContent: (value: string) => `当前值:${value}`,
|
||||
},
|
||||
'en-US': {
|
||||
city: 'City',
|
||||
cascade: 'Cascade',
|
||||
withPopup: 'With Popup',
|
||||
chooseCity: 'Choose City',
|
||||
showToolbar: 'Show Toolbar',
|
||||
dateColumns: dateColumns['en-US'],
|
||||
basicColumns: basicColumns['en-US'],
|
||||
defaultIndex: 'Default Index',
|
||||
disableOption: 'Disable Option',
|
||||
cascadeColumns: cascadeColumns['en-US'],
|
||||
disabledColumns: disabledColumns['en-US'],
|
||||
multipleColumns: 'Multiple Columns',
|
||||
setColumnValues: 'Set Column Values',
|
||||
customChildrenKey: 'Custom Columns Fields',
|
||||
customChildrenColumns: cascadeColumnsCustomKey['en-US'],
|
||||
textColumns: ['Delaware', 'Florida', 'Georqia', 'Indiana', 'Maine'],
|
||||
disabledColumns: [
|
||||
{ text: 'Delaware', disabled: true },
|
||||
{ text: 'Florida' },
|
||||
{ text: 'Georqia' },
|
||||
],
|
||||
column3: {
|
||||
Group1: ['Delaware', 'Florida', 'Georqia', 'Indiana', 'Maine'],
|
||||
Group2: ['Alabama', 'Kansas', 'Louisiana', 'Texas'],
|
||||
},
|
||||
customChildrenColumns: customKeyColumns['en-US'],
|
||||
toastContent: (value: string, index: number) =>
|
||||
`Value: ${value}, Index:${index}`,
|
||||
},
|
||||
});
|
||||
|
||||
const picker = ref();
|
||||
const showPicker = ref(false);
|
||||
const fieldValue = ref('');
|
||||
const customFieldName = ref({
|
||||
const customFieldName = {
|
||||
text: 'cityName',
|
||||
value: 'cityName',
|
||||
children: 'cities',
|
||||
});
|
||||
|
||||
const columns = computed(() => {
|
||||
const column = t('column3');
|
||||
return [
|
||||
{
|
||||
values: Object.keys(column),
|
||||
className: 'column1',
|
||||
},
|
||||
{
|
||||
values: column[Object.keys(column)[0]],
|
||||
className: 'column2',
|
||||
defaultIndex: 2,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
const onChange1 = (value: string, index: number) => {
|
||||
Toast(t('toastContent', value, index));
|
||||
};
|
||||
|
||||
const onChange2 = (values: string[]) => {
|
||||
picker.value.setColumnValues(1, t('column3')[values[0]]);
|
||||
const onChange1 = ({ selectedValues }: PickerChangeEventParams) => {
|
||||
Toast(t('toastContent', selectedValues.join(',')));
|
||||
};
|
||||
|
||||
const onConfirm = (value: string, index: number) => {
|
||||
Toast(t('toastContent', value, index));
|
||||
const onConfirm = ({ selectedValues }: PickerConfirmEventParams) => {
|
||||
Toast(t('toastContent', selectedValues.join(',')));
|
||||
};
|
||||
|
||||
const onCancel = () => Toast(t('cancel'));
|
||||
|
||||
const onCancel2 = () => {
|
||||
showPicker.value = false;
|
||||
};
|
||||
|
||||
const onClickField = () => {
|
||||
showPicker.value = true;
|
||||
};
|
||||
|
||||
const onConfirm2 = (value: string) => {
|
||||
showPicker.value = false;
|
||||
fieldValue.value = value;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<demo-block card :title="t('basicUsage')">
|
||||
<van-picker
|
||||
:title="t('title')"
|
||||
:columns="t('textColumns')"
|
||||
:columns="t('basicColumns')"
|
||||
@change="onChange1"
|
||||
@confirm="onConfirm"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
<demo-block card :title="t('defaultIndex')">
|
||||
<van-picker
|
||||
:title="t('title')"
|
||||
:columns="t('textColumns')"
|
||||
:default-index="2"
|
||||
@change="onChange1"
|
||||
/>
|
||||
</demo-block>
|
||||
<WithPopup />
|
||||
|
||||
<demo-block card :title="t('multipleColumns')">
|
||||
<van-picker
|
||||
@ -159,37 +92,10 @@ const onConfirm2 = (value: string) => {
|
||||
<van-picker :title="t('title')" :columns="t('disabledColumns')" />
|
||||
</demo-block>
|
||||
|
||||
<demo-block card :title="t('setColumnValues')">
|
||||
<van-picker
|
||||
ref="picker"
|
||||
:title="t('title')"
|
||||
:columns="columns"
|
||||
@change="onChange2"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
<demo-block card :title="t('loadingStatus')">
|
||||
<van-picker loading :title="t('title')" :columns="columns" />
|
||||
<van-picker loading :title="t('title')" />
|
||||
</demo-block>
|
||||
|
||||
<demo-block card :title="t('withPopup')">
|
||||
<van-field
|
||||
v-model="fieldValue"
|
||||
is-link
|
||||
readonly
|
||||
:label="t('city')"
|
||||
:placeholder="t('chooseCity')"
|
||||
@click="onClickField"
|
||||
/>
|
||||
<van-popup v-model:show="showPicker" round position="bottom">
|
||||
<van-picker
|
||||
:title="t('title')"
|
||||
:columns="t('textColumns')"
|
||||
@cancel="onCancel2"
|
||||
@confirm="onConfirm2"
|
||||
/>
|
||||
</van-popup>
|
||||
</demo-block>
|
||||
<demo-block card :title="t('customChildrenKey')">
|
||||
<van-picker
|
||||
:title="t('title')"
|
||||
|
@ -9,9 +9,10 @@ export type {
|
||||
PickerOption,
|
||||
PickerInstance,
|
||||
PickerFieldNames,
|
||||
PickerObjectColumn,
|
||||
PickerObjectOption,
|
||||
PickerToolbarPosition,
|
||||
PickerCancelEventParams,
|
||||
PickerChangeEventParams,
|
||||
PickerConfirmEventParams,
|
||||
} from './types';
|
||||
|
||||
declare module 'vue' {
|
||||
|
@ -84,86 +84,35 @@ exports[`should render demo and match snapshot 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="van-picker">
|
||||
<div class="van-picker__toolbar">
|
||||
<button type="button"
|
||||
class="van-picker__cancel van-haptics-feedback"
|
||||
<div class="van-cell van-cell--clickable van-field"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div class="van-cell__title van-field__label">
|
||||
<label id="van-field-label"
|
||||
for="van-field-input"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<div class="van-picker__title van-ellipsis">
|
||||
Title
|
||||
</div>
|
||||
<button type="button"
|
||||
class="van-picker__confirm van-haptics-feedback"
|
||||
>
|
||||
Confirm
|
||||
</button>
|
||||
City
|
||||
</label>
|
||||
</div>
|
||||
<div class="van-picker__columns"
|
||||
style="height: 264px;"
|
||||
>
|
||||
<div class="van-picker-column">
|
||||
<ul style="transform: translate3d(0, 22px, 0); transition-duration: 0ms; transition-property: none;"
|
||||
class="van-picker-column__wrapper"
|
||||
<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"
|
||||
>
|
||||
<li role="button"
|
||||
style="height: 44px;"
|
||||
tabindex="0"
|
||||
class="van-picker-column__item"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
Delaware
|
||||
</div>
|
||||
</li>
|
||||
<li role="button"
|
||||
style="height: 44px;"
|
||||
tabindex="0"
|
||||
class="van-picker-column__item"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
Florida
|
||||
</div>
|
||||
</li>
|
||||
<li role="button"
|
||||
style="height: 44px;"
|
||||
tabindex="0"
|
||||
class="van-picker-column__item van-picker-column__item--selected"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
Georqia
|
||||
</div>
|
||||
</li>
|
||||
<li role="button"
|
||||
style="height: 44px;"
|
||||
tabindex="0"
|
||||
class="van-picker-column__item"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
Indiana
|
||||
</div>
|
||||
</li>
|
||||
<li role="button"
|
||||
style="height: 44px;"
|
||||
tabindex="0"
|
||||
class="van-picker-column__item"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
Maine
|
||||
</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>
|
||||
<i class="van-badge__wrapper van-icon van-icon-arrow van-cell__right-icon">
|
||||
</i>
|
||||
</div>
|
||||
<transition-stub>
|
||||
</transition-stub>
|
||||
<transition-stub>
|
||||
</transition-stub>
|
||||
</div>
|
||||
<div>
|
||||
<div class="van-picker">
|
||||
@ -186,13 +135,13 @@ exports[`should render demo and match snapshot 1`] = `
|
||||
style="height: 264px;"
|
||||
>
|
||||
<div class="van-picker-column">
|
||||
<ul style="transform: translate3d(0, 22px, 0); transition-duration: 0ms; transition-property: none;"
|
||||
<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"
|
||||
class="van-picker-column__item van-picker-column__item--selected"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
Monday
|
||||
@ -210,7 +159,7 @@ exports[`should render demo and match snapshot 1`] = `
|
||||
<li role="button"
|
||||
style="height: 44px;"
|
||||
tabindex="0"
|
||||
class="van-picker-column__item van-picker-column__item--selected"
|
||||
class="van-picker-column__item"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
Wednesday
|
||||
@ -237,13 +186,13 @@ exports[`should render demo and match snapshot 1`] = `
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker-column">
|
||||
<ul style="transform: translate3d(0, 66px, 0); transition-duration: 0ms; transition-property: none;"
|
||||
<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"
|
||||
class="van-picker-column__item van-picker-column__item--selected"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
Morning
|
||||
@ -252,7 +201,7 @@ exports[`should render demo and match snapshot 1`] = `
|
||||
<li role="button"
|
||||
style="height: 44px;"
|
||||
tabindex="0"
|
||||
class="van-picker-column__item van-picker-column__item--selected"
|
||||
class="van-picker-column__item"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
Afternoon
|
||||
@ -447,112 +396,6 @@ 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">
|
||||
Title
|
||||
</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 column1">
|
||||
<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">
|
||||
Group1
|
||||
</div>
|
||||
</li>
|
||||
<li role="button"
|
||||
style="height: 44px;"
|
||||
tabindex="0"
|
||||
class="van-picker-column__item"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
Group2
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker-column column2">
|
||||
<ul style="transform: translate3d(0, 22px, 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"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
Delaware
|
||||
</div>
|
||||
</li>
|
||||
<li role="button"
|
||||
style="height: 44px;"
|
||||
tabindex="0"
|
||||
class="van-picker-column__item"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
Florida
|
||||
</div>
|
||||
</li>
|
||||
<li role="button"
|
||||
style="height: 44px;"
|
||||
tabindex="0"
|
||||
class="van-picker-column__item van-picker-column__item--selected"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
Georqia
|
||||
</div>
|
||||
</li>
|
||||
<li role="button"
|
||||
style="height: 44px;"
|
||||
tabindex="0"
|
||||
class="van-picker-column__item"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
Indiana
|
||||
</div>
|
||||
</li>
|
||||
<li role="button"
|
||||
style="height: 44px;"
|
||||
tabindex="0"
|
||||
class="van-picker-column__item"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
Maine
|
||||
</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>
|
||||
<div>
|
||||
<div class="van-picker">
|
||||
<div class="van-picker__toolbar">
|
||||
@ -587,123 +430,15 @@ exports[`should render demo and match snapshot 1`] = `
|
||||
<div class="van-picker__columns"
|
||||
style="height: 264px;"
|
||||
>
|
||||
<div class="van-picker-column column1">
|
||||
<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">
|
||||
Group1
|
||||
</div>
|
||||
</li>
|
||||
<li role="button"
|
||||
style="height: 44px;"
|
||||
tabindex="0"
|
||||
class="van-picker-column__item"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
Group2
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker-column column2">
|
||||
<ul style="transform: translate3d(0, 22px, 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"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
Delaware
|
||||
</div>
|
||||
</li>
|
||||
<li role="button"
|
||||
style="height: 44px;"
|
||||
tabindex="0"
|
||||
class="van-picker-column__item"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
Florida
|
||||
</div>
|
||||
</li>
|
||||
<li role="button"
|
||||
style="height: 44px;"
|
||||
tabindex="0"
|
||||
class="van-picker-column__item van-picker-column__item--selected"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
Georqia
|
||||
</div>
|
||||
</li>
|
||||
<li role="button"
|
||||
style="height: 44px;"
|
||||
tabindex="0"
|
||||
class="van-picker-column__item"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
Indiana
|
||||
</div>
|
||||
</li>
|
||||
<li role="button"
|
||||
style="height: 44px;"
|
||||
tabindex="0"
|
||||
class="van-picker-column__item"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
Maine
|
||||
</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>
|
||||
<div>
|
||||
<div class="van-cell van-cell--clickable van-field"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div class="van-cell__title van-field__label">
|
||||
<label id="van-field-label"
|
||||
for="van-field-input"
|
||||
>
|
||||
City
|
||||
</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">
|
||||
</i>
|
||||
</div>
|
||||
<transition-stub>
|
||||
</transition-stub>
|
||||
<transition-stub>
|
||||
</transition-stub>
|
||||
</div>
|
||||
<div>
|
||||
<div class="van-picker">
|
||||
<div class="van-picker__toolbar">
|
||||
|
@ -1,179 +1,6 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`column watch default index 1`] = `
|
||||
<div class="van-picker-column">
|
||||
<ul style="transform: translate3d(0, 50px, 0); transition-duration: 0ms; transition-property: none;"
|
||||
class="van-picker-column__wrapper"
|
||||
>
|
||||
<li role="button"
|
||||
style="height: 50px;"
|
||||
tabindex="-1"
|
||||
class="van-picker-column__item van-picker-column__item--disabled"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
1
|
||||
</div>
|
||||
</li>
|
||||
<li role="button"
|
||||
style="height: 50px;"
|
||||
tabindex="0"
|
||||
class="van-picker-column__item van-picker-column__item--selected"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
1990
|
||||
</div>
|
||||
</li>
|
||||
<li role="button"
|
||||
style="height: 50px;"
|
||||
tabindex="0"
|
||||
class="van-picker-column__item"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
1991
|
||||
</div>
|
||||
</li>
|
||||
<li role="button"
|
||||
style="height: 50px;"
|
||||
tabindex="0"
|
||||
class="van-picker-column__item"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
1992
|
||||
</div>
|
||||
</li>
|
||||
<li role="button"
|
||||
style="height: 50px;"
|
||||
tabindex="0"
|
||||
class="van-picker-column__item"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
1993
|
||||
</div>
|
||||
</li>
|
||||
<li role="button"
|
||||
style="height: 50px;"
|
||||
tabindex="0"
|
||||
class="van-picker-column__item"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
1994
|
||||
</div>
|
||||
</li>
|
||||
<li role="button"
|
||||
style="height: 50px;"
|
||||
tabindex="0"
|
||||
class="van-picker-column__item"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
1995
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`column watch default index 2`] = `
|
||||
<div class="van-picker-column">
|
||||
<ul style="transform: translate3d(0, 0px, 0); transition-duration: 0ms; transition-property: none;"
|
||||
class="van-picker-column__wrapper"
|
||||
>
|
||||
<li role="button"
|
||||
style="height: 50px;"
|
||||
tabindex="-1"
|
||||
class="van-picker-column__item van-picker-column__item--disabled"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
1
|
||||
</div>
|
||||
</li>
|
||||
<li role="button"
|
||||
style="height: 50px;"
|
||||
tabindex="0"
|
||||
class="van-picker-column__item"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
1990
|
||||
</div>
|
||||
</li>
|
||||
<li role="button"
|
||||
style="height: 50px;"
|
||||
tabindex="0"
|
||||
class="van-picker-column__item van-picker-column__item--selected"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
1991
|
||||
</div>
|
||||
</li>
|
||||
<li role="button"
|
||||
style="height: 50px;"
|
||||
tabindex="0"
|
||||
class="van-picker-column__item"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
1992
|
||||
</div>
|
||||
</li>
|
||||
<li role="button"
|
||||
style="height: 50px;"
|
||||
tabindex="0"
|
||||
class="van-picker-column__item"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
1993
|
||||
</div>
|
||||
</li>
|
||||
<li role="button"
|
||||
style="height: 50px;"
|
||||
tabindex="0"
|
||||
class="van-picker-column__item"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
1994
|
||||
</div>
|
||||
</li>
|
||||
<li role="button"
|
||||
style="height: 50px;"
|
||||
tabindex="0"
|
||||
class="van-picker-column__item"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
1995
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`columns-top、columns-bottom prop 1`] = `
|
||||
<div class="van-picker">
|
||||
<div class="van-picker__toolbar">
|
||||
<button type="button"
|
||||
class="van-picker__cancel van-haptics-feedback"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button type="button"
|
||||
class="van-picker__confirm van-haptics-feedback"
|
||||
>
|
||||
Confirm
|
||||
</button>
|
||||
</div>
|
||||
Custom Columns Top
|
||||
<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"
|
||||
>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
Custom Columns Bottom
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`not allow html 1`] = `
|
||||
exports[`should not allow to render html text 1`] = `
|
||||
<div class="van-picker">
|
||||
<div class="van-picker__toolbar">
|
||||
<button type="button"
|
||||
@ -217,235 +44,7 @@ exports[`not allow html 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`render option slot with object columns 1`] = `
|
||||
<div class="van-picker">
|
||||
<div class="van-picker__toolbar">
|
||||
<button type="button"
|
||||
class="van-picker__cancel van-haptics-feedback"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<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">
|
||||
foo
|
||||
</div>
|
||||
</li>
|
||||
<li role="button"
|
||||
style="height: 44px;"
|
||||
tabindex="0"
|
||||
class="van-picker-column__item"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
bar
|
||||
</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>
|
||||
`;
|
||||
|
||||
exports[`render option slot with simple columns 1`] = `
|
||||
<div class="van-picker">
|
||||
<div class="van-picker__toolbar">
|
||||
<button type="button"
|
||||
class="van-picker__cancel van-haptics-feedback"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<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"
|
||||
>
|
||||
foo
|
||||
</li>
|
||||
<li role="button"
|
||||
style="height: 44px;"
|
||||
tabindex="0"
|
||||
class="van-picker-column__item"
|
||||
>
|
||||
bar
|
||||
</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>
|
||||
`;
|
||||
|
||||
exports[`set rem item-height 1`] = `
|
||||
<div class="van-picker">
|
||||
<div class="van-picker__toolbar">
|
||||
<button type="button"
|
||||
class="van-picker__cancel van-haptics-feedback"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button type="button"
|
||||
class="van-picker__confirm van-haptics-feedback"
|
||||
>
|
||||
Confirm
|
||||
</button>
|
||||
</div>
|
||||
<div class="van-picker__columns"
|
||||
style="height: 960px;"
|
||||
>
|
||||
<div class="van-picker-column">
|
||||
<ul style="transform: translate3d(0, 400px, 0); transition-duration: 0ms; transition-property: none;"
|
||||
class="van-picker-column__wrapper"
|
||||
>
|
||||
<li role="button"
|
||||
style="height: 160px;"
|
||||
tabindex="0"
|
||||
class="van-picker-column__item van-picker-column__item--selected"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
1990
|
||||
</div>
|
||||
</li>
|
||||
<li role="button"
|
||||
style="height: 160px;"
|
||||
tabindex="0"
|
||||
class="van-picker-column__item"
|
||||
>
|
||||
<div class="van-ellipsis">
|
||||
1991
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker__mask"
|
||||
style="background-size: 100% 400px;"
|
||||
>
|
||||
</div>
|
||||
<div class="van-hairline-unset--top-bottom van-picker__frame"
|
||||
style="height: 160px;"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`should render confirm/cancel slot correctly 1`] = `
|
||||
<div class="van-picker">
|
||||
<div class="van-picker__toolbar">
|
||||
<button type="button"
|
||||
class="van-picker__cancel van-haptics-feedback"
|
||||
>
|
||||
Custom Cancel
|
||||
</button>
|
||||
<button type="button"
|
||||
class="van-picker__confirm van-haptics-feedback"
|
||||
>
|
||||
Custom 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"
|
||||
>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`should render title slot correctly 1`] = `
|
||||
<div class="van-picker">
|
||||
<div class="van-picker__toolbar">
|
||||
<button type="button"
|
||||
class="van-picker__cancel van-haptics-feedback"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
Custom title
|
||||
<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"
|
||||
>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`should render toolbar slot correctly 1`] = `
|
||||
<div class="van-picker">
|
||||
<div class="van-picker__toolbar">
|
||||
Custom toolbar
|
||||
</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"
|
||||
>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`toolbar-position prop 1`] = `
|
||||
exports[`should render bottom toolbar when toolbar-position is bottom 1`] = `
|
||||
<div class="van-picker">
|
||||
<div class="van-picker__columns"
|
||||
style="height: 264px;"
|
||||
|
152
packages/vant/src/picker/test/__snapshots__/slots.spec.ts.snap
Normal file
152
packages/vant/src/picker/test/__snapshots__/slots.spec.ts.snap
Normal file
@ -0,0 +1,152 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`should render columns-top、columns-bottom slots correctly 1`] = `
|
||||
<div class="van-picker">
|
||||
<div class="van-picker__toolbar">
|
||||
<button type="button"
|
||||
class="van-picker__cancel van-haptics-feedback"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button type="button"
|
||||
class="van-picker__confirm van-haptics-feedback"
|
||||
>
|
||||
Confirm
|
||||
</button>
|
||||
</div>
|
||||
Custom Columns Top
|
||||
<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"
|
||||
>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
Custom Columns Bottom
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`should render confirm/cancel slot correctly 1`] = `
|
||||
<div class="van-picker">
|
||||
<div class="van-picker__toolbar">
|
||||
<button type="button"
|
||||
class="van-picker__cancel van-haptics-feedback"
|
||||
>
|
||||
Custom Cancel
|
||||
</button>
|
||||
<button type="button"
|
||||
class="van-picker__confirm van-haptics-feedback"
|
||||
>
|
||||
Custom 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"
|
||||
>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`should render option slot correctly 1`] = `
|
||||
<div class="van-picker">
|
||||
<div class="van-picker__toolbar">
|
||||
<button type="button"
|
||||
class="van-picker__cancel van-haptics-feedback"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<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"
|
||||
>
|
||||
Custom 1990
|
||||
</li>
|
||||
<li role="button"
|
||||
style="height: 44px;"
|
||||
tabindex="0"
|
||||
class="van-picker-column__item"
|
||||
>
|
||||
Custom 1991
|
||||
</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>
|
||||
`;
|
||||
|
||||
exports[`should render title slot correctly 1`] = `
|
||||
<div class="van-picker">
|
||||
<div class="van-picker__toolbar">
|
||||
<button type="button"
|
||||
class="van-picker__cancel van-haptics-feedback"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
Custom title
|
||||
<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"
|
||||
>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`should render toolbar slot correctly 1`] = `
|
||||
<div class="van-picker">
|
||||
<div class="van-picker__toolbar">
|
||||
Custom toolbar
|
||||
</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"
|
||||
>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
@ -1,20 +1,16 @@
|
||||
import { later, mount, triggerDrag } from '../../../test';
|
||||
import { Picker } from '..';
|
||||
import PickerColumn from '../PickerColumn';
|
||||
|
||||
const simpleColumn = ['1990', '1991', '1992', '1993', '1994', '1995'];
|
||||
const columns = [
|
||||
{
|
||||
values: ['vip', 'normal'],
|
||||
className: 'column1',
|
||||
},
|
||||
{
|
||||
values: simpleColumn,
|
||||
className: 'column2',
|
||||
},
|
||||
const simpleColumn = [
|
||||
{ text: '1990', value: '1990' },
|
||||
{ text: '1991', value: '1991' },
|
||||
{ text: '1992', value: '1992' },
|
||||
{ text: '1993', value: '1993' },
|
||||
{ text: '1994', value: '1994' },
|
||||
{ text: '1995', value: '1995' },
|
||||
];
|
||||
|
||||
test('simple columns confirm & cancel event', () => {
|
||||
test('should emit confirm event after clicking the confirm button', () => {
|
||||
const wrapper = mount(Picker, {
|
||||
props: {
|
||||
showToolbar: true,
|
||||
@ -23,89 +19,32 @@ test('simple columns confirm & cancel event', () => {
|
||||
});
|
||||
|
||||
wrapper.find('.van-picker__confirm').trigger('click');
|
||||
wrapper.find('.van-picker__cancel').trigger('click');
|
||||
expect(wrapper.emitted('confirm')![0]).toEqual(['1990', 0]);
|
||||
expect(wrapper.emitted('cancel')![0]).toEqual(['1990', 0]);
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
test('multiple columns confirm & cancel event', () => {
|
||||
const wrapper = mount(Picker, {
|
||||
props: {
|
||||
showToolbar: true,
|
||||
columns,
|
||||
expect(wrapper.emitted('confirm')![0]).toEqual([
|
||||
{
|
||||
selectedOptions: [{ text: '1990', value: '1990' }],
|
||||
selectedValues: ['1990'],
|
||||
},
|
||||
});
|
||||
|
||||
wrapper.find('.van-picker__confirm').trigger('click');
|
||||
wrapper.find('.van-picker__cancel').trigger('click');
|
||||
|
||||
const params = [
|
||||
['vip', '1990'],
|
||||
[0, 0],
|
||||
];
|
||||
|
||||
expect(wrapper.emitted('confirm')![0]).toEqual(params);
|
||||
expect(wrapper.emitted('cancel')![0]).toEqual(params);
|
||||
});
|
||||
|
||||
test('set picker values', () => {
|
||||
const wrapper = mount(Picker, {
|
||||
props: {
|
||||
columns,
|
||||
},
|
||||
});
|
||||
const vm = wrapper.vm as Record<string, any>;
|
||||
|
||||
expect(vm.getColumnValues(-1)).toEqual(undefined);
|
||||
expect(vm.getColumnValues(1)).toHaveLength(6);
|
||||
expect(vm.getColumnValue(1)).toEqual('1990');
|
||||
|
||||
vm.setColumnValue(0, 'normal');
|
||||
expect(vm.getColumnValue(0)).toEqual('normal');
|
||||
|
||||
vm.setColumnIndex(0, 0);
|
||||
expect(vm.getColumnValue(0)).toEqual('vip');
|
||||
|
||||
vm.setColumnValue(1, '1991');
|
||||
expect(vm.getColumnValue(1)).toEqual('1991');
|
||||
|
||||
vm.setColumnValues(0, ['vip', 'normal', 'other']);
|
||||
expect(vm.getColumnValues(0)).toHaveLength(3);
|
||||
expect(vm.getValues()).toHaveLength(2);
|
||||
|
||||
vm.setColumnValues(-1, []);
|
||||
expect(vm.getValues()).toHaveLength(2);
|
||||
|
||||
vm.setValues(['vip', '1992']);
|
||||
expect(vm.getColumnIndex(1)).toEqual(2);
|
||||
expect(vm.getColumnIndex(2)).toEqual(undefined);
|
||||
expect(vm.getIndexes(2)).toEqual([0, 2]);
|
||||
|
||||
vm.setIndexes([1, 4]);
|
||||
expect(vm.getColumnValue(1)).toEqual('1994');
|
||||
expect(vm.getColumnValue(2)).toEqual(undefined);
|
||||
});
|
||||
|
||||
test('drag columns', () => {
|
||||
const wrapper = mount(Picker, {
|
||||
props: {
|
||||
columns,
|
||||
},
|
||||
});
|
||||
|
||||
triggerDrag(wrapper.find('.van-picker-column'), 0, -100);
|
||||
wrapper.find('.van-picker-column ul').trigger('transitionend');
|
||||
|
||||
// 由于在极短的时间(大约几毫秒)移动 `100px`,因此再计算惯性滚动的距离时,
|
||||
// 会得到一个很大的值,导致会滚动到且选中列表的最后一项
|
||||
expect(wrapper.emitted<[Array<string>, number]>('change')![0][0]).toEqual([
|
||||
'normal',
|
||||
'1990',
|
||||
]);
|
||||
});
|
||||
|
||||
test('drag simple columns', () => {
|
||||
test('should emit cancel event after clicking the cancel button', () => {
|
||||
const wrapper = mount(Picker, {
|
||||
props: {
|
||||
showToolbar: true,
|
||||
columns: simpleColumn,
|
||||
},
|
||||
});
|
||||
|
||||
wrapper.find('.van-picker__cancel').trigger('click');
|
||||
expect(wrapper.emitted('cancel')![0]).toEqual([
|
||||
{
|
||||
selectedOptions: [{ text: '1990', value: '1990' }],
|
||||
selectedValues: ['1990'],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('should emit change event after draging the column', () => {
|
||||
const wrapper = mount(Picker, {
|
||||
props: {
|
||||
columns: simpleColumn,
|
||||
@ -115,121 +54,21 @@ test('drag simple columns', () => {
|
||||
triggerDrag(wrapper.find('.van-picker-column'), 0, -100);
|
||||
wrapper.find('.van-picker-column ul').trigger('transitionend');
|
||||
|
||||
// 由于在极短的时间(大约几毫秒)移动 `100px`,因此再计算惯性滚动的距离时,
|
||||
// 会得到一个很大的值,导致会滚动到且选中列表的最后一项
|
||||
expect(wrapper.emitted<[string, number]>('change')![0][0]).toEqual('1995');
|
||||
expect(wrapper.emitted('change')).toEqual([
|
||||
[
|
||||
{
|
||||
columnIndex: 0,
|
||||
selectedOptions: [{ text: '1995', value: '1995' }],
|
||||
selectedValues: ['1995'],
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
test('column watch default index', async () => {
|
||||
const disabled = { disabled: true, text: 1 };
|
||||
const wrapper = mount(PickerColumn, {
|
||||
props: {
|
||||
initialOptions: [disabled, ...simpleColumn],
|
||||
textKey: 'text',
|
||||
itemHeight: 50,
|
||||
visibleItemCount: 5,
|
||||
swipeDuration: 1000,
|
||||
},
|
||||
} as any);
|
||||
|
||||
await later();
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
|
||||
await wrapper.setProps({
|
||||
defaultIndex: 2,
|
||||
});
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should render title slot correctly', () => {
|
||||
const wrapper = mount(Picker, {
|
||||
slots: {
|
||||
title: () => 'Custom title',
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should render toolbar slot correctly', () => {
|
||||
const wrapper = mount(Picker, {
|
||||
slots: {
|
||||
toolbar: () => 'Custom toolbar',
|
||||
},
|
||||
});
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should render confirm/cancel slot correctly', () => {
|
||||
const wrapper = mount(Picker, {
|
||||
slots: {
|
||||
confirm: () => 'Custom Confirm',
|
||||
cancel: () => 'Custom Cancel',
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('render option slot with simple columns', () => {
|
||||
const wrapper = mount(Picker, {
|
||||
props: {
|
||||
columns: ['foo', 'bar'],
|
||||
showToolbar: true,
|
||||
},
|
||||
slots: {
|
||||
option: (item) => item,
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('render option slot with object columns', () => {
|
||||
const wrapper = mount(Picker, {
|
||||
props: {
|
||||
columns: [{ text: 'foo' }, { text: 'bar' }],
|
||||
showToolbar: true,
|
||||
},
|
||||
slots: {
|
||||
options: (item) => item,
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('simulation finger swipe again before transitionend', () => {
|
||||
// mock getComputedStyle
|
||||
// see: https://github.com/jsdom/jsdom/issues/2588
|
||||
const originGetComputedStyle = window.getComputedStyle;
|
||||
window.getComputedStyle = (ele) => {
|
||||
const style = originGetComputedStyle(ele);
|
||||
|
||||
return {
|
||||
...style,
|
||||
transform: 'matrix(1, 0, 0, 1, 0, -5)',
|
||||
};
|
||||
};
|
||||
|
||||
const wrapper = mount(Picker, {
|
||||
props: {
|
||||
columns: simpleColumn,
|
||||
},
|
||||
});
|
||||
|
||||
triggerDrag(wrapper.find('.van-picker-column'), 0, -5);
|
||||
triggerDrag(wrapper.find('.van-picker-column'), -5, -100);
|
||||
wrapper.find('.van-picker-column ul').trigger('transitionend');
|
||||
expect(wrapper.emitted<[string, number]>('change')![0][0]).toEqual('1995');
|
||||
});
|
||||
|
||||
test('click column item', () => {
|
||||
test('should emit change event when after clicking a option', async () => {
|
||||
const columns = [
|
||||
{ text: '杭州' },
|
||||
{ text: '宁波' },
|
||||
{ text: '温州', disabled: true },
|
||||
{ text: '嘉兴', disabled: true },
|
||||
{ text: 'A', value: 'A' },
|
||||
{ text: 'B', value: 'B' },
|
||||
];
|
||||
const wrapper = mount(Picker, {
|
||||
props: {
|
||||
@ -237,13 +76,50 @@ test('click column item', () => {
|
||||
},
|
||||
});
|
||||
|
||||
wrapper.findAll('.van-picker-column__item')[3].trigger('click');
|
||||
expect(wrapper.emitted<[string, number]>('change')![0][0]).toEqual(
|
||||
columns[1]
|
||||
);
|
||||
await wrapper.findAll('.van-picker-column__item')[1].trigger('click');
|
||||
expect(wrapper.emitted('change')).toEqual([
|
||||
[
|
||||
{
|
||||
columnIndex: 0,
|
||||
selectedOptions: [{ text: 'B', value: 'B' }],
|
||||
selectedValues: ['B'],
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
test('toolbar-position prop', () => {
|
||||
test('should not emit change event if modelValue is not changed', async () => {
|
||||
const columns = [
|
||||
{ text: 'A', value: 'A' },
|
||||
{ text: 'B', value: 'B' },
|
||||
];
|
||||
const wrapper = mount(Picker, {
|
||||
props: {
|
||||
modelValue: ['B'],
|
||||
columns,
|
||||
},
|
||||
});
|
||||
|
||||
await wrapper.findAll('.van-picker-column__item')[1].trigger('click');
|
||||
expect(wrapper.emitted('change')).toBeFalsy();
|
||||
});
|
||||
|
||||
test('should not emit change event when after clicking a disabled option', async () => {
|
||||
const columns = [
|
||||
{ text: 'A', value: 'A' },
|
||||
{ text: 'B', value: 'B', disabled: true },
|
||||
];
|
||||
const wrapper = mount(Picker, {
|
||||
props: {
|
||||
columns,
|
||||
},
|
||||
});
|
||||
|
||||
await wrapper.findAll('.van-picker-column__item')[1].trigger('click');
|
||||
expect(wrapper.emitted<[string, number]>('change')).toBeFalsy();
|
||||
});
|
||||
|
||||
test('should render bottom toolbar when toolbar-position is bottom', () => {
|
||||
const wrapper = mount(Picker, {
|
||||
props: {
|
||||
showToolbar: true,
|
||||
@ -254,66 +130,67 @@ test('toolbar-position prop', () => {
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('not allow html', () => {
|
||||
test('should not allow to render html text', () => {
|
||||
const wrapper = mount(Picker, {
|
||||
props: {
|
||||
allowHtml: false,
|
||||
columns: ['<div>option</div>'],
|
||||
columns: [{ text: '<div>option</div>' }],
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('columns-top、columns-bottom prop', () => {
|
||||
test('should allow to update columns props dynamically', async () => {
|
||||
const wrapper = mount(Picker, {
|
||||
props: {
|
||||
showToolbar: true,
|
||||
},
|
||||
slots: {
|
||||
'columns-top': () => 'Custom Columns Top',
|
||||
'columns-bottom': () => 'Custom Columns Bottom',
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('watch columns change', async () => {
|
||||
const wrapper = mount(Picker, {
|
||||
props: {
|
||||
showToolbar: true,
|
||||
columns: ['1', '2'],
|
||||
defaultIndex: 1,
|
||||
modelValue: ['2'],
|
||||
columns: [
|
||||
{ text: '1', value: '1' },
|
||||
{ text: '2', value: '2' },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
await wrapper.setProps({
|
||||
columns: ['2', '3'],
|
||||
columns: [
|
||||
{ text: '2', value: '2' },
|
||||
{ text: '3', value: '3' },
|
||||
],
|
||||
});
|
||||
|
||||
wrapper.find('.van-picker__confirm').trigger('click');
|
||||
expect(wrapper.emitted<[string, number]>('confirm')![0]).toEqual(['3', 1]);
|
||||
expect(wrapper.emitted<[string, number]>('confirm')![0]).toEqual([
|
||||
{ selectedOptions: [{ text: '2', value: '2' }], selectedValues: ['2'] },
|
||||
]);
|
||||
});
|
||||
|
||||
test('should not reset index when columns unchanged', async () => {
|
||||
const wrapper = mount(Picker, {
|
||||
props: {
|
||||
modelValue: ['2'],
|
||||
showToolbar: true,
|
||||
columns: ['1', '2'],
|
||||
columns: [
|
||||
{ text: '1', value: '1' },
|
||||
{ text: '2', value: '2' },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
(wrapper.vm as Record<string, any>).setIndexes([1]);
|
||||
await wrapper.setProps({
|
||||
columns: ['1', '2'],
|
||||
columns: [
|
||||
{ text: '1', value: '1' },
|
||||
{ text: '2', value: '2' },
|
||||
],
|
||||
});
|
||||
|
||||
wrapper.find('.van-picker__confirm').trigger('click');
|
||||
expect(wrapper.emitted<[string, number]>('confirm')![0]).toEqual(['2', 1]);
|
||||
await wrapper.find('.van-picker__confirm').trigger('click');
|
||||
expect(wrapper.emitted<[string, number]>('confirm')![0]).toEqual([
|
||||
{ selectedOptions: [{ text: '2', value: '2' }], selectedValues: ['2'] },
|
||||
]);
|
||||
});
|
||||
|
||||
test('set rem item-height', async () => {
|
||||
test('should allow to set rem option height', async () => {
|
||||
const originGetComputedStyle = window.getComputedStyle;
|
||||
|
||||
window.getComputedStyle = () => ({ fontSize: '16px' } as CSSStyleDeclaration);
|
||||
@ -321,27 +198,29 @@ test('set rem item-height', async () => {
|
||||
const wrapper = mount(Picker, {
|
||||
props: {
|
||||
columns: simpleColumn.slice(0, 2),
|
||||
itemHeight: '10rem',
|
||||
optionHeight: '10rem',
|
||||
},
|
||||
});
|
||||
|
||||
await later();
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
expect(wrapper.find('.van-picker-column__item').style.height).toEqual(
|
||||
'160px'
|
||||
);
|
||||
|
||||
window.getComputedStyle = originGetComputedStyle;
|
||||
});
|
||||
|
||||
test('readonly prop', () => {
|
||||
test('should not allow to change option when using readonly prop', async () => {
|
||||
const wrapper = mount(Picker, {
|
||||
props: {
|
||||
columns,
|
||||
columns: simpleColumn,
|
||||
readonly: true,
|
||||
},
|
||||
});
|
||||
|
||||
triggerDrag(wrapper.find('.van-picker-column'), 0, -100);
|
||||
wrapper.find('.van-picker-column ul').trigger('transitionend');
|
||||
wrapper.findAll('.van-picker-column__item')[3].trigger('click');
|
||||
await wrapper.find('.van-picker-column ul').trigger('transitionend');
|
||||
await wrapper.findAll('.van-picker-column__item')[3].trigger('click');
|
||||
|
||||
expect(wrapper.emitted('change')).toBeFalsy();
|
||||
});
|
||||
@ -349,7 +228,7 @@ test('readonly prop', () => {
|
||||
test('should not render mask and frame when options is empty', async () => {
|
||||
const wrapper = mount(Picker, {
|
||||
props: {
|
||||
columns: [{ values: [] }],
|
||||
columns: [[], []],
|
||||
},
|
||||
});
|
||||
expect(wrapper.find('.van-picker__mask').exists()).toBeFalsy();
|
||||
|
64
packages/vant/src/picker/test/slots.spec.ts
Normal file
64
packages/vant/src/picker/test/slots.spec.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import { mount } from '../../../test';
|
||||
import { Picker, PickerOption } from '..';
|
||||
|
||||
const simpleColumn = [
|
||||
{ text: '1990', value: '1990' },
|
||||
{ text: '1991', value: '1991' },
|
||||
];
|
||||
|
||||
test('should render title slot correctly', () => {
|
||||
const wrapper = mount(Picker, {
|
||||
slots: {
|
||||
title: () => 'Custom title',
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should render toolbar slot correctly', () => {
|
||||
const wrapper = mount(Picker, {
|
||||
slots: {
|
||||
toolbar: () => 'Custom toolbar',
|
||||
},
|
||||
});
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should render confirm/cancel slot correctly', () => {
|
||||
const wrapper = mount(Picker, {
|
||||
slots: {
|
||||
confirm: () => 'Custom Confirm',
|
||||
cancel: () => 'Custom Cancel',
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should render option slot correctly', () => {
|
||||
const wrapper = mount(Picker, {
|
||||
props: {
|
||||
columns: simpleColumn,
|
||||
},
|
||||
slots: {
|
||||
option: (option: PickerOption) => `Custom ${option.text}`,
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should render columns-top、columns-bottom slots correctly', () => {
|
||||
const wrapper = mount(Picker, {
|
||||
props: {
|
||||
showToolbar: true,
|
||||
},
|
||||
slots: {
|
||||
'columns-top': () => 'Custom Columns Top',
|
||||
'columns-bottom': () => 'Custom Columns Bottom',
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
@ -6,42 +6,24 @@ export type PickerToolbarPosition = 'top' | 'bottom';
|
||||
|
||||
export type PickerFieldNames = {
|
||||
text?: string;
|
||||
values?: string;
|
||||
value?: string;
|
||||
children?: string;
|
||||
};
|
||||
|
||||
export type PickerObjectOption = {
|
||||
export type PickerOption = {
|
||||
text?: string | number;
|
||||
value?: string | number;
|
||||
disabled?: boolean;
|
||||
// for custom filed names
|
||||
[key: PropertyKey]: any;
|
||||
};
|
||||
|
||||
export type PickerOption = string | number | PickerObjectOption;
|
||||
|
||||
export type PickerObjectColumn = {
|
||||
values?: PickerOption[];
|
||||
children?: PickerColumn;
|
||||
className?: unknown;
|
||||
defaultIndex?: number;
|
||||
// for custom filed names
|
||||
[key: PropertyKey]: any;
|
||||
};
|
||||
|
||||
export type PickerColumn = PickerOption[] | PickerObjectColumn;
|
||||
export type PickerColumn = PickerOption[];
|
||||
|
||||
export type PickerExpose = {
|
||||
confirm: () => void;
|
||||
getValues: <T = PickerOption>() => T[];
|
||||
setValues: (values: string[]) => void;
|
||||
getIndexes: () => number[];
|
||||
setIndexes: (indexes: number[]) => void;
|
||||
getColumnIndex: (index: number) => number;
|
||||
setColumnIndex: (columnIndex: number, optionIndex: number) => void;
|
||||
getColumnValue: <T = PickerOption>(index: number) => T;
|
||||
setColumnValue: (index: number, value: string) => void;
|
||||
getColumnValues: <T = PickerOption>(index: number) => T[];
|
||||
setColumnValues: (index: number, options: PickerOption[]) => void;
|
||||
};
|
||||
|
||||
export type PickerColumnProvide = {
|
||||
@ -59,3 +41,14 @@ export type PickerColumnProvide = {
|
||||
};
|
||||
|
||||
export type PickerInstance = ComponentPublicInstance<PickerProps, PickerExpose>;
|
||||
|
||||
export type PickerConfirmEventParams = {
|
||||
selectedValues: Array<number | string>;
|
||||
selectedOptions: PickerOption[];
|
||||
};
|
||||
|
||||
export type PickerCancelEventParams = PickerConfirmEventParams;
|
||||
|
||||
export type PickerChangeEventParams = PickerConfirmEventParams & {
|
||||
columnIndex: number;
|
||||
};
|
||||
|
110
packages/vant/src/picker/utils.ts
Normal file
110
packages/vant/src/picker/utils.ts
Normal file
@ -0,0 +1,110 @@
|
||||
import { isDef, clamp, extend } from '../utils';
|
||||
import type { Ref } from 'vue';
|
||||
import type { PickerOption, PickerColumn, PickerFieldNames } from './types';
|
||||
|
||||
export function getFirstEnabledOption(options: PickerOption[]) {
|
||||
return options.find((option) => !option.disabled) || options[0];
|
||||
}
|
||||
|
||||
export function getColumnsType(
|
||||
columns: PickerColumn | PickerColumn[],
|
||||
fields: Required<PickerFieldNames>
|
||||
) {
|
||||
const firstColumn = columns[0];
|
||||
if (firstColumn) {
|
||||
if (Array.isArray(firstColumn)) {
|
||||
return 'multiple';
|
||||
}
|
||||
if (fields.children in firstColumn) {
|
||||
return 'cascade';
|
||||
}
|
||||
}
|
||||
return 'default';
|
||||
}
|
||||
|
||||
export function findIndexOfEnabledOption(
|
||||
options: PickerOption[],
|
||||
index: number
|
||||
) {
|
||||
index = clamp(index, 0, options.length);
|
||||
|
||||
for (let i = index; i < options.length; i++) {
|
||||
if (!options[i].disabled) return i;
|
||||
}
|
||||
for (let i = index - 1; i >= 0; i--) {
|
||||
if (!options[i].disabled) return i;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function findOptionByValue(
|
||||
options: PickerOption[],
|
||||
value: number | string,
|
||||
fields: Required<PickerFieldNames>
|
||||
) {
|
||||
const index = options.findIndex((option) => option[fields.value] === value);
|
||||
const enabledIndex = findIndexOfEnabledOption(options, index);
|
||||
return options[enabledIndex];
|
||||
}
|
||||
|
||||
export function formatCascadeColumns(
|
||||
columns: PickerColumn | PickerColumn[],
|
||||
fields: Required<PickerFieldNames>,
|
||||
selectedValues: Ref<Array<number | string>>
|
||||
) {
|
||||
const formatted: PickerColumn[] = [];
|
||||
|
||||
let cursor: PickerOption | undefined = {
|
||||
[fields.children]: columns,
|
||||
};
|
||||
let columnIndex = 0;
|
||||
|
||||
while (cursor && cursor[fields.children]) {
|
||||
const options: PickerOption[] = cursor[fields.children];
|
||||
const value = selectedValues.value[columnIndex];
|
||||
|
||||
cursor = isDef(value)
|
||||
? findOptionByValue(options, value, fields)
|
||||
: undefined;
|
||||
|
||||
if (!cursor && options.length) {
|
||||
const firstValue = getFirstEnabledOption(options)[fields.value];
|
||||
cursor = findOptionByValue(options, firstValue, fields);
|
||||
}
|
||||
|
||||
columnIndex++;
|
||||
formatted.push(options);
|
||||
}
|
||||
|
||||
return formatted;
|
||||
}
|
||||
|
||||
export function getElementTranslateY(element: Element) {
|
||||
const { transform } = window.getComputedStyle(element);
|
||||
const translateY = transform.slice(7, transform.length - 1).split(', ')[5];
|
||||
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> {
|
||||
return extend(
|
||||
{
|
||||
text: 'text',
|
||||
value: 'value',
|
||||
children: 'children',
|
||||
},
|
||||
fields
|
||||
);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user