types(Field): use tsx (#8151)

This commit is contained in:
neverland 2021-02-14 11:31:43 +08:00 committed by GitHub
parent fa8b494f0e
commit 393b2a256f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 136 additions and 84 deletions

View File

@ -279,7 +279,7 @@ export default {
| format-trigger | 格式化函数触发的时机,可选值为 `onBlur` | _string_ | `onChange` |
| arrow-direction | 箭头方向,可选值为 `left` `up` `down` | _string_ | `right` |
| label-class | 左侧文本额外类名 | _string \| Array \| object_ | - |
| label-width | 左侧文本宽度,默认单位为`px` | _number \| string_ | `6.2em` |
| label-width | 左侧文本宽度,默认单位为 `px` | _number \| string_ | `6.2em` |
| label-align | 左侧文本对齐方式,可选值为 `center` `right` | _string_ | `left` |
| input-align | 输入框对齐方式,可选值为 `center` `right` | _string_ | `left` |
| error-message-align | 错误提示文案对齐方式,可选值为 `center` `right` | _string_ | `left` |
@ -298,7 +298,7 @@ export default {
| focus | 输入框获得焦点时触发 | _event: Event_ |
| blur | 输入框失去焦点时触发 | _event: Event_ |
| clear | 点击清除按钮时触发 | _event: MouseEvent_ |
| click | 点击 Field 时触发 | _event: MouseEvent_ |
| click | 点击组件时触发 | _event: MouseEvent_ |
| click-input | 点击输入区域时触发 | _event: MouseEvent_ |
| click-left-icon | 点击左侧图标时触发 | _event: MouseEvent_ |
| click-right-icon | 点击右侧图标时触发 | _event: MouseEvent_ |
@ -316,7 +316,7 @@ export default {
| 名称 | 说明 |
| ---------- | ---------------------------------------------------------- |
| label | 自定义输入框 label 标签 |
| label | 自定义输入框左侧文本 |
| input | 自定义输入框,使用此插槽后,与输入框相关的属性和事件将失效 |
| left-icon | 自定义输入框头部图标 |
| right-icon | 自定义输入框尾部图标 |

View File

@ -5,7 +5,9 @@ import {
computed,
nextTick,
reactive,
PropType,
onMounted,
HTMLAttributes,
} from 'vue';
// Utils
@ -32,6 +34,17 @@ import { FORM_KEY, FIELD_KEY } from '../composables/use-link-field';
import Icon from '../icon';
import Cell, { cellProps } from '../cell';
// Types
import type {
FieldRule,
FieldType,
FieldTextAlign,
FieldClearTrigger,
FieldFormatTrigger,
FieldAutosizeConfig,
FieldValidateTrigger,
} from './types';
const [createComponent, bem] = createNamespace('field');
export default createComponent({
@ -39,24 +52,24 @@ export default createComponent({
...cellProps,
rows: [Number, String],
name: String,
rules: Array,
autosize: [Boolean, Object],
rules: Array as PropType<FieldRule[]>,
autosize: [Boolean, Object] as PropType<boolean | FieldAutosizeConfig>,
leftIcon: String,
rightIcon: String,
clearable: Boolean,
formatter: Function,
formatter: Function as PropType<(value: string) => string>,
maxlength: [Number, String],
labelWidth: [Number, String],
labelClass: null,
labelAlign: String,
inputAlign: String,
labelClass: null as any,
labelAlign: String as PropType<FieldTextAlign>,
inputAlign: String as PropType<FieldTextAlign>,
placeholder: String,
autocomplete: String,
errorMessage: String,
errorMessageAlign: String,
errorMessageAlign: String as PropType<FieldTextAlign>,
showWordLimit: Boolean,
type: {
type: String,
type: String as PropType<FieldType>,
default: 'text',
},
error: {
@ -80,11 +93,11 @@ export default createComponent({
default: '',
},
clearTrigger: {
type: String,
type: String as PropType<FieldClearTrigger>,
default: 'focus',
},
formatTrigger: {
type: String,
type: String as PropType<FieldFormatTrigger>,
default: 'onChange',
},
},
@ -107,12 +120,14 @@ export default createComponent({
validateMessage: '',
});
const inputRef = ref();
const childFieldValue = ref();
const inputRef = ref<HTMLInputElement>();
const childFieldValue = ref<() => unknown>();
const { parent: form } = useParent(FORM_KEY);
const { parent: form } = useParent<any>(FORM_KEY);
const getProp = (key) => {
const getModelValue = () => String(props.modelValue ?? '');
const getProp = (key: keyof typeof props) => {
if (isDef(props[key])) {
return props[key];
}
@ -125,7 +140,7 @@ export default createComponent({
const readonly = getProp('readonly');
if (props.clearable && !readonly) {
const hasValue = isDef(props.modelValue) && props.modelValue !== '';
const hasValue = getModelValue() !== '';
const trigger =
props.clearTrigger === 'always' ||
(props.clearTrigger === 'focus' && state.focused);
@ -141,9 +156,9 @@ export default createComponent({
return props.modelValue;
});
const runValidator = (value, rule) =>
const runValidator = (value: unknown, rule: FieldRule) =>
new Promise((resolve) => {
const returnVal = rule.validator(value, rule);
const returnVal = rule.validator!(value, rule);
if (isPromise(returnVal)) {
return returnVal.then(resolve);
@ -152,16 +167,16 @@ export default createComponent({
resolve(returnVal);
});
const getRuleMessage = (value, rule) => {
const getRuleMessage = (value: unknown, rule: FieldRule) => {
const { message } = rule;
if (isFunction(message)) {
return message(value, rule);
}
return message;
return message || '';
};
const runRules = (rules) =>
const runRules = (rules: FieldRule[]) =>
rules.reduce(
(promise, rule) =>
promise.then(() => {
@ -204,25 +219,25 @@ export default createComponent({
};
const validate = (rules = props.rules) =>
new Promise((resolve) => {
if (!rules) {
new Promise<{ name?: string; message: string } | void>((resolve) => {
resetValidation();
if (rules) {
runRules(rules).then(() => {
if (state.validateFailed) {
resolve({
name: props.name,
message: state.validateMessage,
});
} else {
resolve();
}
});
} else {
resolve();
}
resetValidation();
runRules(rules).then(() => {
if (state.validateFailed) {
resolve({
name: props.name,
message: state.validateMessage,
});
} else {
resolve();
}
});
});
const validateWithTrigger = (trigger) => {
const validateWithTrigger = (trigger: FieldValidateTrigger) => {
if (form && props.rules) {
const defaultTrigger = form.props.validateTrigger === trigger;
const rules = props.rules.filter((rule) => {
@ -237,19 +252,25 @@ export default createComponent({
}
};
const updateValue = (value, trigger = 'onChange') => {
value = isDef(value) ? String(value) : '';
// native maxlength have incorrect line-break counting
// see: https://github.com/youzan/vant/issues/5033
const { maxlength, modelValue } = props;
// native maxlength have incorrect line-break counting
// see: https://github.com/youzan/vant/issues/5033
const limitValueLength = (value: string) => {
const { maxlength } = props;
if (isDef(maxlength) && value.length > maxlength) {
const modelValue = getModelValue();
if (modelValue && modelValue.length === +maxlength) {
value = modelValue;
} else {
value = value.slice(0, maxlength);
return modelValue;
}
return value.slice(0, +maxlength);
}
return value;
};
const updateValue = (
value: string,
trigger: FieldFormatTrigger = 'onChange'
) => {
value = limitValueLength(value);
if (props.type === 'number' || props.type === 'digit') {
const isNumber = props.type === 'number';
@ -269,10 +290,10 @@ export default createComponent({
}
};
const onInput = (event) => {
const onInput = (event: Event) => {
// skip update value when composing
if (!event.target.composing) {
updateValue(event.target.value);
if (!event.target!.composing) {
updateValue((event.target as HTMLInputElement).value);
}
};
@ -288,7 +309,7 @@ export default createComponent({
}
};
const onFocus = (event) => {
const onFocus = (event: Event) => {
state.focused = true;
emit('focus', event);
@ -299,27 +320,27 @@ export default createComponent({
}
};
const onBlur = (event) => {
const onBlur = (event: Event) => {
state.focused = false;
updateValue(props.modelValue, 'onBlur');
updateValue(getModelValue(), 'onBlur');
emit('blur', event);
validateWithTrigger('onBlur');
resetScroll();
};
const onClickInput = (event) => {
const onClickInput = (event: MouseEvent) => {
emit('click-input', event);
};
const onClickLeftIcon = (event) => {
const onClickLeftIcon = (event: MouseEvent) => {
emit('click-left-icon', event);
};
const onClickRightIcon = (event) => {
const onClickRightIcon = (event: MouseEvent) => {
emit('click-right-icon', event);
};
const onClear = (event) => {
const onClear = (event: MouseEvent) => {
preventDefault(event);
emit('update:modelValue', '');
emit('clear', event);
@ -341,11 +362,11 @@ export default createComponent({
}
});
const onKeypress = (event) => {
const onKeypress = (event: KeyboardEvent) => {
const ENTER_CODE = 13;
if (event.keyCode === ENTER_CODE) {
const submitOnEnter = getProp('submitOnEnter');
const submitOnEnter = form && form.props.submitOnEnter;
if (!submitOnEnter && props.type !== 'textarea') {
preventDefault(event);
}
@ -359,15 +380,15 @@ export default createComponent({
emit('keypress', event);
};
const onCompositionStart = (event) => {
event.target.composing = true;
const onCompositionStart = (event: Event) => {
event.target!.composing = true;
};
const onCompositionEnd = (event) => {
const onCompositionEnd = (event: Event) => {
const { target } = event;
if (target.composing) {
target.composing = false;
trigger(target, 'input');
if (target!.composing) {
target!.composing = false;
trigger(target as Element, 'input');
}
};
@ -383,16 +404,16 @@ export default createComponent({
let height = input.scrollHeight;
if (isObject(props.autosize)) {
const { maxHeight, minHeight } = props.autosize;
if (maxHeight) {
if (maxHeight !== undefined) {
height = Math.min(height, maxHeight);
}
if (minHeight) {
if (minHeight !== undefined) {
height = Math.max(height, minHeight);
}
}
if (height) {
input.style.height = height + 'px';
input.style.height = `${height}px`;
}
};
@ -415,7 +436,7 @@ export default createComponent({
const inputProps = {
ref: inputRef,
name: props.name,
rows: props.rows,
rows: props.rows !== undefined ? +props.rows : undefined,
class: bem('control', inputAlign),
value: props.modelValue,
disabled,
@ -439,7 +460,7 @@ export default createComponent({
}
let inputType = type;
let inputMode;
let inputMode: HTMLAttributes['inputmode'];
// type="number" is weired in iOS, and can't prevent dot in Android
// so use inputmode to set keyboard in mordern browers
@ -490,7 +511,7 @@ export default createComponent({
const renderWordLimit = () => {
if (props.showWordLimit && props.maxlength) {
const count = (props.modelValue || '').length;
const count = getModelValue().length;
return (
<div class={bem('word-limit')}>
<span class={bem('word-num')}>{count}</span>/{props.maxlength}
@ -541,8 +562,8 @@ export default createComponent({
watch(
() => props.modelValue,
(value) => {
updateValue(value);
() => {
updateValue(getModelValue());
resetValidation();
validateWithTrigger('onChange');
nextTick(adjustSize);
@ -550,7 +571,7 @@ export default createComponent({
);
onMounted(() => {
updateValue(props.modelValue, props.formatTrigger);
updateValue(getModelValue(), props.formatTrigger);
nextTick(adjustSize);
});

39
src/field/types.ts Normal file
View File

@ -0,0 +1,39 @@
export type FieldType =
| 'tel'
| 'text'
| 'digit'
| 'number'
| 'search'
| 'password'
| 'textarea';
export type FieldTextAlign = 'left' | 'center' | 'right';
export type FieldClearTrigger = 'always' | 'focus';
export type FieldFormatTrigger = 'onBlur' | 'onChange';
export type FieldValidateTrigger = 'onBlur' | 'onChange' | 'onSubmit';
export type FieldAutosizeConfig = {
maxHeight?: number;
minHeight?: number;
};
export type FieldRule = {
pattern?: RegExp;
trigger?: FieldValidateTrigger;
message?: string | ((value: any, rule: FieldRule) => string);
required?: boolean;
validator?: (
value: any,
rule: FieldRule
) => boolean | string | Promise<boolean | string>;
formatter?: (value: any, rule: FieldRule) => string;
};
declare global {
interface EventTarget {
composing?: boolean;
}
}

View File

@ -1,13 +1,4 @@
export type FieldValidateTrigger = 'onSubmit' | 'onChange' | 'onBlur';
export type FieldRule = {
pattern?: RegExp;
trigger?: FieldValidateTrigger;
message?: string | ((value: unknown, rule: FieldRule) => string);
required?: boolean;
validator?: (value: unknown, rule: FieldRule) => boolean | Promise<boolean>;
formatter?: (value: unknown, rule: FieldRule) => unknown;
};
import type { FieldRule } from './types';
function isEmptyValue(value: unknown) {
if (Array.isArray(value)) {

View File

@ -21,5 +21,6 @@ declare module 'vue' {
onToggle?: EventHandler;
onConfirm?: EventHandler;
onClickStep?: EventHandler;
onTouchstart?: EventHandler;
}
}