diff --git a/packages/input/src/input.vue b/packages/input/src/input.vue
index 148f49ed3..89b9bce66 100644
--- a/packages/input/src/input.vue
+++ b/packages/input/src/input.vue
@@ -28,11 +28,9 @@
:disabled="inputDisabled"
:readonly="readonly"
:autocomplete="autoComplete || autocomplete"
- :value="nativeInputValue"
ref="input"
- @compositionstart="handleComposition"
- @compositionupdate="handleComposition"
- @compositionend="handleComposition"
+ @compositionstart="handleCompositionStart"
+ @compositionend="handleCompositionEnd"
@input="handleInput"
@focus="handleFocus"
@blur="handleBlur"
@@ -82,10 +80,8 @@
v-else
:tabindex="tabindex"
class="el-textarea__inner"
- :value="nativeInputValue"
- @compositionstart="handleComposition"
- @compositionupdate="handleComposition"
- @compositionend="handleComposition"
+ @compositionstart="handleCompositionStart"
+ @compositionend="handleCompositionEnd"
@input="handleInput"
ref="textarea"
v-bind="$attrs"
@@ -130,7 +126,7 @@
textareaCalcStyle: {},
hovering: false,
focused: false,
- isOnComposition: false,
+ isComposing: false,
passwordVisible: false
};
},
@@ -208,7 +204,7 @@
return this.disabled || (this.elForm || {}).disabled;
},
nativeInputValue() {
- return this.value === null || this.value === undefined ? '' : this.value;
+ return this.value === null || this.value === undefined ? '' : String(this.value);
},
showClear() {
return this.clearable &&
@@ -231,6 +227,12 @@
if (this.validateEvent) {
this.dispatch('ElFormItem', 'el.form.change', [val]);
}
+ },
+ // native input value is set explicitly
+ // do not use v-model / :value in template
+ // see: https://github.com/ElemeFE/element/issues/14521
+ nativeInputValue() {
+ this.setNativeInputValue();
}
},
@@ -277,21 +279,27 @@
this.textareaCalcStyle = calcTextareaHeight(this.$refs.textarea, minRows, maxRows);
},
+ setNativeInputValue() {
+ const input = this.getInput();
+ if (!input) return;
+ if (input.value === this.nativeInputValue) return;
+ input.value = this.nativeInputValue;
+ },
handleFocus(event) {
this.focused = true;
this.$emit('focus', event);
},
- handleComposition(event) {
- if (event.type === 'compositionstart') {
- this.isOnComposition = true;
- }
- if (event.type === 'compositionend') {
- this.isOnComposition = false;
- this.handleInput(event);
- }
+ handleCompositionStart() {
+ this.isComposing = true;
+ },
+ handleCompositionEnd(event) {
+ this.isComposing = false;
+ this.handleInput(event);
},
handleInput(event) {
- if (this.isOnComposition) return;
+ // 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
@@ -299,12 +307,9 @@
this.$emit('input', event.target.value);
- // set input's value, in case parent refuses the change
+ // ensure native input value is controlled
// see: https://github.com/ElemeFE/element/issues/12850
- this.$nextTick(() => {
- let input = this.getInput();
- input.value = this.value;
- });
+ this.$nextTick(this.setNativeInputValue);
},
handleChange(event) {
this.$emit('change', event.target.value);
@@ -355,6 +360,7 @@
},
mounted() {
+ this.setNativeInputValue();
this.resizeTextarea();
this.updateIconOffset();
},
diff --git a/test/unit/specs/input.spec.js b/test/unit/specs/input.spec.js
index 089afe9ee..5c26589c7 100644
--- a/test/unit/specs/input.spec.js
+++ b/test/unit/specs/input.spec.js
@@ -1,4 +1,4 @@
-import { createVue, destroyVM, wait, waitImmediate } from '../util';
+import { createVue, destroyVM, triggerEvent, wait, waitImmediate } from '../util';
describe('Input', () => {
let vm;
@@ -6,7 +6,7 @@ describe('Input', () => {
destroyVM(vm);
});
- it('create', () => {
+ it('create', async() => {
vm = createVue({
template: `
{
:maxlength="5"
placeholder="请输入内容"
@focus="handleFocus"
- value="input">
+ :value="input">
`,
data() {
return {
+ input: 'input',
inputFocus: false
};
},
@@ -35,6 +36,18 @@ describe('Input', () => {
expect(inputElm.value).to.equal('input');
expect(inputElm.getAttribute('minlength')).to.equal('3');
expect(inputElm.getAttribute('maxlength')).to.equal('5');
+
+ vm.input = 'text';
+ await waitImmediate();
+ expect(inputElm.value).to.equal('text');
+ });
+
+ it('default to empty', () => {
+ vm = createVue({
+ template: ''
+ }, true);
+ let inputElm = vm.$el.querySelector('input');
+ expect(inputElm.value).to.equal('');
});
it('disabled', () => {
@@ -236,7 +249,7 @@ describe('Input', () => {
});
describe('Input Events', () => {
- it('event:focus & blur', done => {
+ it('event:focus & blur', async() => {
vm = createVue({
template: `
{
vm.$el.querySelector('input').focus();
vm.$el.querySelector('input').blur();
- vm.$nextTick(_ => {
- expect(spyFocus.calledOnce).to.be.true;
- expect(spyBlur.calledOnce).to.be.true;
- done();
- });
+ await waitImmediate();
+ expect(spyFocus.calledOnce).to.be.true;
+ expect(spyBlur.calledOnce).to.be.true;
});
- it('event:change', done => {
+ it('event:change', async() => {
// NOTE: should be same as native's change behavior
vm = createVue({
template: `
@@ -290,13 +301,11 @@ describe('Input', () => {
// simplified test, component should emit change when native does
simulateEvent('1', 'input');
simulateEvent('2', 'change');
- vm.$nextTick(_ => {
- expect(spy.calledWith('2')).to.be.true;
- expect(spy.calledOnce).to.be.true;
- done();
- });
+ await waitImmediate();
+ expect(spy.calledWith('2')).to.be.true;
+ expect(spy.calledOnce).to.be.true;
});
- it('event:clear', done => {
+ it('event:clear', async() => {
vm = createVue({
template: `
{
// focus to show clear button
inputElm.focus();
vm.$refs.input.$on('clear', spyClear);
- vm.$nextTick(_ => {
- vm.$el.querySelector('.el-input__clear').click();
- vm.$nextTick(_ => {
- expect(spyClear.calledOnce).to.be.true;
- done();
- });
- });
+ await waitImmediate();
+ vm.$el.querySelector('.el-input__clear').click();
+ await waitImmediate();
+ expect(spyClear.calledOnce).to.be.true;
+ });
+ it('event:input', async() => {
+ vm = createVue({
+ template: `
+
+
+ `,
+ data() {
+ return {
+ input: 'a'
+ };
+ }
+ }, true);
+ const spy = sinon.spy();
+ vm.$refs.input.$on('input', spy);
+ const nativeInput = vm.$refs.input.$el.querySelector('input');
+ nativeInput.value = '1';
+ triggerEvent(nativeInput, 'compositionstart');
+ triggerEvent(nativeInput, 'input');
+ await waitImmediate();
+ nativeInput.value = '2';
+ triggerEvent(nativeInput, 'compositionupdate');
+ triggerEvent(nativeInput, 'input');
+ await waitImmediate();
+ triggerEvent(nativeInput, 'compositionend');
+ await waitImmediate();
+ // input event does not fire during composition
+ expect(spy.calledOnce).to.be.true;
+ // native input value is controlled
+ expect(vm.input).to.equal('a');
+ expect(nativeInput.value).to.equal('a');
+
});
});
describe('Input Methods', () => {
- it('method:select', done => {
+ it('method:select', async() => {
const testContent = 'test';
vm = createVue({
@@ -347,11 +389,9 @@ describe('Input', () => {
vm.$refs.inputComp.select();
- vm.$nextTick(_ => {
- expect(vm.$refs.inputComp.$refs.input.selectionStart).to.equal(0);
- expect(vm.$refs.inputComp.$refs.input.selectionEnd).to.equal(testContent.length);
- done();
- });
+ await waitImmediate();
+ expect(vm.$refs.inputComp.$refs.input.selectionStart).to.equal(0);
+ expect(vm.$refs.inputComp.$refs.input.selectionEnd).to.equal(testContent.length);
});
});
});