mirror of
https://gitee.com/vant-contrib/vant.git
synced 2025-04-06 03:57:59 +08:00
refactor(Field): refactor with composition-api
This commit is contained in:
parent
7e86fb9e43
commit
00dbf2cc50
@ -1,17 +1,15 @@
|
|||||||
import { watch, inject, WatchSource, getCurrentInstance } from 'vue';
|
import { watch, inject } from 'vue';
|
||||||
import { FIELD_KEY } from '../field';
|
import { FIELD_KEY } from '../field';
|
||||||
|
|
||||||
export function useParentField(watchSource: WatchSource) {
|
export function useParentField(getValue: () => unknown) {
|
||||||
const field = inject(FIELD_KEY, null) as any;
|
const field = inject(FIELD_KEY, null) as any;
|
||||||
|
|
||||||
if (field && !field.children) {
|
if (field && !field.childFieldValue.value) {
|
||||||
field.children = getCurrentInstance()!.proxy;
|
field.childFieldValue.value = getValue;
|
||||||
|
|
||||||
watch(watchSource, () => {
|
watch(getValue, () => {
|
||||||
if (field) {
|
field.resetValidation();
|
||||||
field.resetValidation();
|
field.validateWithTrigger('onChange');
|
||||||
field.validateWithTrigger('onChange');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,13 @@
|
|||||||
|
import {
|
||||||
|
ref,
|
||||||
|
watch,
|
||||||
|
provide,
|
||||||
|
computed,
|
||||||
|
nextTick,
|
||||||
|
reactive,
|
||||||
|
onMounted,
|
||||||
|
} from 'vue';
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
import { resetScroll } from '../utils/dom/reset-scroll';
|
import { resetScroll } from '../utils/dom/reset-scroll';
|
||||||
import { formatNumber } from '../utils/format/number';
|
import { formatNumber } from '../utils/format/number';
|
||||||
@ -11,28 +21,20 @@ import {
|
|||||||
createNamespace,
|
createNamespace,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
|
|
||||||
|
// Composition
|
||||||
|
import { useExpose } from '../composition/use-expose';
|
||||||
|
import { useParent } from '../composition/use-relation';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import Icon from '../icon';
|
import Icon from '../icon';
|
||||||
import Cell, { cellProps } from '../cell';
|
import Cell, { cellProps } from '../cell';
|
||||||
|
import { FORM_KEY } from '../form';
|
||||||
|
|
||||||
const [createComponent, bem] = createNamespace('field');
|
const [createComponent, bem] = createNamespace('field');
|
||||||
|
|
||||||
export const FIELD_KEY = 'vanField';
|
export const FIELD_KEY = 'vanField';
|
||||||
|
|
||||||
export default createComponent({
|
export default createComponent({
|
||||||
provide() {
|
|
||||||
return {
|
|
||||||
vanField: this,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
inject: {
|
|
||||||
vanForm: {
|
|
||||||
from: 'vanForm',
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
...cellProps,
|
...cellProps,
|
||||||
rows: [Number, String],
|
rows: [Number, String],
|
||||||
@ -91,91 +93,37 @@ export default createComponent({
|
|||||||
'update:modelValue',
|
'update:modelValue',
|
||||||
],
|
],
|
||||||
|
|
||||||
data() {
|
setup(props, { emit, slots }) {
|
||||||
return {
|
const state = reactive({
|
||||||
focused: false,
|
focused: false,
|
||||||
validateFailed: false,
|
validateFailed: false,
|
||||||
validateMessage: '',
|
validateMessage: '',
|
||||||
};
|
});
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
const root = ref();
|
||||||
modelValue(val) {
|
const inputRef = ref();
|
||||||
this.updateValue(val);
|
const childFieldValue = ref();
|
||||||
this.resetValidation();
|
|
||||||
this.validateWithTrigger('onChange');
|
|
||||||
this.$nextTick(this.adjustSize);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
const showClear = computed(() => {
|
||||||
this.updateValue(this.modelValue, this.formatTrigger);
|
if (props.clearable && !props.readonly) {
|
||||||
this.$nextTick(this.adjustSize);
|
const hasValue = isDef(props.modelValue) && props.modelValue !== '';
|
||||||
|
|
||||||
if (this.vanForm) {
|
|
||||||
this.vanForm.addField(this);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
beforeUnmount() {
|
|
||||||
if (this.vanForm) {
|
|
||||||
this.vanForm.removeField(this);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
showClear() {
|
|
||||||
if (this.clearable && !this.readonly) {
|
|
||||||
const hasValue = isDef(this.modelValue) && this.modelValue !== '';
|
|
||||||
const trigger =
|
const trigger =
|
||||||
this.clearTrigger === 'always' ||
|
props.clearTrigger === 'always' ||
|
||||||
(this.clearTrigger === 'focus' && this.focused);
|
(props.clearTrigger === 'focus' && state.focused);
|
||||||
|
|
||||||
return hasValue && trigger;
|
return hasValue && trigger;
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
|
|
||||||
showError() {
|
const formValue = computed(() => {
|
||||||
if (this.error !== null) {
|
if (childFieldValue.value && slots.input) {
|
||||||
return this.error;
|
return childFieldValue.value();
|
||||||
}
|
}
|
||||||
if (this.vanForm && this.vanForm.showError && this.validateFailed) {
|
return props.modelValue;
|
||||||
return true;
|
});
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
labelStyle() {
|
const runValidator = (value, rule) =>
|
||||||
const labelWidth = this.getProp('labelWidth');
|
new Promise((resolve) => {
|
||||||
if (labelWidth) {
|
|
||||||
return { width: addUnit(labelWidth) };
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
formValue() {
|
|
||||||
if (this.children && this.$slots.input) {
|
|
||||||
return this.children.modelValue;
|
|
||||||
}
|
|
||||||
return this.modelValue;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
// @exposed-api
|
|
||||||
focus() {
|
|
||||||
if (this.$refs.input) {
|
|
||||||
this.$refs.input.focus();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// @exposed-api
|
|
||||||
blur() {
|
|
||||||
if (this.$refs.input) {
|
|
||||||
this.$refs.input.blur();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
runValidator(value, rule) {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
const returnVal = rule.validator(value, rule);
|
const returnVal = rule.validator(value, rule);
|
||||||
|
|
||||||
if (isPromise(returnVal)) {
|
if (isPromise(returnVal)) {
|
||||||
@ -184,9 +132,8 @@ export default createComponent({
|
|||||||
|
|
||||||
resolve(returnVal);
|
resolve(returnVal);
|
||||||
});
|
});
|
||||||
},
|
|
||||||
|
|
||||||
isEmptyValue(value) {
|
const isEmptyValue = (value) => {
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
return !value.length;
|
return !value.length;
|
||||||
}
|
}
|
||||||
@ -194,85 +141,97 @@ export default createComponent({
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return !value;
|
return !value;
|
||||||
},
|
};
|
||||||
|
|
||||||
runSyncRule(value, rule) {
|
const runSyncRule = (value, rule) => {
|
||||||
if (rule.required && this.isEmptyValue(value)) {
|
if (rule.required && isEmptyValue(value)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (rule.pattern && !rule.pattern.test(value)) {
|
if (rule.pattern && !rule.pattern.test(value)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
};
|
||||||
|
|
||||||
getRuleMessage(value, rule) {
|
const getRuleMessage = (value, rule) => {
|
||||||
const { message } = rule;
|
const { message } = rule;
|
||||||
|
|
||||||
if (isFunction(message)) {
|
if (isFunction(message)) {
|
||||||
return message(value, rule);
|
return message(value, rule);
|
||||||
}
|
}
|
||||||
|
|
||||||
return message;
|
return message;
|
||||||
},
|
};
|
||||||
|
|
||||||
runRules(rules) {
|
const runRules = (rules) =>
|
||||||
return rules.reduce(
|
rules.reduce(
|
||||||
(promise, rule) =>
|
(promise, rule) =>
|
||||||
promise.then(() => {
|
promise.then(() => {
|
||||||
if (this.validateFailed) {
|
if (state.validateFailed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let value = this.formValue;
|
let { value } = formValue;
|
||||||
|
|
||||||
if (rule.formatter) {
|
if (rule.formatter) {
|
||||||
value = rule.formatter(value, rule);
|
value = rule.formatter(value, rule);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.runSyncRule(value, rule)) {
|
if (!runSyncRule(value, rule)) {
|
||||||
this.validateFailed = true;
|
state.validateFailed = true;
|
||||||
this.validateMessage = this.getRuleMessage(value, rule);
|
state.validateMessage = getRuleMessage(value, rule);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rule.validator) {
|
if (rule.validator) {
|
||||||
return this.runValidator(value, rule).then((result) => {
|
return runValidator(value, rule).then((result) => {
|
||||||
if (result === false) {
|
if (result === false) {
|
||||||
this.validateFailed = true;
|
state.validateFailed = true;
|
||||||
this.validateMessage = this.getRuleMessage(value, rule);
|
state.validateMessage = getRuleMessage(value, rule);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
Promise.resolve()
|
Promise.resolve()
|
||||||
);
|
);
|
||||||
},
|
|
||||||
|
|
||||||
validate(rules = this.rules) {
|
const resetValidation = () => {
|
||||||
return new Promise((resolve) => {
|
if (state.validateFailed) {
|
||||||
|
state.validateFailed = false;
|
||||||
|
state.validateMessage = '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const validate = (rules = props.rules) =>
|
||||||
|
new Promise((resolve) => {
|
||||||
if (!rules) {
|
if (!rules) {
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.resetValidation();
|
resetValidation();
|
||||||
this.runRules(rules).then(() => {
|
runRules(rules).then(() => {
|
||||||
if (this.validateFailed) {
|
if (state.validateFailed) {
|
||||||
resolve({
|
resolve({
|
||||||
name: this.name,
|
name: props.name,
|
||||||
message: this.validateMessage,
|
message: state.validateMessage,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
|
||||||
|
|
||||||
validateWithTrigger(trigger) {
|
const { parent: form } = useParent(FORM_KEY, {
|
||||||
if (this.vanForm && this.rules) {
|
root,
|
||||||
const defaultTrigger = this.vanForm.validateTrigger === trigger;
|
props,
|
||||||
const rules = this.rules.filter((rule) => {
|
validate,
|
||||||
|
formValue,
|
||||||
|
resetValidation,
|
||||||
|
});
|
||||||
|
|
||||||
|
const validateWithTrigger = (trigger) => {
|
||||||
|
if (form && props.rules) {
|
||||||
|
const defaultTrigger = form.validateTrigger === trigger;
|
||||||
|
const rules = props.rules.filter((rule) => {
|
||||||
if (rule.trigger) {
|
if (rule.trigger) {
|
||||||
return rule.trigger === trigger;
|
return rule.trigger === trigger;
|
||||||
}
|
}
|
||||||
@ -280,134 +239,160 @@ export default createComponent({
|
|||||||
return defaultTrigger;
|
return defaultTrigger;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.validate(rules);
|
validate(rules);
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
|
|
||||||
resetValidation() {
|
const updateValue = (value, trigger = 'onChange') => {
|
||||||
if (this.validateFailed) {
|
|
||||||
this.validateFailed = false;
|
|
||||||
this.validateMessage = '';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
updateValue(value, trigger = 'onChange') {
|
|
||||||
value = isDef(value) ? String(value) : '';
|
value = isDef(value) ? String(value) : '';
|
||||||
|
|
||||||
// native maxlength not work when type is number
|
// native maxlength not work when type is number
|
||||||
const { maxlength } = this;
|
const { maxlength } = props;
|
||||||
if (isDef(maxlength) && value.length > maxlength) {
|
if (isDef(maxlength) && value.length > maxlength) {
|
||||||
value = value.slice(0, maxlength);
|
value = value.slice(0, maxlength);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.type === 'number' || this.type === 'digit') {
|
if (props.type === 'number' || props.type === 'digit') {
|
||||||
const isNumber = this.type === 'number';
|
const isNumber = props.type === 'number';
|
||||||
value = formatNumber(value, isNumber, isNumber);
|
value = formatNumber(value, isNumber, isNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.formatter && trigger === this.formatTrigger) {
|
if (props.formatter && trigger === props.formatTrigger) {
|
||||||
value = this.formatter(value);
|
value = props.formatter(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { input } = this.$refs;
|
if (inputRef.value && value !== inputRef.value.value) {
|
||||||
if (input && value !== input.value) {
|
inputRef.value.value = value;
|
||||||
input.value = value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value !== this.modelValue) {
|
if (value !== props.modelValue) {
|
||||||
this.$emit('update:modelValue', value);
|
emit('update:modelValue', value);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
this.currentValue = value;
|
const onInput = (event) => {
|
||||||
},
|
// skip update value when composing
|
||||||
|
if (!event.target.composing) {
|
||||||
onInput(event) {
|
updateValue(event.target.value);
|
||||||
// not update v-model when composing
|
|
||||||
if (event.target.composing) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
this.updateValue(event.target.value);
|
const focus = () => {
|
||||||
},
|
if (inputRef.value) {
|
||||||
|
inputRef.value.focus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
onFocus(event) {
|
const blur = () => {
|
||||||
this.focused = true;
|
if (inputRef.value) {
|
||||||
this.$emit('focus', event);
|
inputRef.value.blur();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFocus = (event) => {
|
||||||
|
state.focused = true;
|
||||||
|
emit('focus', event);
|
||||||
|
|
||||||
// readonly not work in lagacy mobile safari
|
// readonly not work in lagacy mobile safari
|
||||||
/* istanbul ignore if */
|
if (props.readonly) {
|
||||||
if (this.readonly) {
|
blur();
|
||||||
this.blur();
|
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
|
|
||||||
onBlur(event) {
|
const onBlur = (event) => {
|
||||||
this.focused = false;
|
state.focused = false;
|
||||||
this.updateValue(this.modelValue, 'onBlur');
|
updateValue(props.modelValue, 'onBlur');
|
||||||
this.$emit('blur', event);
|
emit('blur', event);
|
||||||
this.validateWithTrigger('onBlur');
|
validateWithTrigger('onBlur');
|
||||||
resetScroll();
|
resetScroll();
|
||||||
},
|
};
|
||||||
|
|
||||||
onClickInput(event) {
|
const onClickInput = (event) => {
|
||||||
this.$emit('click-input', event);
|
emit('click-input', event);
|
||||||
},
|
};
|
||||||
|
|
||||||
onClickLeftIcon(event) {
|
const onClickLeftIcon = (event) => {
|
||||||
this.$emit('click-left-icon', event);
|
emit('click-left-icon', event);
|
||||||
},
|
};
|
||||||
|
|
||||||
onClickRightIcon(event) {
|
const onClickRightIcon = (event) => {
|
||||||
this.$emit('click-right-icon', event);
|
emit('click-right-icon', event);
|
||||||
},
|
};
|
||||||
|
|
||||||
onClear(event) {
|
const onClear = (event) => {
|
||||||
preventDefault(event);
|
preventDefault(event);
|
||||||
this.$emit('update:modelValue', '');
|
emit('update:modelValue', '');
|
||||||
this.$emit('clear', event);
|
emit('clear', event);
|
||||||
},
|
};
|
||||||
|
|
||||||
onKeypress(event) {
|
const showError = computed(() => {
|
||||||
|
if (typeof props.error === 'boolean') {
|
||||||
|
return props.error;
|
||||||
|
}
|
||||||
|
if (form && form.showError && state.validateFailed) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const getProp = (key) => {
|
||||||
|
if (isDef(props[key])) {
|
||||||
|
return props[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (form && isDef(form[key])) {
|
||||||
|
return form[key];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const labelStyle = computed(() => {
|
||||||
|
const labelWidth = getProp('labelWidth');
|
||||||
|
if (labelWidth) {
|
||||||
|
return { width: addUnit(labelWidth) };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const onKeypress = (event) => {
|
||||||
const ENTER_CODE = 13;
|
const ENTER_CODE = 13;
|
||||||
|
|
||||||
if (event.keyCode === ENTER_CODE) {
|
if (event.keyCode === ENTER_CODE) {
|
||||||
const submitOnEnter = this.getProp('submitOnEnter');
|
const submitOnEnter = getProp('submitOnEnter');
|
||||||
if (!submitOnEnter && this.type !== 'textarea') {
|
if (!submitOnEnter && props.type !== 'textarea') {
|
||||||
preventDefault(event);
|
preventDefault(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
// trigger blur after click keyboard search button
|
// trigger blur after click keyboard search button
|
||||||
if (this.type === 'search') {
|
if (props.type === 'search') {
|
||||||
this.blur();
|
blur();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$emit('keypress', event);
|
emit('keypress', event);
|
||||||
},
|
};
|
||||||
|
|
||||||
onCompositionStart(event) {
|
const onCompositionStart = (event) => {
|
||||||
event.target.composing = true;
|
event.target.composing = true;
|
||||||
},
|
};
|
||||||
|
|
||||||
onCompositionEnd(event) {
|
const onCompositionEnd = (event) => {
|
||||||
const { target } = event;
|
const { target } = event;
|
||||||
if (target.composing) {
|
if (target.composing) {
|
||||||
target.composing = false;
|
target.composing = false;
|
||||||
trigger(target, 'input');
|
trigger(target, 'input');
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
|
|
||||||
adjustSize() {
|
const adjustSize = () => {
|
||||||
const { input } = this.$refs;
|
const input = inputRef.value;
|
||||||
if (!(this.type === 'textarea' && this.autosize) || !input) {
|
|
||||||
|
if (!(props.type === 'textarea' && props.autosize) || !input) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
input.style.height = 'auto';
|
input.style.height = 'auto';
|
||||||
|
|
||||||
let height = input.scrollHeight;
|
let height = input.scrollHeight;
|
||||||
if (isObject(this.autosize)) {
|
if (isObject(props.autosize)) {
|
||||||
const { maxHeight, minHeight } = this.autosize;
|
const { maxHeight, minHeight } = props.autosize;
|
||||||
if (maxHeight) {
|
if (maxHeight) {
|
||||||
height = Math.min(height, maxHeight);
|
height = Math.min(height, maxHeight);
|
||||||
}
|
}
|
||||||
@ -419,43 +404,44 @@ export default createComponent({
|
|||||||
if (height) {
|
if (height) {
|
||||||
input.style.height = height + 'px';
|
input.style.height = height + 'px';
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
|
|
||||||
genInput() {
|
const renderInput = () => {
|
||||||
const { type } = this;
|
const inputAlign = getProp('inputAlign');
|
||||||
const inputAlign = this.getProp('inputAlign');
|
|
||||||
|
|
||||||
if (this.$slots.input) {
|
if (slots.input) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class={bem('control', [inputAlign, 'custom'])}
|
class={bem('control', [inputAlign, 'custom'])}
|
||||||
onClick={this.onClickInput}
|
onClick={onClickInput}
|
||||||
>
|
>
|
||||||
{this.$slots.input()}
|
{slots.input()}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const inputProps = {
|
const inputProps = {
|
||||||
ref: 'input',
|
ref: inputRef,
|
||||||
name: this.name,
|
name: props.name,
|
||||||
rows: this.rows,
|
rows: props.rows,
|
||||||
style: null,
|
style: null,
|
||||||
class: bem('control', inputAlign),
|
class: bem('control', inputAlign),
|
||||||
value: this.modelValue,
|
value: props.modelValue,
|
||||||
disabled: this.disabled,
|
disabled: props.disabled,
|
||||||
readonly: this.readonly,
|
readonly: props.readonly,
|
||||||
placeholder: this.placeholder,
|
placeholder: props.placeholder,
|
||||||
onBlur: this.onBlur,
|
onBlur,
|
||||||
onFocus: this.onFocus,
|
onFocus,
|
||||||
onInput: this.onInput,
|
onInput,
|
||||||
onClick: this.onClickInput,
|
onClick: onClickInput,
|
||||||
onChange: this.onCompositionEnd,
|
onChange: onCompositionEnd,
|
||||||
onKeypress: this.onKeypress,
|
onKeypress,
|
||||||
onCompositionend: this.onCompositionEnd,
|
onCompositionend: onCompositionEnd,
|
||||||
onCompositionstart: this.onCompositionStart,
|
onCompositionstart: onCompositionStart,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { type } = props;
|
||||||
|
|
||||||
if (type === 'textarea') {
|
if (type === 'textarea') {
|
||||||
return <textarea {...inputProps} />;
|
return <textarea {...inputProps} />;
|
||||||
}
|
}
|
||||||
@ -476,137 +462,144 @@ export default createComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return <input type={inputType} inputmode={inputMode} {...inputProps} />;
|
return <input type={inputType} inputmode={inputMode} {...inputProps} />;
|
||||||
},
|
};
|
||||||
|
|
||||||
genLeftIcon() {
|
const renderLeftIcon = () => {
|
||||||
const leftIconSlot = this.$slots['left-icon'];
|
const leftIconSlot = slots['left-icon'];
|
||||||
|
|
||||||
if (this.leftIcon || leftIconSlot) {
|
if (props.leftIcon || leftIconSlot) {
|
||||||
return (
|
return (
|
||||||
<div class={bem('left-icon')} onClick={this.onClickLeftIcon}>
|
<div class={bem('left-icon')} onClick={onClickLeftIcon}>
|
||||||
{leftIconSlot ? (
|
{leftIconSlot ? (
|
||||||
leftIconSlot()
|
leftIconSlot()
|
||||||
) : (
|
) : (
|
||||||
<Icon name={this.leftIcon} classPrefix={this.iconPrefix} />
|
<Icon name={props.leftIcon} classPrefix={props.iconPrefix} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
|
|
||||||
genRightIcon() {
|
const renderRightIcon = () => {
|
||||||
const rightIconSlot = this.$slots['right-icon'];
|
const rightIconSlot = slots['right-icon'];
|
||||||
|
|
||||||
if (this.rightIcon || rightIconSlot) {
|
if (props.rightIcon || rightIconSlot) {
|
||||||
return (
|
return (
|
||||||
<div class={bem('right-icon')} onClick={this.onClickRightIcon}>
|
<div class={bem('right-icon')} onClick={onClickRightIcon}>
|
||||||
{rightIconSlot ? (
|
{rightIconSlot ? (
|
||||||
rightIconSlot()
|
rightIconSlot()
|
||||||
) : (
|
) : (
|
||||||
<Icon name={this.rightIcon} classPrefix={this.iconPrefix} />
|
<Icon name={props.rightIcon} classPrefix={props.iconPrefix} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
|
|
||||||
genWordLimit() {
|
|
||||||
if (this.showWordLimit && this.maxlength) {
|
|
||||||
const count = (this.modelValue || '').length;
|
|
||||||
|
|
||||||
|
const renderWordLimit = () => {
|
||||||
|
if (props.showWordLimit && props.maxlength) {
|
||||||
|
const count = (props.modelValue || '').length;
|
||||||
return (
|
return (
|
||||||
<div class={bem('word-limit')}>
|
<div class={bem('word-limit')}>
|
||||||
<span class={bem('word-num')}>{count}</span>/{this.maxlength}
|
<span class={bem('word-num')}>{count}</span>/{props.maxlength}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
|
|
||||||
genMessage() {
|
const renderMessage = () => {
|
||||||
if (this.vanForm && this.vanForm.showErrorMessage === false) {
|
if (form && form.showErrorMessage === false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = this.errorMessage || this.validateMessage;
|
const message = props.errorMessage || state.validateMessage;
|
||||||
|
|
||||||
if (message) {
|
if (message) {
|
||||||
const errorMessageAlign = this.getProp('errorMessageAlign');
|
const errorMessageAlign = getProp('errorMessageAlign');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={bem('error-message', errorMessageAlign)}>{message}</div>
|
<div class={bem('error-message', errorMessageAlign)}>{message}</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
|
|
||||||
getProp(key) {
|
const renderLabel = () => {
|
||||||
if (isDef(this[key])) {
|
const colon = getProp('colon') ? ':' : '';
|
||||||
return this[key];
|
|
||||||
|
if (slots.label) {
|
||||||
|
return [slots.label(), colon];
|
||||||
}
|
}
|
||||||
|
if (props.label) {
|
||||||
if (this.vanForm && isDef(this.vanForm[key])) {
|
return <span>{props.label + colon}</span>;
|
||||||
return this.vanForm[key];
|
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
|
|
||||||
genLabel() {
|
useExpose({
|
||||||
const colon = this.getProp('colon') ? ':' : '';
|
focus,
|
||||||
|
blur,
|
||||||
|
});
|
||||||
|
|
||||||
if (this.$slots.label) {
|
provide(FIELD_KEY, {
|
||||||
return [this.$slots.label(), colon];
|
childFieldValue,
|
||||||
|
resetValidation,
|
||||||
|
validateWithTrigger,
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(value) => {
|
||||||
|
updateValue(value);
|
||||||
|
resetValidation();
|
||||||
|
validateWithTrigger('onChange');
|
||||||
|
nextTick(adjustSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.label) {
|
|
||||||
return <span>{this.label + colon}</span>;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const slots = this.$slots;
|
|
||||||
const labelAlign = this.getProp('labelAlign');
|
|
||||||
const Label = this.genLabel();
|
|
||||||
const LeftIcon = this.genLeftIcon();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Cell
|
|
||||||
v-slots={{
|
|
||||||
icon: LeftIcon ? () => LeftIcon : null,
|
|
||||||
title: Label ? () => Label : null,
|
|
||||||
extra: slots.extra,
|
|
||||||
}}
|
|
||||||
icon={this.leftIcon}
|
|
||||||
size={this.size}
|
|
||||||
class={bem({
|
|
||||||
error: this.showError,
|
|
||||||
disabled: this.disabled,
|
|
||||||
[`label-${labelAlign}`]: labelAlign,
|
|
||||||
'min-height': this.type === 'textarea' && !this.autosize,
|
|
||||||
})}
|
|
||||||
center={this.center}
|
|
||||||
border={this.border}
|
|
||||||
isLink={this.isLink}
|
|
||||||
required={this.required}
|
|
||||||
clickable={this.clickable}
|
|
||||||
titleStyle={this.labelStyle}
|
|
||||||
valueClass={bem('value')}
|
|
||||||
titleClass={[bem('label', labelAlign), this.labelClass]}
|
|
||||||
arrowDirection={this.arrowDirection}
|
|
||||||
>
|
|
||||||
<div class={bem('body')}>
|
|
||||||
{this.genInput()}
|
|
||||||
{this.showClear && (
|
|
||||||
<Icon
|
|
||||||
name="clear"
|
|
||||||
class={bem('clear')}
|
|
||||||
onTouchstart={this.onClear}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{this.genRightIcon()}
|
|
||||||
{slots.button && <div class={bem('button')}>{slots.button()}</div>}
|
|
||||||
</div>
|
|
||||||
{this.genWordLimit()}
|
|
||||||
{this.genMessage()}
|
|
||||||
</Cell>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
updateValue(props.modelValue, props.formatTrigger);
|
||||||
|
nextTick(adjustSize);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
const labelAlign = getProp('labelAlign');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Cell
|
||||||
|
v-slots={{
|
||||||
|
icon: renderLeftIcon,
|
||||||
|
title: renderLabel,
|
||||||
|
extra: slots.extra,
|
||||||
|
}}
|
||||||
|
ref={root}
|
||||||
|
size={props.size}
|
||||||
|
icon={props.leftIcon}
|
||||||
|
class={bem({
|
||||||
|
error: showError.value,
|
||||||
|
disabled: props.disabled,
|
||||||
|
[`label-${labelAlign}`]: labelAlign,
|
||||||
|
'min-height': props.type === 'textarea' && !props.autosize,
|
||||||
|
})}
|
||||||
|
center={props.center}
|
||||||
|
border={props.border}
|
||||||
|
isLink={props.isLink}
|
||||||
|
required={props.required}
|
||||||
|
clickable={props.clickable}
|
||||||
|
titleStyle={labelStyle.value}
|
||||||
|
valueClass={bem('value')}
|
||||||
|
titleClass={[bem('label', labelAlign), props.labelClass]}
|
||||||
|
arrowDirection={props.arrowDirection}
|
||||||
|
>
|
||||||
|
<div class={bem('body')}>
|
||||||
|
{renderInput()}
|
||||||
|
{showClear.value && (
|
||||||
|
<Icon name="clear" class={bem('clear')} onTouchstart={onClear} />
|
||||||
|
)}
|
||||||
|
{renderRightIcon()}
|
||||||
|
{slots.button && <div class={bem('button')}>{slots.button()}</div>}
|
||||||
|
</div>
|
||||||
|
{renderWordLimit()}
|
||||||
|
{renderMessage()}
|
||||||
|
</Cell>
|
||||||
|
);
|
||||||
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { createNamespace } from '../utils';
|
import { createNamespace } from '../utils';
|
||||||
// import { sortChildren } from '../utils/vnodes';
|
|
||||||
|
|
||||||
const [createComponent, bem] = createNamespace('form');
|
const [createComponent, bem] = createNamespace('form');
|
||||||
|
|
||||||
|
export const FORM_KEY = 'vanForm';
|
||||||
|
|
||||||
export default createComponent({
|
export default createComponent({
|
||||||
props: {
|
props: {
|
||||||
colon: Boolean,
|
colon: Boolean,
|
||||||
@ -34,13 +35,13 @@ export default createComponent({
|
|||||||
|
|
||||||
provide() {
|
provide() {
|
||||||
return {
|
return {
|
||||||
vanForm: this,
|
[FORM_KEY]: this,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
fields: [],
|
children: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -49,7 +50,7 @@ export default createComponent({
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const errors = [];
|
const errors = [];
|
||||||
|
|
||||||
this.fields
|
this.children
|
||||||
.reduce(
|
.reduce(
|
||||||
(promise, field) =>
|
(promise, field) =>
|
||||||
promise.then(() => {
|
promise.then(() => {
|
||||||
@ -75,7 +76,7 @@ export default createComponent({
|
|||||||
|
|
||||||
validateAll() {
|
validateAll() {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
Promise.all(this.fields.map((item) => item.validate())).then(
|
Promise.all(this.children.map((item) => item.validate())).then(
|
||||||
(errors) => {
|
(errors) => {
|
||||||
errors = errors.filter((item) => item);
|
errors = errors.filter((item) => item);
|
||||||
|
|
||||||
@ -98,7 +99,7 @@ export default createComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
validateField(name) {
|
validateField(name) {
|
||||||
const matched = this.fields.filter((item) => item.name === name);
|
const matched = this.children.filter((item) => item.props.name === name);
|
||||||
|
|
||||||
if (matched.length) {
|
if (matched.length) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@ -117,8 +118,8 @@ export default createComponent({
|
|||||||
|
|
||||||
// @exposed-api
|
// @exposed-api
|
||||||
resetValidation(name) {
|
resetValidation(name) {
|
||||||
this.fields.forEach((item) => {
|
this.children.forEach((item) => {
|
||||||
if (!name || item.name === name) {
|
if (!name || item.props.name === name) {
|
||||||
item.resetValidation();
|
item.resetValidation();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -126,28 +127,18 @@ export default createComponent({
|
|||||||
|
|
||||||
// @exposed-api
|
// @exposed-api
|
||||||
scrollToField(name, options) {
|
scrollToField(name, options) {
|
||||||
this.fields.some((item) => {
|
this.children.some((item) => {
|
||||||
if (item.name === name) {
|
if (item.props.name === name) {
|
||||||
item.$el.scrollIntoView(options);
|
item.root.value.scrollIntoView(options);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
addField(field) {
|
|
||||||
this.fields.push(field);
|
|
||||||
// TODO
|
|
||||||
// sortChildren(this.fields, this);
|
|
||||||
},
|
|
||||||
|
|
||||||
removeField(field) {
|
|
||||||
this.fields = this.fields.filter((item) => item !== field);
|
|
||||||
},
|
|
||||||
|
|
||||||
getValues() {
|
getValues() {
|
||||||
return this.fields.reduce((form, field) => {
|
return this.children.reduce((form, field) => {
|
||||||
form[field.name] = field.formValue;
|
form[field.props.name] = field.formValue;
|
||||||
return form;
|
return form;
|
||||||
}, {});
|
}, {});
|
||||||
},
|
},
|
||||||
|
@ -9,7 +9,7 @@ export const FieldMixin = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
value() {
|
modelValue() {
|
||||||
const field = this.vanField;
|
const field = this.vanField;
|
||||||
|
|
||||||
if (field) {
|
if (field) {
|
||||||
@ -22,8 +22,8 @@ export const FieldMixin = {
|
|||||||
created() {
|
created() {
|
||||||
const field = this.vanField;
|
const field = this.vanField;
|
||||||
|
|
||||||
if (field && !field.children) {
|
if (field && !field.childFieldValue.value) {
|
||||||
field.children = this;
|
field.childFieldValue.value = () => this.modelValue;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user