mirror of
https://gitee.com/vant-contrib/vant.git
synced 2025-05-22 14:39:16 +08:00
types(Picker): use tsx (#8137)
This commit is contained in:
parent
76aaba8089
commit
7355b610e1
@ -60,7 +60,7 @@
|
||||
"@vant/icons": "^1.5.2",
|
||||
"@vant/lazyload": "^1.0.2",
|
||||
"@vant/popperjs": "^1.0.2",
|
||||
"@vant/use": "^1.0.3"
|
||||
"@vant/use": "^1.0.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.0.0"
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { reactive, ref, watch } from 'vue';
|
||||
/* eslint-disable no-use-before-define */
|
||||
import { ref, watch, reactive, PropType } from 'vue';
|
||||
import { PICKER_KEY } from './shared';
|
||||
|
||||
// Utils
|
||||
@ -20,7 +21,7 @@ const MOMENTUM_LIMIT_DISTANCE = 15;
|
||||
|
||||
const [createComponent, bem] = createNamespace('picker-column');
|
||||
|
||||
function getElementTranslateY(element) {
|
||||
function getElementTranslateY(element: Element) {
|
||||
const style = window.getComputedStyle(element);
|
||||
const transform = style.transform || style.webkitTransform;
|
||||
const translateY = transform.slice(7, transform.length - 1).split(', ')[5];
|
||||
@ -28,22 +29,59 @@ function getElementTranslateY(element) {
|
||||
return Number(translateY);
|
||||
}
|
||||
|
||||
function isOptionDisabled(option) {
|
||||
export type PickerOption =
|
||||
| string
|
||||
| {
|
||||
text: string;
|
||||
disabled?: boolean;
|
||||
// for custom filed names
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
export type PickerStringColumn = string[];
|
||||
|
||||
export type PickerObjectColumn = {
|
||||
values?: PickerOption[];
|
||||
children?: PickerColumn;
|
||||
className?: any;
|
||||
defaultIndex?: number;
|
||||
// for custom filed names
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
export type PickerColumn = PickerStringColumn | PickerObjectColumn;
|
||||
|
||||
function isOptionDisabled(option: PickerOption) {
|
||||
return isObject(option) && option.disabled;
|
||||
}
|
||||
|
||||
export default createComponent({
|
||||
props: {
|
||||
textKey: String,
|
||||
readonly: Boolean,
|
||||
allowHtml: Boolean,
|
||||
className: String,
|
||||
itemHeight: Number,
|
||||
defaultIndex: Number,
|
||||
swipeDuration: [Number, String],
|
||||
visibleItemCount: [Number, String],
|
||||
textKey: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
itemHeight: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
swipeDuration: {
|
||||
type: [Number, String],
|
||||
required: true,
|
||||
},
|
||||
visibleItemCount: {
|
||||
type: [Number, String],
|
||||
required: true,
|
||||
},
|
||||
defaultIndex: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
initialOptions: {
|
||||
type: Array,
|
||||
type: Array as PropType<PickerOption[]>,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
@ -51,13 +89,13 @@ export default createComponent({
|
||||
emits: ['change'],
|
||||
|
||||
setup(props, { emit, slots }) {
|
||||
let moving;
|
||||
let startOffset;
|
||||
let touchStartTime;
|
||||
let momentumOffset;
|
||||
let transitionEndTrigger;
|
||||
let moving: boolean;
|
||||
let startOffset: number;
|
||||
let touchStartTime: number;
|
||||
let momentumOffset: number;
|
||||
let transitionEndTrigger: null | (() => void);
|
||||
|
||||
const wrapper = ref();
|
||||
const wrapper = ref<HTMLElement>();
|
||||
|
||||
const state = reactive({
|
||||
index: props.defaultIndex,
|
||||
@ -71,9 +109,9 @@ export default createComponent({
|
||||
const count = () => state.options.length;
|
||||
|
||||
const baseOffset = () =>
|
||||
(props.itemHeight * (props.visibleItemCount - 1)) / 2;
|
||||
(props.itemHeight * (+props.visibleItemCount - 1)) / 2;
|
||||
|
||||
const adjustIndex = (index) => {
|
||||
const adjustIndex = (index: number) => {
|
||||
index = range(index, 0, count());
|
||||
|
||||
for (let i = index; i < count(); i++) {
|
||||
@ -84,7 +122,7 @@ export default createComponent({
|
||||
}
|
||||
};
|
||||
|
||||
const setIndex = (index, emitChange) => {
|
||||
const setIndex = (index: number, emitChange?: boolean) => {
|
||||
index = adjustIndex(index) || 0;
|
||||
|
||||
const offset = -index * props.itemHeight;
|
||||
@ -108,14 +146,14 @@ export default createComponent({
|
||||
state.offset = offset;
|
||||
};
|
||||
|
||||
const setOptions = (options) => {
|
||||
const setOptions = (options: PickerOption[]) => {
|
||||
if (JSON.stringify(options) !== JSON.stringify(state.options)) {
|
||||
state.options = deepClone(options);
|
||||
setIndex(props.defaultIndex);
|
||||
}
|
||||
};
|
||||
|
||||
const onClickItem = (index) => {
|
||||
const onClickItem = (index: number) => {
|
||||
if (moving || props.readonly) {
|
||||
return;
|
||||
}
|
||||
@ -125,17 +163,17 @@ export default createComponent({
|
||||
setIndex(index, true);
|
||||
};
|
||||
|
||||
const getOptionText = (option) => {
|
||||
const getOptionText = (option: PickerOption) => {
|
||||
if (isObject(option) && props.textKey in option) {
|
||||
return option[props.textKey];
|
||||
}
|
||||
return option;
|
||||
};
|
||||
|
||||
const getIndexByOffset = (offset) =>
|
||||
const getIndexByOffset = (offset: number) =>
|
||||
range(Math.round(-offset / props.itemHeight), 0, count() - 1);
|
||||
|
||||
const momentum = (distance, duration) => {
|
||||
const momentum = (distance: number, duration: number) => {
|
||||
const speed = Math.abs(distance / duration);
|
||||
|
||||
distance = state.offset + (speed / 0.003) * (distance < 0 ? -1 : 1);
|
||||
@ -156,7 +194,7 @@ export default createComponent({
|
||||
}
|
||||
};
|
||||
|
||||
const onTouchStart = (event) => {
|
||||
const onTouchStart = (event: TouchEvent) => {
|
||||
if (props.readonly) {
|
||||
return;
|
||||
}
|
||||
@ -164,7 +202,7 @@ export default createComponent({
|
||||
touch.start(event);
|
||||
|
||||
if (moving) {
|
||||
const translateY = getElementTranslateY(wrapper.value);
|
||||
const translateY = getElementTranslateY(wrapper.value!);
|
||||
state.offset = Math.min(0, translateY - baseOffset());
|
||||
startOffset = state.offset;
|
||||
} else {
|
||||
@ -177,7 +215,7 @@ export default createComponent({
|
||||
transitionEndTrigger = null;
|
||||
};
|
||||
|
||||
const onTouchMove = (event) => {
|
||||
const onTouchMove = (event: TouchEvent) => {
|
||||
if (props.readonly) {
|
||||
return;
|
||||
}
|
||||
@ -234,7 +272,7 @@ export default createComponent({
|
||||
height: `${props.itemHeight}px`,
|
||||
};
|
||||
|
||||
return state.options.map((option, index) => {
|
||||
return state.options.map((option, index: number) => {
|
||||
const text = getOptionText(option);
|
||||
const disabled = isOptionDisabled(option);
|
||||
|
||||
@ -264,7 +302,7 @@ export default createComponent({
|
||||
});
|
||||
};
|
||||
|
||||
const setValue = (value) => {
|
||||
const setValue = (value: string) => {
|
||||
const { options } = state;
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
if (getOptionText(options[i]) === value) {
|
@ -343,12 +343,12 @@ export default {
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| --- | --- | --- | --- |
|
||||
| columns | 对象数组,配置每一列显示的数据 | _Column[]_ | `[]` |
|
||||
| columns-field-names | 自定义`columns`结构中的字段 | _object_ | `{ text: 'text', values: 'values', children: 'children' }` |
|
||||
| columns-field-names | 自定义 `columns` 结构中的字段 | _object_ | `{ text: 'text', values: 'values', children: 'children' }` |
|
||||
| title | 顶部栏标题 | _string_ | - |
|
||||
| confirm-button-text | 确认按钮文字 | _string_ | `确认` |
|
||||
| cancel-button-text | 取消按钮文字 | _string_ | `取消` |
|
||||
| value-key | 选项对象中,选项文字对应的键名 | _string_ | `text` |
|
||||
| toolbar-position | 顶部栏位置,可选值为`bottom` | _string_ | `top` |
|
||||
| toolbar-position | 顶部栏位置,可选值为 `bottom` | _string_ | `top` |
|
||||
| loading | 是否显示加载状态 | _boolean_ | `false` |
|
||||
| show-toolbar | 是否显示顶部栏 | _boolean_ | `true` |
|
||||
| allow-html | 是否允许选项内容中渲染 HTML | _boolean_ | `false` |
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ref, watch, computed } from 'vue';
|
||||
import { ref, watch, computed, PropType, ComponentPublicInstance } from 'vue';
|
||||
import { pickerProps, PICKER_KEY } from './shared';
|
||||
|
||||
// Utils
|
||||
@ -11,16 +11,28 @@ import { useExpose } from '../composables/use-expose';
|
||||
|
||||
// Components
|
||||
import Loading from '../loading';
|
||||
import PickerColumn from './PickerColumn';
|
||||
import Column, {
|
||||
PickerOption,
|
||||
PickerColumn,
|
||||
PickerObjectColumn,
|
||||
} from './PickerColumn';
|
||||
|
||||
const [createComponent, bem, t] = createNamespace('picker');
|
||||
|
||||
export type PickerToolbarPosition = 'top' | 'bottom';
|
||||
|
||||
export type PickerFieldNames = {
|
||||
text?: string;
|
||||
values?: string;
|
||||
children?: string;
|
||||
};
|
||||
|
||||
export default createComponent({
|
||||
props: {
|
||||
...pickerProps,
|
||||
columnsFieldNames: Object,
|
||||
columnsFieldNames: Object as PropType<PickerFieldNames>,
|
||||
columns: {
|
||||
type: Array,
|
||||
type: Array as PropType<PickerColumn[]>,
|
||||
default: () => [],
|
||||
},
|
||||
defaultIndex: {
|
||||
@ -28,7 +40,7 @@ export default createComponent({
|
||||
default: 0,
|
||||
},
|
||||
toolbarPosition: {
|
||||
type: String,
|
||||
type: String as PropType<PickerToolbarPosition>,
|
||||
default: 'top',
|
||||
},
|
||||
// @deprecated
|
||||
@ -42,7 +54,7 @@ export default createComponent({
|
||||
emits: ['confirm', 'cancel', 'change'],
|
||||
|
||||
setup(props, { emit, slots }) {
|
||||
const formattedColumns = ref([]);
|
||||
const formattedColumns = ref<PickerObjectColumn[]>([]);
|
||||
|
||||
const { text: textKey, values: valuesKey, children: childrenKey } = {
|
||||
// compatible with valueKey prop
|
||||
@ -52,29 +64,32 @@ export default createComponent({
|
||||
...props.columnsFieldNames,
|
||||
};
|
||||
|
||||
const { children, linkChildren } = useChildren(PICKER_KEY);
|
||||
const { children, linkChildren } = useChildren<
|
||||
// eslint-disable-next-line
|
||||
ComponentPublicInstance<{}, any>
|
||||
>(PICKER_KEY);
|
||||
|
||||
linkChildren();
|
||||
|
||||
const itemHeight = computed(() => unitToPx(props.itemHeight));
|
||||
|
||||
const dataType = computed(() => {
|
||||
const { columns } = props;
|
||||
const firstColumn = columns[0] || {};
|
||||
|
||||
if (firstColumn[childrenKey]) {
|
||||
const firstColumn = props.columns[0];
|
||||
if (typeof firstColumn === 'string') {
|
||||
return 'text';
|
||||
}
|
||||
if (childrenKey in firstColumn) {
|
||||
return 'cascade';
|
||||
}
|
||||
if (firstColumn[valuesKey]) {
|
||||
return 'object';
|
||||
}
|
||||
return 'text';
|
||||
return 'object';
|
||||
});
|
||||
|
||||
const formatCascade = () => {
|
||||
const formatted = [];
|
||||
const formatted: PickerObjectColumn[] = [];
|
||||
|
||||
let cursor = { [childrenKey]: props.columns };
|
||||
let cursor: PickerObjectColumn = {
|
||||
[childrenKey]: props.columns,
|
||||
};
|
||||
|
||||
while (cursor && cursor[childrenKey]) {
|
||||
const children = cursor[childrenKey];
|
||||
@ -109,7 +124,7 @@ export default createComponent({
|
||||
} else if (dataType.value === 'cascade') {
|
||||
formatCascade();
|
||||
} else {
|
||||
formattedColumns.value = columns;
|
||||
formattedColumns.value = columns as PickerObjectColumn[];
|
||||
}
|
||||
};
|
||||
|
||||
@ -117,15 +132,17 @@ export default createComponent({
|
||||
const getIndexes = () => children.map((child) => child.state.index);
|
||||
|
||||
// set options of column by index
|
||||
const setColumnValues = (index, options) => {
|
||||
const setColumnValues = (index: number, options: PickerOption[]) => {
|
||||
const column = children[index];
|
||||
if (column) {
|
||||
column.setOptions(options);
|
||||
}
|
||||
};
|
||||
|
||||
const onCascadeChange = (columnIndex) => {
|
||||
let cursor = { [childrenKey]: props.columns };
|
||||
const onCascadeChange = (columnIndex: number) => {
|
||||
let cursor: PickerObjectColumn = {
|
||||
[childrenKey]: props.columns,
|
||||
};
|
||||
const indexes = getIndexes();
|
||||
|
||||
for (let i = 0; i <= columnIndex; i++) {
|
||||
@ -140,21 +157,21 @@ export default createComponent({
|
||||
};
|
||||
|
||||
// get column instance by index
|
||||
const getColumn = (index) => children[index];
|
||||
const getChild = (index: number) => children[index];
|
||||
|
||||
// get column value by index
|
||||
const getColumnValue = (index) => {
|
||||
const column = getColumn(index);
|
||||
return column && column.getValue();
|
||||
const getColumnValue = (index: number) => {
|
||||
const column = getChild(index);
|
||||
if (column) {
|
||||
return column.getValue();
|
||||
}
|
||||
};
|
||||
|
||||
// set column value by index
|
||||
const setColumnValue = (index, value) => {
|
||||
const column = getColumn(index);
|
||||
|
||||
const setColumnValue = (index: number, value: string) => {
|
||||
const column = getChild(index);
|
||||
if (column) {
|
||||
column.setValue(value);
|
||||
|
||||
if (dataType.value === 'cascade') {
|
||||
onCascadeChange(index);
|
||||
}
|
||||
@ -162,41 +179,50 @@ export default createComponent({
|
||||
};
|
||||
|
||||
// get column option index by column index
|
||||
const getColumnIndex = (index) => (getColumn(index) || {}).state.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, optionIndex) => {
|
||||
const column = getColumn(columnIndex);
|
||||
|
||||
const setColumnIndex = (columnIndex: number, optionIndex: number) => {
|
||||
const column = getChild(columnIndex);
|
||||
if (column) {
|
||||
column.setIndex(optionIndex);
|
||||
if (props.dataType === 'cascade') {
|
||||
if (dataType.value === 'cascade') {
|
||||
onCascadeChange(columnIndex);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// get options of column by index
|
||||
const getColumnValues = (index) => (children[index] || {}).state.options;
|
||||
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) => {
|
||||
const setValues = (values: string[]) => {
|
||||
values.forEach((value, index) => {
|
||||
setColumnValue(index, value);
|
||||
});
|
||||
};
|
||||
|
||||
// set indexes of all columns
|
||||
const setIndexes = (indexes) => {
|
||||
const setIndexes = (indexes: number[]) => {
|
||||
indexes.forEach((optionIndex, columnIndex) => {
|
||||
setColumnIndex(columnIndex, optionIndex);
|
||||
});
|
||||
};
|
||||
|
||||
const emitAction = (event) => {
|
||||
const emitAction = (event: 'confirm' | 'cancel') => {
|
||||
if (dataType.value === 'text') {
|
||||
emit(event, getColumnValue(0), getColumnIndex(0));
|
||||
} else {
|
||||
@ -204,7 +230,7 @@ export default createComponent({
|
||||
}
|
||||
};
|
||||
|
||||
const onChange = (columnIndex) => {
|
||||
const onChange = (columnIndex: number) => {
|
||||
if (dataType.value === 'cascade') {
|
||||
onCascadeChange(columnIndex);
|
||||
}
|
||||
@ -266,7 +292,7 @@ export default createComponent({
|
||||
|
||||
const renderColumnItems = () =>
|
||||
formattedColumns.value.map((item, columnIndex) => (
|
||||
<PickerColumn
|
||||
<Column
|
||||
v-slots={{ option: slots.option }}
|
||||
textKey={textKey}
|
||||
readonly={props.readonly}
|
||||
@ -275,8 +301,8 @@ export default createComponent({
|
||||
itemHeight={itemHeight.value}
|
||||
defaultIndex={item.defaultIndex ?? +props.defaultIndex}
|
||||
swipeDuration={props.swipeDuration}
|
||||
visibleItemCount={props.visibleItemCount}
|
||||
initialOptions={item[valuesKey]}
|
||||
visibleItemCount={props.visibleItemCount}
|
||||
onChange={() => {
|
||||
onChange(columnIndex);
|
||||
}}
|
||||
@ -284,7 +310,7 @@ export default createComponent({
|
||||
));
|
||||
|
||||
const renderColumns = () => {
|
||||
const wrapHeight = itemHeight.value * props.visibleItemCount;
|
||||
const wrapHeight = itemHeight.value * +props.visibleItemCount;
|
||||
const frameStyle = { height: `${itemHeight.value}px` };
|
||||
const columnsStyle = { height: `${wrapHeight}px` };
|
||||
const maskStyle = {
|
@ -1,13 +1,3 @@
|
||||
export type SharedPickerProps = {
|
||||
title?: string;
|
||||
loading?: boolean;
|
||||
itemHeight?: number;
|
||||
showToolbar?: boolean;
|
||||
visibleItemCount: number | string;
|
||||
cancelButtonText?: string;
|
||||
confirmButtonText?: string;
|
||||
};
|
||||
|
||||
export const PICKER_KEY = 'vanPicker';
|
||||
|
||||
export const pickerProps = {
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { deepAssign } from './deep-assign';
|
||||
|
||||
export function deepClone(obj: Record<string, any>): Record<string, any> {
|
||||
export function deepClone<T extends Record<string, any>>(obj: T): T {
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map((item) => deepClone(item));
|
||||
return (obj.map((item) => deepClone(item)) as unknown) as T;
|
||||
}
|
||||
|
||||
if (typeof obj === 'object') {
|
||||
return deepAssign({}, obj);
|
||||
return deepAssign({}, obj) as T;
|
||||
}
|
||||
|
||||
return obj;
|
||||
|
@ -1981,10 +1981,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@vant/touch-emulator/-/touch-emulator-1.2.0.tgz#486300b23e57db9ce9231a04e0a0c621c68692d8"
|
||||
integrity sha512-sJ97zU85zOq51qoi7+CpBEcOyH3CitjP1KC7/GQwqaurUJni+EP7/F9n0HMnAh8GXMjgtgDBNJ5z48x+coNKYQ==
|
||||
|
||||
"@vant/use@^1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.npmjs.org/@vant/use/-/use-1.0.3.tgz#ec3f82c30b883b1ceb36e49b60882e332ea04bf4"
|
||||
integrity sha512-tnR6mdsjnN2mmiBznn8FNueyZYSFBavlOPNZd3Nf9SRP4QLPeHeebGSxWqYpFf6jpjYqOy0HHgtXz2Gu8dLeaw==
|
||||
"@vant/use@^1.0.4":
|
||||
version "1.0.4"
|
||||
resolved "https://registry.npm.taobao.org/@vant/use/download/@vant/use-1.0.4.tgz#67f827a40e74f3c318d5f05c31751610d8056184"
|
||||
integrity sha1-Z/gnpA5088MY1fBcMXUWENgFYYQ=
|
||||
dependencies:
|
||||
"@babel/runtime" "7.x"
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user