mirror of
https://gitee.com/vant-contrib/vant.git
synced 2025-04-06 03:57:59 +08:00
321 lines
7.2 KiB
JavaScript
321 lines
7.2 KiB
JavaScript
import Icon from '../icon';
|
|
import Cell from '../cell';
|
|
import { cellProps } from '../cell/shared';
|
|
import { preventDefault } from '../utils/dom/event';
|
|
import { getRootScrollTop } from '../utils/dom/scroll';
|
|
import { createNamespace, isObj, isDef, suffixPx } from '../utils';
|
|
import { isIOS } from '../utils/validate/system';
|
|
|
|
const [createComponent, bem] = createNamespace('field');
|
|
|
|
export default createComponent({
|
|
inheritAttrs: false,
|
|
|
|
props: {
|
|
...cellProps,
|
|
error: Boolean,
|
|
leftIcon: String,
|
|
rightIcon: String,
|
|
readonly: Boolean,
|
|
clearable: Boolean,
|
|
labelWidth: [String, Number],
|
|
labelClass: null,
|
|
labelAlign: String,
|
|
inputAlign: String,
|
|
autosize: [Boolean, Object],
|
|
errorMessage: String,
|
|
errorMessageAlign: String,
|
|
type: {
|
|
type: String,
|
|
default: 'text'
|
|
}
|
|
},
|
|
|
|
data() {
|
|
return {
|
|
focused: false
|
|
};
|
|
},
|
|
|
|
watch: {
|
|
value() {
|
|
this.$nextTick(this.adjustSize);
|
|
}
|
|
},
|
|
|
|
mounted() {
|
|
this.format();
|
|
this.$nextTick(this.adjustSize);
|
|
},
|
|
|
|
computed: {
|
|
showClear() {
|
|
return (
|
|
this.clearable &&
|
|
this.focused &&
|
|
this.value !== '' &&
|
|
isDef(this.value) &&
|
|
!this.readonly
|
|
);
|
|
},
|
|
|
|
listeners() {
|
|
const listeners = {
|
|
...this.$listeners,
|
|
input: this.onInput,
|
|
keypress: this.onKeypress,
|
|
focus: this.onFocus,
|
|
blur: this.onBlur
|
|
};
|
|
|
|
delete listeners.click;
|
|
|
|
return listeners;
|
|
},
|
|
|
|
labelStyle() {
|
|
const { labelWidth } = this;
|
|
if (labelWidth) {
|
|
return { width: suffixPx(labelWidth) };
|
|
}
|
|
}
|
|
},
|
|
|
|
methods: {
|
|
focus() {
|
|
if (this.$refs.input) {
|
|
this.$refs.input.focus();
|
|
}
|
|
},
|
|
|
|
blur() {
|
|
if (this.$refs.input) {
|
|
this.$refs.input.blur();
|
|
}
|
|
},
|
|
|
|
// native maxlength not work when type = number
|
|
format(target = this.$refs.input) {
|
|
if (!target) {
|
|
return;
|
|
}
|
|
|
|
let { value } = target;
|
|
const { maxlength } = this.$attrs;
|
|
|
|
if (this.type === 'number' && isDef(maxlength) && value.length > maxlength) {
|
|
value = value.slice(0, maxlength);
|
|
target.value = value;
|
|
}
|
|
|
|
return value;
|
|
},
|
|
|
|
onInput(event) {
|
|
// not update v-model when composing
|
|
if (event.target.composing) {
|
|
return;
|
|
}
|
|
|
|
this.$emit('input', this.format(event.target));
|
|
},
|
|
|
|
onFocus(event) {
|
|
this.focused = true;
|
|
this.$emit('focus', event);
|
|
|
|
// hack for safari
|
|
/* istanbul ignore if */
|
|
if (this.readonly) {
|
|
this.blur();
|
|
}
|
|
},
|
|
|
|
onBlur(event) {
|
|
this.focused = false;
|
|
this.$emit('blur', event);
|
|
|
|
// Hack for iOS12 page scroll
|
|
// https://developers.weixin.qq.com/community/develop/doc/00044ae90742f8c82fb78fcae56800
|
|
/* istanbul ignore next */
|
|
if (isIOS()) {
|
|
window.scrollTo(0, getRootScrollTop());
|
|
}
|
|
},
|
|
|
|
onClick(event) {
|
|
this.$emit('click', event);
|
|
},
|
|
|
|
onClickLeftIcon() {
|
|
this.$emit('click-left-icon');
|
|
},
|
|
|
|
onClickRightIcon() {
|
|
this.$emit('click-right-icon');
|
|
},
|
|
|
|
onClear(event) {
|
|
preventDefault(event);
|
|
this.$emit('input', '');
|
|
this.$emit('clear');
|
|
},
|
|
|
|
onKeypress(event) {
|
|
if (this.type === 'number') {
|
|
const { keyCode } = event;
|
|
const allowPoint = String(this.value).indexOf('.') === -1;
|
|
const isValidKey =
|
|
(keyCode >= 48 && keyCode <= 57) ||
|
|
(keyCode === 46 && allowPoint) ||
|
|
keyCode === 45;
|
|
|
|
if (!isValidKey) {
|
|
preventDefault(event);
|
|
}
|
|
}
|
|
|
|
// trigger blur after click keyboard search button
|
|
/* istanbul ignore next */
|
|
if (this.type === 'search' && event.keyCode === 13) {
|
|
this.blur();
|
|
}
|
|
|
|
this.$emit('keypress', event);
|
|
},
|
|
|
|
adjustSize() {
|
|
const { input } = this.$refs;
|
|
if (!(this.type === 'textarea' && this.autosize) || !input) {
|
|
return;
|
|
}
|
|
|
|
input.style.height = 'auto';
|
|
|
|
let height = input.scrollHeight;
|
|
if (isObj(this.autosize)) {
|
|
const { maxHeight, minHeight } = this.autosize;
|
|
if (maxHeight) {
|
|
height = Math.min(height, maxHeight);
|
|
}
|
|
if (minHeight) {
|
|
height = Math.max(height, minHeight);
|
|
}
|
|
}
|
|
|
|
if (height) {
|
|
input.style.height = height + 'px';
|
|
}
|
|
},
|
|
|
|
renderInput() {
|
|
const inputSlot = this.slots('input');
|
|
|
|
if (inputSlot) {
|
|
return (
|
|
<div class={bem('control', this.inputAlign)}>
|
|
{inputSlot}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const inputProps = {
|
|
ref: 'input',
|
|
class: bem('control', this.inputAlign),
|
|
domProps: {
|
|
value: this.value
|
|
},
|
|
attrs: {
|
|
...this.$attrs,
|
|
readonly: this.readonly
|
|
},
|
|
on: this.listeners,
|
|
// add model directive to skip IME composition
|
|
directives: [
|
|
{
|
|
name: 'model',
|
|
value: this.value
|
|
}
|
|
]
|
|
};
|
|
|
|
if (this.type === 'textarea') {
|
|
return <textarea {...inputProps} />;
|
|
}
|
|
|
|
return <input type={this.type} {...inputProps} />;
|
|
},
|
|
|
|
renderLeftIcon() {
|
|
const showLeftIcon = this.slots('left-icon') || this.leftIcon;
|
|
if (showLeftIcon) {
|
|
return (
|
|
<div class={bem('left-icon')} onClick={this.onClickLeftIcon}>
|
|
{this.slots('left-icon') || <Icon name={this.leftIcon} />}
|
|
</div>
|
|
);
|
|
}
|
|
},
|
|
|
|
renderRightIcon() {
|
|
const { slots } = this;
|
|
const showRightIcon = slots('right-icon') || this.rightIcon;
|
|
if (showRightIcon) {
|
|
return (
|
|
<div class={bem('right-icon')} onClick={this.onClickRightIcon}>
|
|
{slots('right-icon') || <Icon name={this.rightIcon} />}
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
},
|
|
|
|
render(h) {
|
|
const { slots, labelAlign } = this;
|
|
|
|
const scopedSlots = {
|
|
icon: this.renderLeftIcon
|
|
};
|
|
if (slots('label')) {
|
|
scopedSlots.title = () => slots('label');
|
|
}
|
|
|
|
return (
|
|
<Cell
|
|
icon={this.leftIcon}
|
|
size={this.size}
|
|
title={this.label}
|
|
center={this.center}
|
|
border={this.border}
|
|
isLink={this.isLink}
|
|
required={this.required}
|
|
clickable={this.clickable}
|
|
titleStyle={this.labelStyle}
|
|
titleClass={[bem('label', labelAlign), this.labelClass]}
|
|
class={bem({
|
|
error: this.error,
|
|
disabled: this.$attrs.disabled,
|
|
[`label-${labelAlign}`]: labelAlign,
|
|
'min-height': this.type === 'textarea' && !this.autosize
|
|
})}
|
|
scopedSlots={scopedSlots}
|
|
onClick={this.onClick}
|
|
>
|
|
<div class={bem('body')}>
|
|
{this.renderInput()}
|
|
{this.showClear && (
|
|
<Icon name="clear" class={bem('clear')} onTouchstart={this.onClear} />
|
|
)}
|
|
{this.renderRightIcon()}
|
|
{slots('button') && <div class={bem('button')}>{slots('button')}</div>}
|
|
</div>
|
|
{this.errorMessage && (
|
|
<div class={bem('error-message', this.errorMessageAlign)}>
|
|
{this.errorMessage}
|
|
</div>
|
|
)}
|
|
</Cell>
|
|
);
|
|
}
|
|
});
|