[improvement] rename packages dir to src (#3659)
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
<template>
|
||||
<demo-section>
|
||||
<demo-block :title="$t('basicUsage')">
|
||||
<van-cell-group>
|
||||
<van-field
|
||||
v-model="value"
|
||||
:placeholder="$t('usernamePlaceholder')"
|
||||
/>
|
||||
</van-cell-group>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('title2')">
|
||||
<van-cell-group>
|
||||
<van-field
|
||||
v-model="username"
|
||||
:label="$t('username')"
|
||||
:placeholder="$t('usernamePlaceholder')"
|
||||
required
|
||||
clearable
|
||||
right-icon="question-o"
|
||||
@click-right-icon="$toast('question')"
|
||||
/>
|
||||
|
||||
<van-field
|
||||
v-model="password"
|
||||
type="password"
|
||||
:label="$t('password')"
|
||||
:placeholder="$t('passwordPlaceholder')"
|
||||
required
|
||||
/>
|
||||
</van-cell-group>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('title3')">
|
||||
<van-cell-group>
|
||||
<van-field
|
||||
:value="$t('inputDisabled')"
|
||||
:label="$t('username')"
|
||||
left-icon="contact"
|
||||
disabled
|
||||
/>
|
||||
</van-cell-group>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('title4')">
|
||||
<van-cell-group>
|
||||
<van-field
|
||||
v-model="username2"
|
||||
:label="$t('username')"
|
||||
:placeholder="$t('usernamePlaceholder')"
|
||||
error
|
||||
/>
|
||||
<van-field
|
||||
v-model="phone"
|
||||
:label="$t('phone')"
|
||||
:placeholder="$t('phonePlaceholder')"
|
||||
:error-message="$t('phoneError')"
|
||||
/>
|
||||
</van-cell-group>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('title5')">
|
||||
<van-cell-group>
|
||||
<van-field
|
||||
v-model="message"
|
||||
:label="$t('message')"
|
||||
type="textarea"
|
||||
:placeholder="$t('messagePlaceholder')"
|
||||
rows="1"
|
||||
autosize
|
||||
/>
|
||||
</van-cell-group>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('title6')">
|
||||
<van-cell-group>
|
||||
<van-field
|
||||
center
|
||||
clearable
|
||||
v-model="sms"
|
||||
:label="$t('sms')"
|
||||
:placeholder="$t('smsPlaceholder')"
|
||||
>
|
||||
<template #button>
|
||||
<van-button
|
||||
size="small"
|
||||
type="primary"
|
||||
>
|
||||
{{ $t('sendSMS') }}
|
||||
</van-button>
|
||||
</template>
|
||||
</van-field>
|
||||
</van-cell-group>
|
||||
</demo-block>
|
||||
</demo-section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
i18n: {
|
||||
'zh-CN': {
|
||||
title2: '自定义类型',
|
||||
title3: '禁用输入框',
|
||||
title4: '错误提示',
|
||||
title5: '高度自适应',
|
||||
title6: '插入按钮',
|
||||
message: '留言',
|
||||
phone: '手机号',
|
||||
sms: '短信验证码',
|
||||
sendSMS: '发送验证码',
|
||||
smsPlaceholder: '请输入短信验证码',
|
||||
phonePlaceholder: '请输入手机号',
|
||||
messagePlaceholder: '请输入留言',
|
||||
inputDisabled: '输入框已禁用',
|
||||
phoneError: '手机号格式错误'
|
||||
},
|
||||
'en-US': {
|
||||
title2: 'Custom type',
|
||||
title3: 'Disabled',
|
||||
title4: 'Error info',
|
||||
title5: 'Auto resize',
|
||||
title6: 'Insert button',
|
||||
message: 'Message',
|
||||
phone: 'Phone',
|
||||
sms: 'SMS',
|
||||
sendSMS: 'Send SMS',
|
||||
smsPlaceholder: 'SMS',
|
||||
phonePlaceholder: 'Phone',
|
||||
messagePlaceholder: 'Message',
|
||||
inputDisabled: 'Disabled',
|
||||
phoneError: 'Invalid phone'
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
sms: '',
|
||||
value: '',
|
||||
password: '',
|
||||
username: '',
|
||||
username2: '',
|
||||
message: '',
|
||||
phone: '1365577'
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import '../../style/var';
|
||||
|
||||
.demo-field {
|
||||
padding-bottom: 30px;
|
||||
|
||||
.van-field__right-icon .van-icon {
|
||||
color: @blue;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,177 @@
|
||||
# Field
|
||||
|
||||
### Install
|
||||
|
||||
``` javascript
|
||||
import { Field } from 'vant';
|
||||
|
||||
Vue.use(Field);
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
The value of field is bound with v-model.
|
||||
|
||||
```html
|
||||
<van-cell-group>
|
||||
<van-field v-model="value" placeholder="Username" />
|
||||
</van-cell-group>
|
||||
```
|
||||
|
||||
### Custom type
|
||||
|
||||
Use `type` prop to custom diffrent type fields.
|
||||
|
||||
```html
|
||||
<van-cell-group>
|
||||
<van-field
|
||||
v-model="username"
|
||||
required
|
||||
clearable
|
||||
label="Username"
|
||||
right-icon="question-o"
|
||||
placeholder="Username"
|
||||
@click-right-icon="$toast('question')"
|
||||
/>
|
||||
|
||||
<van-field
|
||||
v-model="password"
|
||||
type="password"
|
||||
label="Password"
|
||||
placeholder="Password"
|
||||
required
|
||||
/>
|
||||
</van-cell-group>
|
||||
```
|
||||
|
||||
### Disabled
|
||||
|
||||
```html
|
||||
<van-cell-group>
|
||||
<van-field
|
||||
value="Disabled"
|
||||
label="Username"
|
||||
left-icon="contact"
|
||||
disabled
|
||||
/>
|
||||
</van-cell-group>
|
||||
```
|
||||
|
||||
### Error info
|
||||
|
||||
Use `error` or `error-message` to show error info
|
||||
|
||||
```html
|
||||
<van-cell-group>
|
||||
<van-field
|
||||
v-model="username"
|
||||
label="Username"
|
||||
placeholder="Username"
|
||||
error
|
||||
/>
|
||||
<van-field
|
||||
v-model="phone"
|
||||
label="Phone"
|
||||
placeholder="Phone"
|
||||
error-message="Invalid phone"
|
||||
/>
|
||||
</van-cell-group>
|
||||
```
|
||||
|
||||
### Auto resize
|
||||
|
||||
Textarea Field can be auto resize when has `autosize` prop
|
||||
|
||||
```html
|
||||
<van-cell-group>
|
||||
<van-field
|
||||
v-model="message"
|
||||
label="Message"
|
||||
type="textarea"
|
||||
placeholder="Message"
|
||||
rows="1"
|
||||
autosize
|
||||
/>
|
||||
</van-cell-group>
|
||||
```
|
||||
|
||||
### Insert button
|
||||
|
||||
Use button slot to insert button
|
||||
|
||||
```html
|
||||
<van-cell-group>
|
||||
<van-field
|
||||
v-model="sms"
|
||||
center
|
||||
clearable
|
||||
label="SMS"
|
||||
placeholder="SMS"
|
||||
>
|
||||
<van-button slot="button" size="small" type="primary">Send SMS</van-button>
|
||||
</van-field>
|
||||
</van-cell-group>
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
Field support all native properties of input tag,such as `maxlength`、`placeholder`、`autofocus`
|
||||
|
||||
| Attribute | Description | Type | Default |
|
||||
|------|------|------|------|
|
||||
| value | Field value | `String | Number` | - |
|
||||
| label | Field label | `String` | - |
|
||||
| type | Input type | `String` | `text` |
|
||||
| size | Size,can be set to `large` | `String` | - |
|
||||
| border | Whether to show inner border | `Boolean` | `true` |
|
||||
| disabled | Whether to disable field | `Boolean` | `false` |
|
||||
| readonly | Whether to be readonly | `Boolean` | `false` |
|
||||
| required | Whether to show required mark | `Boolean` | `false` |
|
||||
| clearable | Whether to be clearable | `Boolean` | `false` |
|
||||
| clickable | Whether to show click feedback when clicked | `Boolean` | `false` |
|
||||
| is-link | Whether to show link icon | `Boolean` | `false` |
|
||||
| error | Whether to show error info | `Boolean` | `false` |
|
||||
| error-message | Error message | `String` | `''` |
|
||||
| label-class | Label className | `any` | - |
|
||||
| label-width | Label width | `String | Number` | `90px` |
|
||||
| label-align | Label text align, can be set to `center` `right` | `String` | `left` |
|
||||
| input-align | Input text align, can be set to `center` `right` | `String` | `left` |
|
||||
| error-message-align | Error message text align, can be set to `center` `right` | `String` | `left` |
|
||||
| autosize | Textarea auto resize,can accpet an object,<br>e.g. { maxHeight: 100, minHeight: 50 } | `Boolean | Object` | `false` |
|
||||
| left-icon | Left side icon name | `String` | - |
|
||||
| right-icon | Right side icon name | `String` | - |
|
||||
|
||||
### Events
|
||||
|
||||
Field support all native events of input tag,such as `focus`、`blur`、`keypress`
|
||||
|
||||
| Event | Description | Parameters |
|
||||
|------|------|------|
|
||||
| input | Triggered when value changed | - |
|
||||
| click | Triggered when click field | - |
|
||||
| clear | Triggered when click clear icon | - |
|
||||
| click-left-icon | Triggered when click the left icon of Field | - |
|
||||
| click-right-icon | Triggered when click the right icon of Field | - |
|
||||
|
||||
### Methods
|
||||
|
||||
Use ref to get field instance and call instance methods
|
||||
|
||||
| Name | Attribute | Return value | Description |
|
||||
|------|------|------|------|
|
||||
| focus | - | - | Trigger input focus |
|
||||
| blur | - | - | Trigger input blur |
|
||||
|
||||
### Slots
|
||||
|
||||
| Name | Description |
|
||||
|------|------|
|
||||
| label | Custom label |
|
||||
| input | Custom input |
|
||||
| left-icon | Custom left icon |
|
||||
| right-icon | Custom right icon |
|
||||
| button | Insert button |
|
||||
@@ -0,0 +1,320 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,133 @@
|
||||
@import '../style/var';
|
||||
|
||||
.van-field {
|
||||
&__label {
|
||||
flex: none;
|
||||
width: @field-label-width;
|
||||
|
||||
&--center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&--right {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__control {
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: @field-input-text-color;
|
||||
text-align: left;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
resize: none;
|
||||
|
||||
&::placeholder {
|
||||
color: @field-placeholder-text-color;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
color: @field-input-disabled-text-color;
|
||||
background-color: transparent;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&--center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&--right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* for ios wechat */
|
||||
&[type="date"],
|
||||
&[type="time"],
|
||||
&[type="datetime-local"] {
|
||||
min-height: @cell-line-height;
|
||||
}
|
||||
}
|
||||
|
||||
&__clear,
|
||||
&__icon,
|
||||
&__button,
|
||||
&__right-icon {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__clear,
|
||||
&__right-icon {
|
||||
margin-right: -10px;
|
||||
padding: 0 10px;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
&__clear {
|
||||
color: @field-clear-icon-color;
|
||||
font-size: @field-clear-icon-size;
|
||||
}
|
||||
|
||||
&__left-icon .van-icon,
|
||||
&__right-icon .van-icon {
|
||||
display: block;
|
||||
min-width: 1em;
|
||||
font-size: @field-icon-size;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
&__left-icon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
&__right-icon {
|
||||
color: @field-right-icon-color;
|
||||
}
|
||||
|
||||
&__button {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
&__error-message {
|
||||
color: @field-error-message-color;
|
||||
font-size: @field-error-message-text-color;
|
||||
text-align: left;
|
||||
|
||||
&--center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&--right {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
.van-field__control {
|
||||
color: @field-input-disabled-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
&--error {
|
||||
.van-field__control {
|
||||
&,
|
||||
&::placeholder {
|
||||
color: @field-input-error-text-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--min-height {
|
||||
.van-field__control {
|
||||
min-height: @field-text-area-min-height;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders demo correctly 1`] = `
|
||||
<div>
|
||||
<div>
|
||||
<div class="van-cell-group van-hairline--top-bottom">
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__value van-cell__value--alone">
|
||||
<div class="van-field__body"><input type="text" placeholder="请输入用户名" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="van-cell-group van-hairline--top-bottom">
|
||||
<div class="van-cell van-cell--required van-field">
|
||||
<div class="van-cell__title van-field__label"><span>用户名</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><input type="text" placeholder="请输入用户名" class="van-field__control">
|
||||
<div class="van-field__right-icon"><i class="van-icon van-icon-question-o">
|
||||
<!----></i></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-cell--required van-field">
|
||||
<div class="van-cell__title van-field__label"><span>密码</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><input type="password" placeholder="请输入密码" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="van-cell-group van-hairline--top-bottom">
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-field__left-icon"><i class="van-icon van-icon-contact">
|
||||
<!----></i></div>
|
||||
<div class="van-cell__title van-field__label"><span>用户名</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><input type="text" disabled="disabled" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="van-cell-group van-hairline--top-bottom">
|
||||
<div class="van-cell van-field van-field--error">
|
||||
<div class="van-cell__title van-field__label"><span>用户名</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><input type="text" placeholder="请输入用户名" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label"><span>手机号</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><input type="text" placeholder="请输入手机号" class="van-field__control"></div>
|
||||
<div class="van-field__error-message">手机号格式错误</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="van-cell-group van-hairline--top-bottom">
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label"><span>留言</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><textarea placeholder="请输入留言" rows="1" class="van-field__control" style="height: auto;"></textarea></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="van-cell-group van-hairline--top-bottom">
|
||||
<div class="van-cell van-cell--center van-field">
|
||||
<div class="van-cell__title van-field__label"><span>短信验证码</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><input type="text" placeholder="请输入短信验证码" class="van-field__control">
|
||||
<div class="van-field__button"><button class="van-button van-button--primary van-button--small"><span class="van-button__text">
|
||||
发送验证码
|
||||
</span></button></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,80 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`clearable 1`] = `
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__value van-cell__value--alone">
|
||||
<div class="van-field__body"><input type="text" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`clearable 2`] = `
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__value van-cell__value--alone">
|
||||
<div class="van-field__body"><input type="text" class="van-field__control"><i class="van-icon van-icon-clear van-field__clear">
|
||||
<!----></i></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`label-class prop 1`] = `
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label custom-label-class"><span>Label</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><input type="text" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`label-width prop with unit 1`] = `
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label" style="width: 10rem;"><span>Label</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><input type="text" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`label-width prop without unit 1`] = `
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label" style="width: 100px;"><span>Label</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><input type="text" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`render input slot 1`] = `
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__value van-cell__value--alone">
|
||||
<div class="van-field__body">
|
||||
<div class="van-field__control">Custom Input</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`render label slot 1`] = `
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label">Custom Label</div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><input type="text" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`render textarea 1`] = `
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__value van-cell__value--alone">
|
||||
<div class="van-field__body"><textarea class="van-field__control" style="height: auto;"></textarea></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`size prop 1`] = `
|
||||
<div class="van-cell van-cell--large van-field">
|
||||
<div class="van-cell__value van-cell__value--alone">
|
||||
<div class="van-field__body"><input type="text" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,4 @@
|
||||
import Demo from '../demo';
|
||||
import demoTest from '../../../test/demo-test';
|
||||
|
||||
demoTest(Demo);
|
||||
@@ -0,0 +1,237 @@
|
||||
import Field from '..';
|
||||
import { mount, later } from '../../../test/utils';
|
||||
|
||||
test('input event', () => {
|
||||
const wrapper = mount(Field);
|
||||
const input = wrapper.find('input');
|
||||
|
||||
input.element.value = '1';
|
||||
input.trigger('input');
|
||||
expect(wrapper.emitted('input')[0][0]).toEqual('1');
|
||||
});
|
||||
|
||||
test('click event', () => {
|
||||
const wrapper = mount(Field);
|
||||
|
||||
wrapper.trigger('click');
|
||||
expect(wrapper.emitted('click')[0][0]).toBeTruthy();
|
||||
});
|
||||
|
||||
test('click icon event', () => {
|
||||
const wrapper = mount(Field, {
|
||||
propsData: {
|
||||
value: 'a',
|
||||
leftIcon: 'contact',
|
||||
rightIcon: 'search'
|
||||
}
|
||||
});
|
||||
|
||||
wrapper.find('.van-field__left-icon').trigger('click');
|
||||
wrapper.find('.van-field__right-icon').trigger('click');
|
||||
expect(wrapper.emitted('click-left-icon')).toBeTruthy();
|
||||
expect(wrapper.emitted('click-right-icon')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('keypress event', () => {
|
||||
const wrapper = mount(Field, {
|
||||
propsData: {
|
||||
value: '',
|
||||
type: 'number'
|
||||
}
|
||||
});
|
||||
|
||||
const fn = jest.fn();
|
||||
const { calls } = fn.mock;
|
||||
const press = keyCode => wrapper.vm.onKeypress({
|
||||
keyCode,
|
||||
preventDefault: fn
|
||||
});
|
||||
|
||||
press(0);
|
||||
expect(calls.length).toBe(1);
|
||||
|
||||
press(50);
|
||||
expect(calls.length).toBe(1);
|
||||
|
||||
wrapper.setProps({ value: '0.1' });
|
||||
press(46);
|
||||
expect(calls.length).toBe(2);
|
||||
|
||||
wrapper.setProps({ type: 'text' });
|
||||
press(0);
|
||||
expect(calls.length).toBe(2);
|
||||
});
|
||||
|
||||
test('render textarea', async () => {
|
||||
const wrapper = mount(Field, {
|
||||
propsData: {
|
||||
type: 'textarea',
|
||||
autosize: true
|
||||
}
|
||||
});
|
||||
|
||||
await later();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('autosize textarea field', () => {
|
||||
const wrapper = mount(Field, {
|
||||
propsData: {
|
||||
type: 'textarea',
|
||||
autosize: {}
|
||||
}
|
||||
});
|
||||
|
||||
const value = '1'.repeat(20);
|
||||
const textarea = wrapper.find('.van-field__control');
|
||||
|
||||
wrapper.setProps({ value });
|
||||
expect(textarea.element.value).toEqual(value);
|
||||
});
|
||||
|
||||
test('autosize object', async () => {
|
||||
const wrapper = mount(Field, {
|
||||
propsData: {
|
||||
type: 'textarea',
|
||||
autosize: {
|
||||
maxHeight: 100,
|
||||
minHeight: 50
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const textarea = wrapper.find('.van-field__control');
|
||||
|
||||
await later();
|
||||
expect(textarea.element.style.height).toEqual(('50px'));
|
||||
});
|
||||
|
||||
test('blur method', () => {
|
||||
const fn = jest.fn();
|
||||
const wrapper = mount(Field);
|
||||
|
||||
wrapper.vm.$on('blur', fn);
|
||||
wrapper.find('input').element.focus();
|
||||
wrapper.vm.blur();
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('focus method', () => {
|
||||
const fn = jest.fn();
|
||||
const wrapper = mount(Field);
|
||||
|
||||
wrapper.vm.$on('focus', fn);
|
||||
wrapper.vm.focus();
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('maxlength', async () => {
|
||||
const wrapper = mount(Field, {
|
||||
attrs: {
|
||||
maxlength: 3
|
||||
},
|
||||
propsData: {
|
||||
value: 1234,
|
||||
type: 'number'
|
||||
}
|
||||
});
|
||||
|
||||
const input = wrapper.find('input');
|
||||
expect(input.element.value).toEqual('123');
|
||||
|
||||
input.element.value = 1234;
|
||||
await later();
|
||||
input.trigger('input');
|
||||
|
||||
expect(input.element.value).toEqual('123');
|
||||
expect(wrapper.emitted('input')[0][0]).toEqual('123');
|
||||
});
|
||||
|
||||
test('clearable', () => {
|
||||
const wrapper = mount(Field, {
|
||||
propsData: {
|
||||
value: 'test',
|
||||
clearable: true
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
const input = wrapper.find('input');
|
||||
input.trigger('focus');
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
wrapper.find('.van-field__clear').trigger('touchstart');
|
||||
expect(wrapper.emitted('input')[0][0]).toEqual('');
|
||||
expect(wrapper.emitted('clear')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('render input slot', () => {
|
||||
const wrapper = mount({
|
||||
template: `
|
||||
<field>
|
||||
<template v-slot:input>Custom Input</template>
|
||||
</field>
|
||||
`,
|
||||
components: {
|
||||
Field
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('render label slot', () => {
|
||||
const wrapper = mount({
|
||||
template: `
|
||||
<field label="Default Label">
|
||||
<template v-slot:label>Custom Label</template>
|
||||
</field>
|
||||
`,
|
||||
components: {
|
||||
Field
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('size prop', () => {
|
||||
const wrapper = mount(Field, {
|
||||
propsData: {
|
||||
size: 'large'
|
||||
}
|
||||
});
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('label-width prop with unit', () => {
|
||||
const wrapper = mount(Field, {
|
||||
propsData: {
|
||||
label: 'Label',
|
||||
labelWidth: '10rem'
|
||||
}
|
||||
});
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('label-width prop without unit', () => {
|
||||
const wrapper = mount(Field, {
|
||||
propsData: {
|
||||
label: 'Label',
|
||||
labelWidth: 100
|
||||
}
|
||||
});
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('label-class prop', () => {
|
||||
const wrapper = mount(Field, {
|
||||
propsData: {
|
||||
label: 'Label',
|
||||
labelClass: 'custom-label-class'
|
||||
}
|
||||
});
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,180 @@
|
||||
# Field 输入框
|
||||
|
||||
### 介绍
|
||||
|
||||
表单中的输入框组件,支持`input`和`textarea`两种类型
|
||||
|
||||
### 引入
|
||||
``` javascript
|
||||
import { Field } from 'vant';
|
||||
|
||||
Vue.use(Field);
|
||||
```
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 基础用法
|
||||
|
||||
通过 v-model 绑定输入框的值
|
||||
|
||||
```html
|
||||
<van-cell-group>
|
||||
<van-field v-model="value" placeholder="请输入用户名" />
|
||||
</van-cell-group>
|
||||
```
|
||||
|
||||
### 自定义类型
|
||||
|
||||
根据`type`属性定义不同类型的输入框
|
||||
|
||||
```html
|
||||
<van-cell-group>
|
||||
<van-field
|
||||
v-model="username"
|
||||
required
|
||||
clearable
|
||||
label="用户名"
|
||||
right-icon="question-o"
|
||||
placeholder="请输入用户名"
|
||||
@click-right-icon="$toast('question')"
|
||||
/>
|
||||
|
||||
<van-field
|
||||
v-model="password"
|
||||
type="password"
|
||||
label="密码"
|
||||
placeholder="请输入密码"
|
||||
required
|
||||
/>
|
||||
</van-cell-group>
|
||||
```
|
||||
|
||||
### 禁用输入框
|
||||
|
||||
```html
|
||||
<van-cell-group>
|
||||
<van-field
|
||||
value="输入框已禁用"
|
||||
label="用户名"
|
||||
left-icon="contact"
|
||||
disabled
|
||||
/>
|
||||
</van-cell-group>
|
||||
```
|
||||
|
||||
### 错误提示
|
||||
|
||||
通过`error`或者`error-message`属性增加对应的错误提示
|
||||
|
||||
```html
|
||||
<van-cell-group>
|
||||
<van-field
|
||||
v-model="username"
|
||||
label="用户名"
|
||||
placeholder="请输入用户名"
|
||||
error
|
||||
/>
|
||||
<van-field
|
||||
v-model="phone"
|
||||
label="手机号"
|
||||
placeholder="请输入手机号"
|
||||
error-message="手机号格式错误"
|
||||
/>
|
||||
</van-cell-group>
|
||||
```
|
||||
|
||||
### 高度自适应
|
||||
|
||||
对于 textarea,可以通过`autosize`属性设置高度自适应
|
||||
|
||||
```html
|
||||
<van-cell-group>
|
||||
<van-field
|
||||
v-model="message"
|
||||
label="留言"
|
||||
type="textarea"
|
||||
placeholder="请输入留言"
|
||||
rows="1"
|
||||
autosize
|
||||
/>
|
||||
</van-cell-group>
|
||||
```
|
||||
|
||||
### 插入按钮
|
||||
|
||||
通过 button 插槽可以在输入框尾部插入按钮
|
||||
|
||||
```html
|
||||
<van-cell-group>
|
||||
<van-field
|
||||
v-model="sms"
|
||||
center
|
||||
clearable
|
||||
label="短信验证码"
|
||||
placeholder="请输入短信验证码"
|
||||
>
|
||||
<van-button slot="button" size="small" type="primary">发送验证码</van-button>
|
||||
</van-field>
|
||||
</van-cell-group>
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
Field 默认支持 Input 标签所有的原生属性,比如 `maxlength`、`placeholder`、`autofocus` 等
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
|------|------|------|------|------|------|
|
||||
| label | 输入框左侧文本 | `String` | - | - |
|
||||
| value | 当前输入的值 | `String | Number` | - | - |
|
||||
| type | 可设置为原生类型, 如 `number` `tel` `textarea` | `String` | `text` | - |
|
||||
| size | 大小,可选值为 `large` | `String` | - | 1.6.9 |
|
||||
| border | 是否显示内边框 | `Boolean` | `true` | - |
|
||||
| disabled | 是否禁用输入框 | `Boolean` | `false` | - |
|
||||
| readonly | 是否只读 | `Boolean` | `false` | - |
|
||||
| required | 是否显示表单必填星号 | `Boolean` | `false` | - |
|
||||
| clearable | 是否启用清除控件 | `Boolean` | `false` | 1.1.9 |
|
||||
| clickable | 是否开启点击反馈 | `Boolean` | `false` | 2.0.0 |
|
||||
| is-link | 是否展示右侧箭头并开启点击反馈 | `Boolean` | `false` | 1.1.10 |
|
||||
| error | 是否将输入内容标红 | `Boolean` | `false` | - |
|
||||
| error-message | 底部错误提示文案,为空时不展示 | `String` | `''` | -
|
||||
| label-class | 左侧文本额外类名 | `any` | - | 2.0.0 |
|
||||
| label-width | 左侧文本宽度,可指定单位,默认为 px | `String | Number` | `90px` | 1.6.17 |
|
||||
| label-align | 左侧文本对齐方式,可选值为 `center` `right` | `String` | `left` | 1.1.10 |
|
||||
| input-align | 输入框内容对齐方式,可选值为 `center` `right` | `String` | `left` | 1.1.10 |
|
||||
| error-message-align | 错误提示文案对齐方式,可选值为 `center` `right` | `String` | `left` | 1.6.11 |
|
||||
| autosize | 自适应内容高度,只对 textarea 有效,可传入对象,<br>如 { maxHeight: 100, minHeight: 50 },单位为 px | `Boolean | Object` | `false` | 1.0.0 |
|
||||
| left-icon | 输入框左侧图标名称或图片链接,可选值见 Icon 组件 | `String` | - | 1.5.7 |
|
||||
| right-icon | 输入框尾部图标名称或图片链接,可选值见 Icon 组件 | `String` | - | 1.5.7 |
|
||||
|
||||
### Events
|
||||
|
||||
Field 默认支持 Input 标签所有的原生事件,如 `focus`、`blur`、`keypress` 等
|
||||
|
||||
| 事件 | 说明 | 回调参数 |
|
||||
|------|------|------|
|
||||
| input | 输入框内容变化时触发 | - |
|
||||
| click | 点击时触发 | - |
|
||||
| clear | 点击清除按钮后触发 | - |
|
||||
| click-left-icon | 点击头部图标时触发 | - |
|
||||
| click-right-icon | 点击尾部图标时触发 | - |
|
||||
|
||||
### 方法
|
||||
|
||||
通过 ref 可以获取到 field 实例并调用实例方法
|
||||
|
||||
| 方法名 | 参数 | 返回值 | 介绍 |
|
||||
|------|------|------|------|
|
||||
| focus | - | - | 获取输入框焦点 |
|
||||
| blur | - | - | 取消输入框焦点 |
|
||||
|
||||
### Slots
|
||||
|
||||
| 名称 | 说明 |
|
||||
|------|------|
|
||||
| label | 自定义输入框标签 |
|
||||
| input | 自定义输入框,使用此插槽后,与输入框相关的属性和事件将失效 |
|
||||
| left-icon | 自定义输入框头部图标 |
|
||||
| right-icon | 自定义输入框尾部图标 |
|
||||
| button | 自定义输入框尾部按钮 |
|
||||
Reference in New Issue
Block a user