types(Picker): use tsx (#8137)

This commit is contained in:
neverland 2021-02-12 12:07:22 +08:00 committed by GitHub
parent 76aaba8089
commit 7355b610e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 144 additions and 90 deletions

View File

@ -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"

View File

@ -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) {

View File

@ -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';
});
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 = {

View File

@ -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 = {

View File

@ -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;

View File

@ -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"