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/icons": "^1.5.2",
"@vant/lazyload": "^1.0.2", "@vant/lazyload": "^1.0.2",
"@vant/popperjs": "^1.0.2", "@vant/popperjs": "^1.0.2",
"@vant/use": "^1.0.3" "@vant/use": "^1.0.4"
}, },
"peerDependencies": { "peerDependencies": {
"vue": "^3.0.0" "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'; import { PICKER_KEY } from './shared';
// Utils // Utils
@ -20,7 +21,7 @@ const MOMENTUM_LIMIT_DISTANCE = 15;
const [createComponent, bem] = createNamespace('picker-column'); const [createComponent, bem] = createNamespace('picker-column');
function getElementTranslateY(element) { function getElementTranslateY(element: Element) {
const style = window.getComputedStyle(element); const style = window.getComputedStyle(element);
const transform = style.transform || style.webkitTransform; const transform = style.transform || style.webkitTransform;
const translateY = transform.slice(7, transform.length - 1).split(', ')[5]; const translateY = transform.slice(7, transform.length - 1).split(', ')[5];
@ -28,22 +29,59 @@ function getElementTranslateY(element) {
return Number(translateY); 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; return isObject(option) && option.disabled;
} }
export default createComponent({ export default createComponent({
props: { props: {
textKey: String,
readonly: Boolean, readonly: Boolean,
allowHtml: Boolean, allowHtml: Boolean,
className: String, className: String,
itemHeight: Number, textKey: {
defaultIndex: Number, type: String,
swipeDuration: [Number, String], required: true,
visibleItemCount: [Number, String], },
itemHeight: {
type: Number,
required: true,
},
swipeDuration: {
type: [Number, String],
required: true,
},
visibleItemCount: {
type: [Number, String],
required: true,
},
defaultIndex: {
type: Number,
default: 0,
},
initialOptions: { initialOptions: {
type: Array, type: Array as PropType<PickerOption[]>,
default: () => [], default: () => [],
}, },
}, },
@ -51,13 +89,13 @@ export default createComponent({
emits: ['change'], emits: ['change'],
setup(props, { emit, slots }) { setup(props, { emit, slots }) {
let moving; let moving: boolean;
let startOffset; let startOffset: number;
let touchStartTime; let touchStartTime: number;
let momentumOffset; let momentumOffset: number;
let transitionEndTrigger; let transitionEndTrigger: null | (() => void);
const wrapper = ref(); const wrapper = ref<HTMLElement>();
const state = reactive({ const state = reactive({
index: props.defaultIndex, index: props.defaultIndex,
@ -71,9 +109,9 @@ export default createComponent({
const count = () => state.options.length; const count = () => state.options.length;
const baseOffset = () => 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()); index = range(index, 0, count());
for (let i = index; i < count(); i++) { 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; index = adjustIndex(index) || 0;
const offset = -index * props.itemHeight; const offset = -index * props.itemHeight;
@ -108,14 +146,14 @@ export default createComponent({
state.offset = offset; state.offset = offset;
}; };
const setOptions = (options) => { const setOptions = (options: PickerOption[]) => {
if (JSON.stringify(options) !== JSON.stringify(state.options)) { if (JSON.stringify(options) !== JSON.stringify(state.options)) {
state.options = deepClone(options); state.options = deepClone(options);
setIndex(props.defaultIndex); setIndex(props.defaultIndex);
} }
}; };
const onClickItem = (index) => { const onClickItem = (index: number) => {
if (moving || props.readonly) { if (moving || props.readonly) {
return; return;
} }
@ -125,17 +163,17 @@ export default createComponent({
setIndex(index, true); setIndex(index, true);
}; };
const getOptionText = (option) => { const getOptionText = (option: PickerOption) => {
if (isObject(option) && props.textKey in option) { if (isObject(option) && props.textKey in option) {
return option[props.textKey]; return option[props.textKey];
} }
return option; return option;
}; };
const getIndexByOffset = (offset) => const getIndexByOffset = (offset: number) =>
range(Math.round(-offset / props.itemHeight), 0, count() - 1); 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); const speed = Math.abs(distance / duration);
distance = state.offset + (speed / 0.003) * (distance < 0 ? -1 : 1); 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) { if (props.readonly) {
return; return;
} }
@ -164,7 +202,7 @@ export default createComponent({
touch.start(event); touch.start(event);
if (moving) { if (moving) {
const translateY = getElementTranslateY(wrapper.value); const translateY = getElementTranslateY(wrapper.value!);
state.offset = Math.min(0, translateY - baseOffset()); state.offset = Math.min(0, translateY - baseOffset());
startOffset = state.offset; startOffset = state.offset;
} else { } else {
@ -177,7 +215,7 @@ export default createComponent({
transitionEndTrigger = null; transitionEndTrigger = null;
}; };
const onTouchMove = (event) => { const onTouchMove = (event: TouchEvent) => {
if (props.readonly) { if (props.readonly) {
return; return;
} }
@ -234,7 +272,7 @@ export default createComponent({
height: `${props.itemHeight}px`, height: `${props.itemHeight}px`,
}; };
return state.options.map((option, index) => { return state.options.map((option, index: number) => {
const text = getOptionText(option); const text = getOptionText(option);
const disabled = isOptionDisabled(option); const disabled = isOptionDisabled(option);
@ -264,7 +302,7 @@ export default createComponent({
}); });
}; };
const setValue = (value) => { const setValue = (value: string) => {
const { options } = state; const { options } = state;
for (let i = 0; i < options.length; i++) { for (let i = 0; i < options.length; i++) {
if (getOptionText(options[i]) === value) { 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'; import { pickerProps, PICKER_KEY } from './shared';
// Utils // Utils
@ -11,16 +11,28 @@ import { useExpose } from '../composables/use-expose';
// Components // Components
import Loading from '../loading'; import Loading from '../loading';
import PickerColumn from './PickerColumn'; import Column, {
PickerOption,
PickerColumn,
PickerObjectColumn,
} from './PickerColumn';
const [createComponent, bem, t] = createNamespace('picker'); const [createComponent, bem, t] = createNamespace('picker');
export type PickerToolbarPosition = 'top' | 'bottom';
export type PickerFieldNames = {
text?: string;
values?: string;
children?: string;
};
export default createComponent({ export default createComponent({
props: { props: {
...pickerProps, ...pickerProps,
columnsFieldNames: Object, columnsFieldNames: Object as PropType<PickerFieldNames>,
columns: { columns: {
type: Array, type: Array as PropType<PickerColumn[]>,
default: () => [], default: () => [],
}, },
defaultIndex: { defaultIndex: {
@ -28,7 +40,7 @@ export default createComponent({
default: 0, default: 0,
}, },
toolbarPosition: { toolbarPosition: {
type: String, type: String as PropType<PickerToolbarPosition>,
default: 'top', default: 'top',
}, },
// @deprecated // @deprecated
@ -42,7 +54,7 @@ export default createComponent({
emits: ['confirm', 'cancel', 'change'], emits: ['confirm', 'cancel', 'change'],
setup(props, { emit, slots }) { setup(props, { emit, slots }) {
const formattedColumns = ref([]); const formattedColumns = ref<PickerObjectColumn[]>([]);
const { text: textKey, values: valuesKey, children: childrenKey } = { const { text: textKey, values: valuesKey, children: childrenKey } = {
// compatible with valueKey prop // compatible with valueKey prop
@ -52,29 +64,32 @@ export default createComponent({
...props.columnsFieldNames, ...props.columnsFieldNames,
}; };
const { children, linkChildren } = useChildren(PICKER_KEY); const { children, linkChildren } = useChildren<
// eslint-disable-next-line
ComponentPublicInstance<{}, any>
>(PICKER_KEY);
linkChildren(); linkChildren();
const itemHeight = computed(() => unitToPx(props.itemHeight)); const itemHeight = computed(() => unitToPx(props.itemHeight));
const dataType = computed(() => { const dataType = computed(() => {
const { columns } = props; const firstColumn = props.columns[0];
const firstColumn = columns[0] || {}; if (typeof firstColumn === 'string') {
return 'text';
if (firstColumn[childrenKey]) { }
if (childrenKey in firstColumn) {
return 'cascade'; return 'cascade';
} }
if (firstColumn[valuesKey]) {
return 'object'; return 'object';
}
return 'text';
}); });
const formatCascade = () => { const formatCascade = () => {
const formatted = []; const formatted: PickerObjectColumn[] = [];
let cursor = { [childrenKey]: props.columns }; let cursor: PickerObjectColumn = {
[childrenKey]: props.columns,
};
while (cursor && cursor[childrenKey]) { while (cursor && cursor[childrenKey]) {
const children = cursor[childrenKey]; const children = cursor[childrenKey];
@ -109,7 +124,7 @@ export default createComponent({
} else if (dataType.value === 'cascade') { } else if (dataType.value === 'cascade') {
formatCascade(); formatCascade();
} else { } else {
formattedColumns.value = columns; formattedColumns.value = columns as PickerObjectColumn[];
} }
}; };
@ -117,15 +132,17 @@ export default createComponent({
const getIndexes = () => children.map((child) => child.state.index); const getIndexes = () => children.map((child) => child.state.index);
// set options of column by index // set options of column by index
const setColumnValues = (index, options) => { const setColumnValues = (index: number, options: PickerOption[]) => {
const column = children[index]; const column = children[index];
if (column) { if (column) {
column.setOptions(options); column.setOptions(options);
} }
}; };
const onCascadeChange = (columnIndex) => { const onCascadeChange = (columnIndex: number) => {
let cursor = { [childrenKey]: props.columns }; let cursor: PickerObjectColumn = {
[childrenKey]: props.columns,
};
const indexes = getIndexes(); const indexes = getIndexes();
for (let i = 0; i <= columnIndex; i++) { for (let i = 0; i <= columnIndex; i++) {
@ -140,21 +157,21 @@ export default createComponent({
}; };
// get column instance by index // get column instance by index
const getColumn = (index) => children[index]; const getChild = (index: number) => children[index];
// get column value by index // get column value by index
const getColumnValue = (index) => { const getColumnValue = (index: number) => {
const column = getColumn(index); const column = getChild(index);
return column && column.getValue(); if (column) {
return column.getValue();
}
}; };
// set column value by index // set column value by index
const setColumnValue = (index, value) => { const setColumnValue = (index: number, value: string) => {
const column = getColumn(index); const column = getChild(index);
if (column) { if (column) {
column.setValue(value); column.setValue(value);
if (dataType.value === 'cascade') { if (dataType.value === 'cascade') {
onCascadeChange(index); onCascadeChange(index);
} }
@ -162,41 +179,50 @@ export default createComponent({
}; };
// get column option index by column index // 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 // set column option index by column index
const setColumnIndex = (columnIndex, optionIndex) => { const setColumnIndex = (columnIndex: number, optionIndex: number) => {
const column = getColumn(columnIndex); const column = getChild(columnIndex);
if (column) { if (column) {
column.setIndex(optionIndex); column.setIndex(optionIndex);
if (props.dataType === 'cascade') { if (dataType.value === 'cascade') {
onCascadeChange(columnIndex); onCascadeChange(columnIndex);
} }
} }
}; };
// get options of column by index // 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 // get values of all columns
const getValues = () => children.map((child) => child.getValue()); const getValues = () => children.map((child) => child.getValue());
// set values of all columns // set values of all columns
const setValues = (values) => { const setValues = (values: string[]) => {
values.forEach((value, index) => { values.forEach((value, index) => {
setColumnValue(index, value); setColumnValue(index, value);
}); });
}; };
// set indexes of all columns // set indexes of all columns
const setIndexes = (indexes) => { const setIndexes = (indexes: number[]) => {
indexes.forEach((optionIndex, columnIndex) => { indexes.forEach((optionIndex, columnIndex) => {
setColumnIndex(columnIndex, optionIndex); setColumnIndex(columnIndex, optionIndex);
}); });
}; };
const emitAction = (event) => { const emitAction = (event: 'confirm' | 'cancel') => {
if (dataType.value === 'text') { if (dataType.value === 'text') {
emit(event, getColumnValue(0), getColumnIndex(0)); emit(event, getColumnValue(0), getColumnIndex(0));
} else { } else {
@ -204,7 +230,7 @@ export default createComponent({
} }
}; };
const onChange = (columnIndex) => { const onChange = (columnIndex: number) => {
if (dataType.value === 'cascade') { if (dataType.value === 'cascade') {
onCascadeChange(columnIndex); onCascadeChange(columnIndex);
} }
@ -266,7 +292,7 @@ export default createComponent({
const renderColumnItems = () => const renderColumnItems = () =>
formattedColumns.value.map((item, columnIndex) => ( formattedColumns.value.map((item, columnIndex) => (
<PickerColumn <Column
v-slots={{ option: slots.option }} v-slots={{ option: slots.option }}
textKey={textKey} textKey={textKey}
readonly={props.readonly} readonly={props.readonly}
@ -275,8 +301,8 @@ export default createComponent({
itemHeight={itemHeight.value} itemHeight={itemHeight.value}
defaultIndex={item.defaultIndex ?? +props.defaultIndex} defaultIndex={item.defaultIndex ?? +props.defaultIndex}
swipeDuration={props.swipeDuration} swipeDuration={props.swipeDuration}
visibleItemCount={props.visibleItemCount}
initialOptions={item[valuesKey]} initialOptions={item[valuesKey]}
visibleItemCount={props.visibleItemCount}
onChange={() => { onChange={() => {
onChange(columnIndex); onChange(columnIndex);
}} }}
@ -284,7 +310,7 @@ export default createComponent({
)); ));
const renderColumns = () => { const renderColumns = () => {
const wrapHeight = itemHeight.value * props.visibleItemCount; const wrapHeight = itemHeight.value * +props.visibleItemCount;
const frameStyle = { height: `${itemHeight.value}px` }; const frameStyle = { height: `${itemHeight.value}px` };
const columnsStyle = { height: `${wrapHeight}px` }; const columnsStyle = { height: `${wrapHeight}px` };
const maskStyle = { 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 PICKER_KEY = 'vanPicker';
export const pickerProps = { export const pickerProps = {

View File

@ -1,12 +1,12 @@
import { deepAssign } from './deep-assign'; 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)) { if (Array.isArray(obj)) {
return obj.map((item) => deepClone(item)); return (obj.map((item) => deepClone(item)) as unknown) as T;
} }
if (typeof obj === 'object') { if (typeof obj === 'object') {
return deepAssign({}, obj); return deepAssign({}, obj) as T;
} }
return obj; return obj;

View File

@ -1981,10 +1981,10 @@
resolved "https://registry.yarnpkg.com/@vant/touch-emulator/-/touch-emulator-1.2.0.tgz#486300b23e57db9ce9231a04e0a0c621c68692d8" resolved "https://registry.yarnpkg.com/@vant/touch-emulator/-/touch-emulator-1.2.0.tgz#486300b23e57db9ce9231a04e0a0c621c68692d8"
integrity sha512-sJ97zU85zOq51qoi7+CpBEcOyH3CitjP1KC7/GQwqaurUJni+EP7/F9n0HMnAh8GXMjgtgDBNJ5z48x+coNKYQ== integrity sha512-sJ97zU85zOq51qoi7+CpBEcOyH3CitjP1KC7/GQwqaurUJni+EP7/F9n0HMnAh8GXMjgtgDBNJ5z48x+coNKYQ==
"@vant/use@^1.0.3": "@vant/use@^1.0.4":
version "1.0.3" version "1.0.4"
resolved "https://registry.npmjs.org/@vant/use/-/use-1.0.3.tgz#ec3f82c30b883b1ceb36e49b60882e332ea04bf4" resolved "https://registry.npm.taobao.org/@vant/use/download/@vant/use-1.0.4.tgz#67f827a40e74f3c318d5f05c31751610d8056184"
integrity sha512-tnR6mdsjnN2mmiBznn8FNueyZYSFBavlOPNZd3Nf9SRP4QLPeHeebGSxWqYpFf6jpjYqOy0HHgtXz2Gu8dLeaw== integrity sha1-Z/gnpA5088MY1fBcMXUWENgFYYQ=
dependencies: dependencies:
"@babel/runtime" "7.x" "@babel/runtime" "7.x"