mirror of https://github.com/ElemeFE/element
[input ]: Add formatting api
parent
2a49142965
commit
907e548cb0
|
@ -673,7 +673,29 @@ export default {
|
|||
</script>
|
||||
```
|
||||
:::
|
||||
### 格式化
|
||||
:::demo `formatter` 和 `parser` 可配合实现格式化能力(仅`type`为text生效)。
|
||||
```html
|
||||
<template>
|
||||
<el-input
|
||||
v-model="input"
|
||||
placeholder="Please input"
|
||||
:formatter="(value) => `$ ${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')"
|
||||
:parser="(value) => value.replace(/\$\s?|(,*)/g, '')"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
input: '',
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
:::
|
||||
### Input Attributes
|
||||
|
||||
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
||||
|
@ -703,8 +725,10 @@ export default {
|
|||
| autofocus | 原生属性,自动获取焦点 | boolean | true, false | false |
|
||||
| form | 原生属性 | string | — | — |
|
||||
| label | 输入框关联的label文字 | string | — | — |
|
||||
| tabindex | 输入框的tabindex | string | - | - |
|
||||
| validate-event | 输入时是否触发表单的校验 | boolean | - | true |
|
||||
| tabindex | 输入框的tabindex | string | — | — |
|
||||
| validate-event | 输入时是否触发表单的校验 | boolean | — | true |
|
||||
| formatter | 指定输入值的格式。(仅当 type 是"text"时才能工作) | Function | — | — |
|
||||
| parser | 指定从格式化器输入中提取的值。(仅当 type 是"text"时才起作用) | Function | — | — |
|
||||
|
||||
### Input Slots
|
||||
| name | 说明 |
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<template>
|
||||
<div :class="[
|
||||
<div
|
||||
:class="[
|
||||
type === 'textarea' ? 'el-textarea' : 'el-input',
|
||||
inputSize ? 'el-input--' + inputSize : '',
|
||||
{
|
||||
|
@ -9,8 +10,9 @@
|
|||
'el-input-group--append': $slots.append,
|
||||
'el-input-group--prepend': $slots.prepend,
|
||||
'el-input--prefix': $slots.prefix || prefixIcon,
|
||||
'el-input--suffix': $slots.suffix || suffixIcon || clearable || showPassword
|
||||
}
|
||||
'el-input--suffix':
|
||||
$slots.suffix || suffixIcon || clearable || showPassword,
|
||||
},
|
||||
]"
|
||||
@mouseenter="hovering = true"
|
||||
@mouseleave="hovering = false"
|
||||
|
@ -38,33 +40,28 @@
|
|||
@blur="handleBlur"
|
||||
@change="handleChange"
|
||||
:aria-label="label"
|
||||
>
|
||||
/>
|
||||
<!-- 前置内容 -->
|
||||
<span class="el-input__prefix" v-if="$slots.prefix || prefixIcon">
|
||||
<slot name="prefix"></slot>
|
||||
<i class="el-input__icon"
|
||||
v-if="prefixIcon"
|
||||
:class="prefixIcon">
|
||||
</i>
|
||||
<i class="el-input__icon" v-if="prefixIcon" :class="prefixIcon"> </i>
|
||||
</span>
|
||||
<!-- 后置内容 -->
|
||||
<span
|
||||
class="el-input__suffix"
|
||||
v-if="getSuffixVisible()">
|
||||
<span class="el-input__suffix" v-if="getSuffixVisible()">
|
||||
<span class="el-input__suffix-inner">
|
||||
<template v-if="!showClear || !showPwdVisible || !isWordLimitVisible">
|
||||
<slot name="suffix"></slot>
|
||||
<i class="el-input__icon"
|
||||
v-if="suffixIcon"
|
||||
:class="suffixIcon">
|
||||
<i class="el-input__icon" v-if="suffixIcon" :class="suffixIcon">
|
||||
</i>
|
||||
</template>
|
||||
<i v-if="showClear"
|
||||
<i
|
||||
v-if="showClear"
|
||||
class="el-input__icon el-icon-circle-close el-input__clear"
|
||||
@mousedown.prevent
|
||||
@click="clear"
|
||||
></i>
|
||||
<i v-if="showPwdVisible"
|
||||
<i
|
||||
v-if="showPwdVisible"
|
||||
class="el-input__icon el-icon-view el-input__clear"
|
||||
@click="handlePasswordVisible"
|
||||
></i>
|
||||
|
@ -74,9 +71,11 @@
|
|||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<i class="el-input__icon"
|
||||
<i
|
||||
class="el-input__icon"
|
||||
v-if="validateState"
|
||||
:class="['el-input__validateIcon', validateIcon]">
|
||||
:class="['el-input__validateIcon', validateIcon]"
|
||||
>
|
||||
</i>
|
||||
</span>
|
||||
<!-- 后置元素 -->
|
||||
|
@ -104,15 +103,19 @@
|
|||
:aria-label="label"
|
||||
>
|
||||
</textarea>
|
||||
<span v-if="isWordLimitVisible && type === 'textarea'" class="el-input__count">{{ textLength }}/{{ upperLimit }}</span>
|
||||
<span
|
||||
v-if="isWordLimitVisible && type === 'textarea'"
|
||||
class="el-input__count"
|
||||
>{{ textLength }}/{{ upperLimit }}</span
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import emitter from 'element-ui/src/mixins/emitter';
|
||||
import Migrating from 'element-ui/src/mixins/migrating';
|
||||
import calcTextareaHeight from './calcTextareaHeight';
|
||||
import merge from 'element-ui/src/utils/merge';
|
||||
import { isKorean } from 'element-ui/src/utils/shared';
|
||||
import calcTextareaHeight from './calcTextareaHeight';
|
||||
|
||||
export default {
|
||||
name: 'ElInput',
|
||||
|
@ -166,7 +169,9 @@
|
|||
type: String,
|
||||
validator(val) {
|
||||
process.env.NODE_ENV !== 'production' &&
|
||||
console.warn('[Element Warn][Input]\'auto-complete\' property will be deprecated in next major version. please use \'autocomplete\' instead.');
|
||||
console.warn(
|
||||
"[Element Warn][Input]'auto-complete' property will be deprecated in next major version. please use 'autocomplete' instead."
|
||||
);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
@ -189,7 +194,15 @@
|
|||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
tabindex: String
|
||||
tabindex: String,
|
||||
formatter: {
|
||||
type: Function,
|
||||
default: null
|
||||
},
|
||||
parser: {
|
||||
type: Function,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
@ -219,28 +232,36 @@
|
|||
return this.disabled || (this.elForm || {}).disabled;
|
||||
},
|
||||
nativeInputValue() {
|
||||
return this.value === null || this.value === undefined ? '' : String(this.value);
|
||||
return this.value === null || this.value === undefined
|
||||
? ''
|
||||
: String(this.value);
|
||||
},
|
||||
showClear() {
|
||||
return this.clearable &&
|
||||
return (
|
||||
this.clearable &&
|
||||
!this.inputDisabled &&
|
||||
!this.readonly &&
|
||||
this.nativeInputValue &&
|
||||
(this.focused || this.hovering);
|
||||
(this.focused || this.hovering)
|
||||
);
|
||||
},
|
||||
showPwdVisible() {
|
||||
return this.showPassword &&
|
||||
return (
|
||||
this.showPassword &&
|
||||
!this.inputDisabled &&
|
||||
!this.readonly &&
|
||||
(!!this.nativeInputValue || this.focused);
|
||||
(!!this.nativeInputValue || this.focused)
|
||||
);
|
||||
},
|
||||
isWordLimitVisible() {
|
||||
return this.showWordLimit &&
|
||||
return (
|
||||
this.showWordLimit &&
|
||||
this.$attrs.maxlength &&
|
||||
(this.type === 'text' || this.type === 'textarea') &&
|
||||
!this.inputDisabled &&
|
||||
!this.readonly &&
|
||||
!this.showPassword;
|
||||
!this.showPassword
|
||||
);
|
||||
},
|
||||
upperLimit() {
|
||||
return this.$attrs.maxlength;
|
||||
|
@ -254,8 +275,7 @@
|
|||
},
|
||||
inputExceed() {
|
||||
// show exceed style if length of initial value greater then maxlength
|
||||
return this.isWordLimitVisible &&
|
||||
(this.textLength > this.upperLimit);
|
||||
return this.isWordLimitVisible && this.textLength > this.upperLimit;
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -294,11 +314,11 @@
|
|||
getMigratingConfig() {
|
||||
return {
|
||||
props: {
|
||||
'icon': 'icon is removed, use suffix-icon / prefix-icon instead.',
|
||||
icon: 'icon is removed, use suffix-icon / prefix-icon instead.',
|
||||
'on-icon-click': 'on-icon-click is removed.'
|
||||
},
|
||||
events: {
|
||||
'click': 'click is removed.'
|
||||
click: 'click is removed.'
|
||||
}
|
||||
};
|
||||
},
|
||||
|
@ -325,7 +345,11 @@
|
|||
const minRows = autosize.minRows;
|
||||
const maxRows = autosize.maxRows;
|
||||
|
||||
this.textareaCalcStyle = calcTextareaHeight(this.$refs.textarea, minRows, maxRows);
|
||||
this.textareaCalcStyle = calcTextareaHeight(
|
||||
this.$refs.textarea,
|
||||
minRows,
|
||||
maxRows
|
||||
);
|
||||
},
|
||||
setNativeInputValue() {
|
||||
const input = this.getInput();
|
||||
|
@ -355,15 +379,19 @@
|
|||
}
|
||||
},
|
||||
handleInput(event) {
|
||||
let { value, type } = event.target;
|
||||
let { formatter, parser } = this;
|
||||
// should not emit input during composition
|
||||
// see: https://github.com/ElemeFE/element/issues/10516
|
||||
if (this.isComposing) return;
|
||||
|
||||
// hack for https://github.com/ElemeFE/element/issues/8548
|
||||
// should remove the following line when we don't support IE
|
||||
if (event.target.value === this.nativeInputValue) return;
|
||||
|
||||
this.$emit('input', event.target.value);
|
||||
if (value === this.nativeInputValue) return;
|
||||
if (formatter && type === 'text') {
|
||||
value = parser ? parser(value) : value;
|
||||
value = formatter(value);
|
||||
}
|
||||
this.$emit('input', value);
|
||||
|
||||
// ensure native input value is controlled
|
||||
// see: https://github.com/ElemeFE/element/issues/12850
|
||||
|
@ -373,7 +401,9 @@
|
|||
this.$emit('change', event.target.value);
|
||||
},
|
||||
calcIconOffset(place) {
|
||||
let elList = [].slice.call(this.$el.querySelectorAll(`.el-input__${place}`) || []);
|
||||
let elList = [].slice.call(
|
||||
this.$el.querySelectorAll(`.el-input__${place}`) || []
|
||||
);
|
||||
if (!elList.length) return;
|
||||
let el = null;
|
||||
for (let i = 0; i < elList.length; i++) {
|
||||
|
@ -390,7 +420,9 @@
|
|||
|
||||
const pendant = pendantMap[place];
|
||||
if (this.$slots[pendant]) {
|
||||
el.style.transform = `translateX(${place === 'suffix' ? '-' : ''}${this.$el.querySelector(`.el-input-group__${pendant}`).offsetWidth}px)`;
|
||||
el.style.transform = `translateX(${place === 'suffix' ? '-' : ''}${
|
||||
this.$el.querySelector(`.el-input-group__${pendant}`).offsetWidth
|
||||
}px)`;
|
||||
} else {
|
||||
el.removeAttribute('style');
|
||||
}
|
||||
|
@ -414,12 +446,14 @@
|
|||
return this.$refs.input || this.$refs.textarea;
|
||||
},
|
||||
getSuffixVisible() {
|
||||
return this.$slots.suffix ||
|
||||
return (
|
||||
this.$slots.suffix ||
|
||||
this.suffixIcon ||
|
||||
this.showClear ||
|
||||
this.showPassword ||
|
||||
this.isWordLimitVisible ||
|
||||
(this.validateState && this.needStatusIcon);
|
||||
(this.validateState && this.needStatusIcon)
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -480,4 +480,30 @@ describe('Input', () => {
|
|||
await waitImmediate();
|
||||
expect(inputElm4.classList.contains('is-exceed')).to.false;
|
||||
});
|
||||
it('formatter Processing test', async() => {
|
||||
vm = createVue({
|
||||
template: `
|
||||
<el-input
|
||||
v-model="input"
|
||||
placeholder="请输入内容"
|
||||
:formatter="(value) => value + 'input'"
|
||||
:parser="(value) => value.replace(/input/g, '')"
|
||||
></el-input>
|
||||
`,
|
||||
data() {
|
||||
return {
|
||||
input: ''
|
||||
};
|
||||
}
|
||||
}, true);
|
||||
let inputElm = vm.$el.querySelector('input');
|
||||
expect(inputElm.getAttribute('placeholder')).to.equal('请输入内容');
|
||||
let evt = document.createEvent('HTMLEvents');
|
||||
evt = new Event('input', {'bubbles': true, 'cancelable': true});
|
||||
inputElm.value = 'text';
|
||||
inputElm.dispatchEvent(evt);
|
||||
await waitImmediate();
|
||||
expect(inputElm.value).to.equal('textinput');
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -12,7 +12,12 @@ export interface AutoSize {
|
|||
/** Maximum rows to show */
|
||||
maxRows: number
|
||||
}
|
||||
|
||||
export interface FormatterHandler {
|
||||
/**
|
||||
* @param value Current value of the text input
|
||||
*/
|
||||
(value: string | number): void
|
||||
}
|
||||
/** Input Component */
|
||||
export declare class ElInput extends ElementUIComponent {
|
||||
/** Type of input */
|
||||
|
@ -90,6 +95,11 @@ export declare class ElInput extends ElementUIComponent {
|
|||
/** Whether to show wordCount when setting maxLength */
|
||||
showWordLimit: boolean
|
||||
|
||||
/**Format value program */
|
||||
formatter:FormatterHandler
|
||||
|
||||
/**Extract value program */
|
||||
parser:FormatterHandler
|
||||
/**
|
||||
* Focus the Input component
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue