From 7355b610e115e5cf841b959ab5e3693f776c21f4 Mon Sep 17 00:00:00 2001 From: neverland Date: Fri, 12 Feb 2021 12:07:22 +0800 Subject: [PATCH] types(Picker): use tsx (#8137) --- package.json | 2 +- .../{PickerColumn.js => PickerColumn.tsx} | 94 ++++++++++----- src/picker/README.zh-CN.md | 4 +- src/picker/{index.js => index.tsx} | 110 +++++++++++------- src/picker/shared.ts | 10 -- src/utils/deep-clone.ts | 6 +- yarn.lock | 8 +- 7 files changed, 144 insertions(+), 90 deletions(-) rename src/picker/{PickerColumn.js => PickerColumn.tsx} (78%) rename src/picker/{index.js => index.tsx} (75%) diff --git a/package.json b/package.json index cc4feaecd..e5d152d46 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/src/picker/PickerColumn.js b/src/picker/PickerColumn.tsx similarity index 78% rename from src/picker/PickerColumn.js rename to src/picker/PickerColumn.tsx index 322dcc349..91ac41405 100644 --- a/src/picker/PickerColumn.js +++ b/src/picker/PickerColumn.tsx @@ -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, 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(); 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) { diff --git a/src/picker/README.zh-CN.md b/src/picker/README.zh-CN.md index 4fc5437b0..809711a40 100644 --- a/src/picker/README.zh-CN.md +++ b/src/picker/README.zh-CN.md @@ -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` | diff --git a/src/picker/index.js b/src/picker/index.tsx similarity index 75% rename from src/picker/index.js rename to src/picker/index.tsx index ce434b8ec..0966b8660 100644 --- a/src/picker/index.js +++ b/src/picker/index.tsx @@ -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, columns: { - type: Array, + type: Array as PropType, default: () => [], }, defaultIndex: { @@ -28,7 +40,7 @@ export default createComponent({ default: 0, }, toolbarPosition: { - type: String, + type: String as PropType, default: 'top', }, // @deprecated @@ -42,7 +54,7 @@ export default createComponent({ emits: ['confirm', 'cancel', 'change'], setup(props, { emit, slots }) { - const formattedColumns = ref([]); + const formattedColumns = ref([]); 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) => ( - { 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 = { diff --git a/src/picker/shared.ts b/src/picker/shared.ts index f39093563..78e9aa35b 100644 --- a/src/picker/shared.ts +++ b/src/picker/shared.ts @@ -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 = { diff --git a/src/utils/deep-clone.ts b/src/utils/deep-clone.ts index 0ab7a7c29..44aada0e6 100644 --- a/src/utils/deep-clone.ts +++ b/src/utils/deep-clone.ts @@ -1,12 +1,12 @@ import { deepAssign } from './deep-assign'; -export function deepClone(obj: Record): Record { +export function deepClone>(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; diff --git a/yarn.lock b/yarn.lock index d7252805c..2cb774b05 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"