mirror of
https://gitee.com/vant-contrib/vant.git
synced 2025-04-25 02:41:46 +08:00
types(Field): use tsx (#8151)
This commit is contained in:
parent
fa8b494f0e
commit
393b2a256f
@ -298,7 +298,7 @@ export default {
|
|||||||
| focus | 输入框获得焦点时触发 | _event: Event_ |
|
| focus | 输入框获得焦点时触发 | _event: Event_ |
|
||||||
| blur | 输入框失去焦点时触发 | _event: Event_ |
|
| blur | 输入框失去焦点时触发 | _event: Event_ |
|
||||||
| clear | 点击清除按钮时触发 | _event: MouseEvent_ |
|
| clear | 点击清除按钮时触发 | _event: MouseEvent_ |
|
||||||
| click | 点击 Field 时触发 | _event: MouseEvent_ |
|
| click | 点击组件时触发 | _event: MouseEvent_ |
|
||||||
| click-input | 点击输入区域时触发 | _event: MouseEvent_ |
|
| click-input | 点击输入区域时触发 | _event: MouseEvent_ |
|
||||||
| click-left-icon | 点击左侧图标时触发 | _event: MouseEvent_ |
|
| click-left-icon | 点击左侧图标时触发 | _event: MouseEvent_ |
|
||||||
| click-right-icon | 点击右侧图标时触发 | _event: MouseEvent_ |
|
| click-right-icon | 点击右侧图标时触发 | _event: MouseEvent_ |
|
||||||
@ -316,7 +316,7 @@ export default {
|
|||||||
|
|
||||||
| 名称 | 说明 |
|
| 名称 | 说明 |
|
||||||
| ---------- | ---------------------------------------------------------- |
|
| ---------- | ---------------------------------------------------------- |
|
||||||
| label | 自定义输入框 label 标签 |
|
| label | 自定义输入框左侧文本 |
|
||||||
| input | 自定义输入框,使用此插槽后,与输入框相关的属性和事件将失效 |
|
| input | 自定义输入框,使用此插槽后,与输入框相关的属性和事件将失效 |
|
||||||
| left-icon | 自定义输入框头部图标 |
|
| left-icon | 自定义输入框头部图标 |
|
||||||
| right-icon | 自定义输入框尾部图标 |
|
| right-icon | 自定义输入框尾部图标 |
|
||||||
|
@ -5,7 +5,9 @@ import {
|
|||||||
computed,
|
computed,
|
||||||
nextTick,
|
nextTick,
|
||||||
reactive,
|
reactive,
|
||||||
|
PropType,
|
||||||
onMounted,
|
onMounted,
|
||||||
|
HTMLAttributes,
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
@ -32,6 +34,17 @@ import { FORM_KEY, FIELD_KEY } from '../composables/use-link-field';
|
|||||||
import Icon from '../icon';
|
import Icon from '../icon';
|
||||||
import Cell, { cellProps } from '../cell';
|
import Cell, { cellProps } from '../cell';
|
||||||
|
|
||||||
|
// Types
|
||||||
|
import type {
|
||||||
|
FieldRule,
|
||||||
|
FieldType,
|
||||||
|
FieldTextAlign,
|
||||||
|
FieldClearTrigger,
|
||||||
|
FieldFormatTrigger,
|
||||||
|
FieldAutosizeConfig,
|
||||||
|
FieldValidateTrigger,
|
||||||
|
} from './types';
|
||||||
|
|
||||||
const [createComponent, bem] = createNamespace('field');
|
const [createComponent, bem] = createNamespace('field');
|
||||||
|
|
||||||
export default createComponent({
|
export default createComponent({
|
||||||
@ -39,24 +52,24 @@ export default createComponent({
|
|||||||
...cellProps,
|
...cellProps,
|
||||||
rows: [Number, String],
|
rows: [Number, String],
|
||||||
name: String,
|
name: String,
|
||||||
rules: Array,
|
rules: Array as PropType<FieldRule[]>,
|
||||||
autosize: [Boolean, Object],
|
autosize: [Boolean, Object] as PropType<boolean | FieldAutosizeConfig>,
|
||||||
leftIcon: String,
|
leftIcon: String,
|
||||||
rightIcon: String,
|
rightIcon: String,
|
||||||
clearable: Boolean,
|
clearable: Boolean,
|
||||||
formatter: Function,
|
formatter: Function as PropType<(value: string) => string>,
|
||||||
maxlength: [Number, String],
|
maxlength: [Number, String],
|
||||||
labelWidth: [Number, String],
|
labelWidth: [Number, String],
|
||||||
labelClass: null,
|
labelClass: null as any,
|
||||||
labelAlign: String,
|
labelAlign: String as PropType<FieldTextAlign>,
|
||||||
inputAlign: String,
|
inputAlign: String as PropType<FieldTextAlign>,
|
||||||
placeholder: String,
|
placeholder: String,
|
||||||
autocomplete: String,
|
autocomplete: String,
|
||||||
errorMessage: String,
|
errorMessage: String,
|
||||||
errorMessageAlign: String,
|
errorMessageAlign: String as PropType<FieldTextAlign>,
|
||||||
showWordLimit: Boolean,
|
showWordLimit: Boolean,
|
||||||
type: {
|
type: {
|
||||||
type: String,
|
type: String as PropType<FieldType>,
|
||||||
default: 'text',
|
default: 'text',
|
||||||
},
|
},
|
||||||
error: {
|
error: {
|
||||||
@ -80,11 +93,11 @@ export default createComponent({
|
|||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
clearTrigger: {
|
clearTrigger: {
|
||||||
type: String,
|
type: String as PropType<FieldClearTrigger>,
|
||||||
default: 'focus',
|
default: 'focus',
|
||||||
},
|
},
|
||||||
formatTrigger: {
|
formatTrigger: {
|
||||||
type: String,
|
type: String as PropType<FieldFormatTrigger>,
|
||||||
default: 'onChange',
|
default: 'onChange',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -107,12 +120,14 @@ export default createComponent({
|
|||||||
validateMessage: '',
|
validateMessage: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const inputRef = ref();
|
const inputRef = ref<HTMLInputElement>();
|
||||||
const childFieldValue = ref();
|
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])) {
|
if (isDef(props[key])) {
|
||||||
return props[key];
|
return props[key];
|
||||||
}
|
}
|
||||||
@ -125,7 +140,7 @@ export default createComponent({
|
|||||||
const readonly = getProp('readonly');
|
const readonly = getProp('readonly');
|
||||||
|
|
||||||
if (props.clearable && !readonly) {
|
if (props.clearable && !readonly) {
|
||||||
const hasValue = isDef(props.modelValue) && props.modelValue !== '';
|
const hasValue = getModelValue() !== '';
|
||||||
const trigger =
|
const trigger =
|
||||||
props.clearTrigger === 'always' ||
|
props.clearTrigger === 'always' ||
|
||||||
(props.clearTrigger === 'focus' && state.focused);
|
(props.clearTrigger === 'focus' && state.focused);
|
||||||
@ -141,9 +156,9 @@ export default createComponent({
|
|||||||
return props.modelValue;
|
return props.modelValue;
|
||||||
});
|
});
|
||||||
|
|
||||||
const runValidator = (value, rule) =>
|
const runValidator = (value: unknown, rule: FieldRule) =>
|
||||||
new Promise((resolve) => {
|
new Promise((resolve) => {
|
||||||
const returnVal = rule.validator(value, rule);
|
const returnVal = rule.validator!(value, rule);
|
||||||
|
|
||||||
if (isPromise(returnVal)) {
|
if (isPromise(returnVal)) {
|
||||||
return returnVal.then(resolve);
|
return returnVal.then(resolve);
|
||||||
@ -152,16 +167,16 @@ export default createComponent({
|
|||||||
resolve(returnVal);
|
resolve(returnVal);
|
||||||
});
|
});
|
||||||
|
|
||||||
const getRuleMessage = (value, rule) => {
|
const getRuleMessage = (value: unknown, rule: FieldRule) => {
|
||||||
const { message } = rule;
|
const { message } = rule;
|
||||||
|
|
||||||
if (isFunction(message)) {
|
if (isFunction(message)) {
|
||||||
return message(value, rule);
|
return message(value, rule);
|
||||||
}
|
}
|
||||||
return message;
|
return message || '';
|
||||||
};
|
};
|
||||||
|
|
||||||
const runRules = (rules) =>
|
const runRules = (rules: FieldRule[]) =>
|
||||||
rules.reduce(
|
rules.reduce(
|
||||||
(promise, rule) =>
|
(promise, rule) =>
|
||||||
promise.then(() => {
|
promise.then(() => {
|
||||||
@ -204,12 +219,9 @@ export default createComponent({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const validate = (rules = props.rules) =>
|
const validate = (rules = props.rules) =>
|
||||||
new Promise((resolve) => {
|
new Promise<{ name?: string; message: string } | void>((resolve) => {
|
||||||
if (!rules) {
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
resetValidation();
|
resetValidation();
|
||||||
|
if (rules) {
|
||||||
runRules(rules).then(() => {
|
runRules(rules).then(() => {
|
||||||
if (state.validateFailed) {
|
if (state.validateFailed) {
|
||||||
resolve({
|
resolve({
|
||||||
@ -220,9 +232,12 @@ export default createComponent({
|
|||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const validateWithTrigger = (trigger) => {
|
const validateWithTrigger = (trigger: FieldValidateTrigger) => {
|
||||||
if (form && props.rules) {
|
if (form && props.rules) {
|
||||||
const defaultTrigger = form.props.validateTrigger === trigger;
|
const defaultTrigger = form.props.validateTrigger === trigger;
|
||||||
const rules = props.rules.filter((rule) => {
|
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
|
// native maxlength have incorrect line-break counting
|
||||||
// see: https://github.com/youzan/vant/issues/5033
|
// see: https://github.com/youzan/vant/issues/5033
|
||||||
const { maxlength, modelValue } = props;
|
const limitValueLength = (value: string) => {
|
||||||
|
const { maxlength } = props;
|
||||||
if (isDef(maxlength) && value.length > maxlength) {
|
if (isDef(maxlength) && value.length > maxlength) {
|
||||||
|
const modelValue = getModelValue();
|
||||||
if (modelValue && modelValue.length === +maxlength) {
|
if (modelValue && modelValue.length === +maxlength) {
|
||||||
value = modelValue;
|
return modelValue;
|
||||||
} else {
|
|
||||||
value = value.slice(0, maxlength);
|
|
||||||
}
|
}
|
||||||
|
return value.slice(0, +maxlength);
|
||||||
}
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateValue = (
|
||||||
|
value: string,
|
||||||
|
trigger: FieldFormatTrigger = 'onChange'
|
||||||
|
) => {
|
||||||
|
value = limitValueLength(value);
|
||||||
|
|
||||||
if (props.type === 'number' || props.type === 'digit') {
|
if (props.type === 'number' || props.type === 'digit') {
|
||||||
const isNumber = props.type === 'number';
|
const isNumber = props.type === 'number';
|
||||||
@ -269,10 +290,10 @@ export default createComponent({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onInput = (event) => {
|
const onInput = (event: Event) => {
|
||||||
// skip update value when composing
|
// skip update value when composing
|
||||||
if (!event.target.composing) {
|
if (!event.target!.composing) {
|
||||||
updateValue(event.target.value);
|
updateValue((event.target as HTMLInputElement).value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -288,7 +309,7 @@ export default createComponent({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onFocus = (event) => {
|
const onFocus = (event: Event) => {
|
||||||
state.focused = true;
|
state.focused = true;
|
||||||
emit('focus', event);
|
emit('focus', event);
|
||||||
|
|
||||||
@ -299,27 +320,27 @@ export default createComponent({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onBlur = (event) => {
|
const onBlur = (event: Event) => {
|
||||||
state.focused = false;
|
state.focused = false;
|
||||||
updateValue(props.modelValue, 'onBlur');
|
updateValue(getModelValue(), 'onBlur');
|
||||||
emit('blur', event);
|
emit('blur', event);
|
||||||
validateWithTrigger('onBlur');
|
validateWithTrigger('onBlur');
|
||||||
resetScroll();
|
resetScroll();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onClickInput = (event) => {
|
const onClickInput = (event: MouseEvent) => {
|
||||||
emit('click-input', event);
|
emit('click-input', event);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onClickLeftIcon = (event) => {
|
const onClickLeftIcon = (event: MouseEvent) => {
|
||||||
emit('click-left-icon', event);
|
emit('click-left-icon', event);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onClickRightIcon = (event) => {
|
const onClickRightIcon = (event: MouseEvent) => {
|
||||||
emit('click-right-icon', event);
|
emit('click-right-icon', event);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onClear = (event) => {
|
const onClear = (event: MouseEvent) => {
|
||||||
preventDefault(event);
|
preventDefault(event);
|
||||||
emit('update:modelValue', '');
|
emit('update:modelValue', '');
|
||||||
emit('clear', event);
|
emit('clear', event);
|
||||||
@ -341,11 +362,11 @@ export default createComponent({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const onKeypress = (event) => {
|
const onKeypress = (event: KeyboardEvent) => {
|
||||||
const ENTER_CODE = 13;
|
const ENTER_CODE = 13;
|
||||||
|
|
||||||
if (event.keyCode === ENTER_CODE) {
|
if (event.keyCode === ENTER_CODE) {
|
||||||
const submitOnEnter = getProp('submitOnEnter');
|
const submitOnEnter = form && form.props.submitOnEnter;
|
||||||
if (!submitOnEnter && props.type !== 'textarea') {
|
if (!submitOnEnter && props.type !== 'textarea') {
|
||||||
preventDefault(event);
|
preventDefault(event);
|
||||||
}
|
}
|
||||||
@ -359,15 +380,15 @@ export default createComponent({
|
|||||||
emit('keypress', event);
|
emit('keypress', event);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onCompositionStart = (event) => {
|
const onCompositionStart = (event: Event) => {
|
||||||
event.target.composing = true;
|
event.target!.composing = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onCompositionEnd = (event) => {
|
const onCompositionEnd = (event: Event) => {
|
||||||
const { target } = event;
|
const { target } = event;
|
||||||
if (target.composing) {
|
if (target!.composing) {
|
||||||
target.composing = false;
|
target!.composing = false;
|
||||||
trigger(target, 'input');
|
trigger(target as Element, 'input');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -383,16 +404,16 @@ export default createComponent({
|
|||||||
let height = input.scrollHeight;
|
let height = input.scrollHeight;
|
||||||
if (isObject(props.autosize)) {
|
if (isObject(props.autosize)) {
|
||||||
const { maxHeight, minHeight } = props.autosize;
|
const { maxHeight, minHeight } = props.autosize;
|
||||||
if (maxHeight) {
|
if (maxHeight !== undefined) {
|
||||||
height = Math.min(height, maxHeight);
|
height = Math.min(height, maxHeight);
|
||||||
}
|
}
|
||||||
if (minHeight) {
|
if (minHeight !== undefined) {
|
||||||
height = Math.max(height, minHeight);
|
height = Math.max(height, minHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (height) {
|
if (height) {
|
||||||
input.style.height = height + 'px';
|
input.style.height = `${height}px`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -415,7 +436,7 @@ export default createComponent({
|
|||||||
const inputProps = {
|
const inputProps = {
|
||||||
ref: inputRef,
|
ref: inputRef,
|
||||||
name: props.name,
|
name: props.name,
|
||||||
rows: props.rows,
|
rows: props.rows !== undefined ? +props.rows : undefined,
|
||||||
class: bem('control', inputAlign),
|
class: bem('control', inputAlign),
|
||||||
value: props.modelValue,
|
value: props.modelValue,
|
||||||
disabled,
|
disabled,
|
||||||
@ -439,7 +460,7 @@ export default createComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
let inputType = type;
|
let inputType = type;
|
||||||
let inputMode;
|
let inputMode: HTMLAttributes['inputmode'];
|
||||||
|
|
||||||
// type="number" is weired in iOS, and can't prevent dot in Android
|
// type="number" is weired in iOS, and can't prevent dot in Android
|
||||||
// so use inputmode to set keyboard in mordern browers
|
// so use inputmode to set keyboard in mordern browers
|
||||||
@ -490,7 +511,7 @@ export default createComponent({
|
|||||||
|
|
||||||
const renderWordLimit = () => {
|
const renderWordLimit = () => {
|
||||||
if (props.showWordLimit && props.maxlength) {
|
if (props.showWordLimit && props.maxlength) {
|
||||||
const count = (props.modelValue || '').length;
|
const count = getModelValue().length;
|
||||||
return (
|
return (
|
||||||
<div class={bem('word-limit')}>
|
<div class={bem('word-limit')}>
|
||||||
<span class={bem('word-num')}>{count}</span>/{props.maxlength}
|
<span class={bem('word-num')}>{count}</span>/{props.maxlength}
|
||||||
@ -541,8 +562,8 @@ export default createComponent({
|
|||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.modelValue,
|
() => props.modelValue,
|
||||||
(value) => {
|
() => {
|
||||||
updateValue(value);
|
updateValue(getModelValue());
|
||||||
resetValidation();
|
resetValidation();
|
||||||
validateWithTrigger('onChange');
|
validateWithTrigger('onChange');
|
||||||
nextTick(adjustSize);
|
nextTick(adjustSize);
|
||||||
@ -550,7 +571,7 @@ export default createComponent({
|
|||||||
);
|
);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
updateValue(props.modelValue, props.formatTrigger);
|
updateValue(getModelValue(), props.formatTrigger);
|
||||||
nextTick(adjustSize);
|
nextTick(adjustSize);
|
||||||
});
|
});
|
||||||
|
|
39
src/field/types.ts
Normal file
39
src/field/types.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,4 @@
|
|||||||
export type FieldValidateTrigger = 'onSubmit' | 'onChange' | 'onBlur';
|
import type { FieldRule } from './types';
|
||||||
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
function isEmptyValue(value: unknown) {
|
function isEmptyValue(value: unknown) {
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
|
1
src/vue-tsx-shim.d.ts
vendored
1
src/vue-tsx-shim.d.ts
vendored
@ -21,5 +21,6 @@ declare module 'vue' {
|
|||||||
onToggle?: EventHandler;
|
onToggle?: EventHandler;
|
||||||
onConfirm?: EventHandler;
|
onConfirm?: EventHandler;
|
||||||
onClickStep?: EventHandler;
|
onClickStep?: EventHandler;
|
||||||
|
onTouchstart?: EventHandler;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user