Răsfoiți Sursa

Input: simplify el-input implementation (#13471)

* input: simplify internal implementation

remove currentValue, strictly follow one-way data flow
hack for MSIE input event when placeholder is set
remove isKorean hack (#11665, #10648)

* input-number: fix for new el-input

* test: input, fix vue warning

* date-time-range: fix for new el-input

* pagination: fix for new el-input, simplify internals

* input: fix input event on compositionend

* input-number: fix for new el-input

* input-number: nuke userInput on change
Jiewei Qian 6 ani în urmă
părinte
comite
e2c5573c1f

+ 65 - 26
packages/date-picker/src/panel/date-range.vue

@@ -28,18 +28,19 @@
                   :placeholder="t('el.datepicker.startDate')"
                   class="el-date-range-picker__editor"
                   :value="minVisibleDate"
-                  @input.native="handleDateInput($event, 'min')"
-                  @change.native="handleDateChange($event, 'min')" />
+                  @input="val => handleDateInput(val, 'min')"
+                  @change="val => handleDateChange(val, 'min')" />
               </span>
               <span class="el-date-range-picker__time-picker-wrap" v-clickoutside="handleMinTimeClose">
                 <el-input
                   size="small"
+                  class="el-date-range-picker__editor"
                   :disabled="rangeState.selecting"
                   :placeholder="t('el.datepicker.startTime')"
-                  class="el-date-range-picker__editor"
                   :value="minVisibleTime"
                   @focus="minTimePickerVisible = true"
-                  @change.native="handleTimeChange($event, 'min')" />
+                  @input="val => handleTimeInput(val, 'min')"
+                  @change="val => handleTimeChange(val, 'min')" />
                 <time-picker
                   ref="minTimePicker"
                   @pick="handleMinTimePick"
@@ -54,25 +55,25 @@
               <span class="el-date-range-picker__time-picker-wrap">
                 <el-input
                   size="small"
+                  class="el-date-range-picker__editor"
                   :disabled="rangeState.selecting"
                   :placeholder="t('el.datepicker.endDate')"
-                  class="el-date-range-picker__editor"
                   :value="maxVisibleDate"
                   :readonly="!minDate"
-                  @input.native="handleDateInput($event, 'max')"
-                  @change.native="handleDateChange($event, 'max')" />
+                  @input="val => handleDateInput(val, 'max')"
+                  @change="val => handleDateChange(val, 'max')" />
               </span>
               <span class="el-date-range-picker__time-picker-wrap" v-clickoutside="handleMaxTimeClose">
                 <el-input
                   size="small"
+                  class="el-date-range-picker__editor"
                   :disabled="rangeState.selecting"
-                  ref="maxInput"
                   :placeholder="t('el.datepicker.endTime')"
-                  class="el-date-range-picker__editor"
                   :value="maxVisibleTime"
-                  @focus="minDate && (maxTimePickerVisible = true)"
                   :readonly="!minDate"
-                  @change.native="handleTimeChange($event, 'max')" />
+                  @focus="minDate && (maxTimePickerVisible = true)"
+                  @input="val => handleTimeInput(val, 'max')"
+                  @change="val => handleTimeChange(val, 'max')" />
                 <time-picker
                   ref="maxTimePicker"
                   @pick="handleMaxTimePick"
@@ -263,19 +264,27 @@
       },
 
       minVisibleDate() {
-        return this.minDate ? formatDate(this.minDate, this.dateFormat) : '';
+        if (this.dateUserInput.min !== null) return this.dateUserInput.min;
+        if (this.minDate) return formatDate(this.minDate, this.dateFormat);
+        return '';
       },
 
       maxVisibleDate() {
-        return (this.maxDate || this.minDate) ? formatDate(this.maxDate || this.minDate, this.dateFormat) : '';
+        if (this.dateUserInput.max !== null) return this.dateUserInput.max;
+        if (this.maxDate || this.minDate) return formatDate(this.maxDate || this.minDate, this.dateFormat);
+        return '';
       },
 
       minVisibleTime() {
-        return this.minDate ? formatDate(this.minDate, this.timeFormat) : '';
+        if (this.timeUserInput.min !== null) return this.timeUserInput.min;
+        if (this.minDate) return formatDate(this.minDate, this.timeFormat);
+        return '';
       },
 
       maxVisibleTime() {
-        return (this.maxDate || this.minDate) ? formatDate(this.maxDate || this.minDate, this.timeFormat) : '';
+        if (this.timeUserInput.max !== null) return this.timeUserInput.max;
+        if (this.maxDate || this.minDate) return formatDate(this.maxDate || this.minDate, this.timeFormat);
+        return '';
       },
 
       timeFormat() {
@@ -330,12 +339,22 @@
         maxTimePickerVisible: false,
         format: '',
         arrowControl: false,
-        unlinkPanels: false
+        unlinkPanels: false,
+        dateUserInput: {
+          min: null,
+          max: null
+        },
+        timeUserInput: {
+          min: null,
+          max: null
+        }
       };
     },
 
     watch: {
       minDate(val) {
+        this.dateUserInput.min = null;
+        this.timeUserInput.min = null;
         this.$nextTick(() => {
           if (this.$refs.maxTimePicker && this.maxDate && this.maxDate < this.minDate) {
             const format = 'HH:mm:ss';
@@ -354,6 +373,8 @@
       },
 
       maxDate(val) {
+        this.dateUserInput.max = null;
+        this.timeUserInput.max = null;
         if (val && this.$refs.maxTimePicker) {
           this.$refs.maxTimePicker.date = val;
           this.$refs.maxTimePicker.value = val;
@@ -433,8 +454,8 @@
         this.rangeState = val.rangeState;
       },
 
-      handleDateInput(event, type) {
-        const value = event.target.value;
+      handleDateInput(value, type) {
+        this.dateUserInput[type] = value;
         if (value.length !== this.dateFormat.length) return;
         const parsedValue = parseDate(value, this.dateFormat);
 
@@ -444,19 +465,22 @@
             return;
           }
           if (type === 'min') {
-            this.minDate = new Date(parsedValue);
+            this.minDate = modifyDate(this.minDate || new Date(), parsedValue.getFullYear(), parsedValue.getMonth(), parsedValue.getDate());
             this.leftDate = new Date(parsedValue);
-            this.rightDate = nextMonth(this.leftDate);
+            if (!this.unlinkPanels) {
+              this.rightDate = nextMonth(this.leftDate);
+            }
           } else {
-            this.maxDate = new Date(parsedValue);
-            this.leftDate = prevMonth(parsedValue);
+            this.maxDate = modifyDate(this.maxDate || new Date(), parsedValue.getFullYear(), parsedValue.getMonth(), parsedValue.getDate());
             this.rightDate = new Date(parsedValue);
+            if (!this.unlinkPanels) {
+              this.leftDate = prevMonth(parsedValue);
+            }
           }
         }
       },
 
-      handleDateChange(event, type) {
-        const value = event.target.value;
+      handleDateChange(value, type) {
         const parsedValue = parseDate(value, this.dateFormat);
         if (parsedValue) {
           if (type === 'min') {
@@ -473,8 +497,23 @@
         }
       },
 
-      handleTimeChange(event, type) {
-        const value = event.target.value;
+      handleTimeInput(value, type) {
+        this.timeUserInput[type] = value;
+        if (value.length !== this.timeFormat.length) return;
+        const parsedValue = parseDate(value, this.timeFormat);
+
+        if (parsedValue) {
+          if (type === 'min') {
+            this.minDate = modifyTime(this.minDate, parsedValue.getHours(), parsedValue.getMinutes(), parsedValue.getSeconds());
+            this.$nextTick(_ => this.$refs.minTimePicker.adjustSpinners());
+          } else {
+            this.maxDate = modifyTime(this.maxDate, parsedValue.getHours(), parsedValue.getMinutes(), parsedValue.getSeconds());
+            this.$nextTick(_ => this.$refs.maxTimePicker.adjustSpinners());
+          }
+        }
+      },
+
+      handleTimeChange(value, type) {
         const parsedValue = parseDate(value, this.timeFormat);
         if (parsedValue) {
           if (type === 'min') {

+ 15 - 8
packages/input-number/src/input-number.vue

@@ -28,7 +28,7 @@
     </span>
     <el-input
       ref="input"
-      :value="currentInputValue"
+      :value="displayValue"
       :placeholder="placeholder"
       :disabled="inputNumberDisabled"
       :size="inputNumberSize"
@@ -40,6 +40,7 @@
       @keydown.down.native.prevent="decrease"
       @blur="handleBlur"
       @focus="handleFocus"
+      @input="handleInput"
       @change="handleInputChange">
     </el-input>
   </div>
@@ -102,7 +103,8 @@
     },
     data() {
       return {
-        currentValue: 0
+        currentValue: 0,
+        userInput: null
       };
     },
     watch: {
@@ -121,6 +123,7 @@
           if (newVal >= this.max) newVal = this.max;
           if (newVal <= this.min) newVal = this.min;
           this.currentValue = newVal;
+          this.userInput = null;
           this.$emit('input', newVal);
         }
       }
@@ -156,7 +159,10 @@
       inputNumberDisabled() {
         return this.disabled || (this.elForm || {}).disabled;
       },
-      currentInputValue() {
+      displayValue() {
+        if (this.userInput !== null) {
+          return this.userInput;
+        }
         const currentValue = this.currentValue;
         if (typeof currentValue === 'number' && this.precision !== undefined) {
           return currentValue.toFixed(this.precision);
@@ -208,7 +214,6 @@
       },
       handleBlur(event) {
         this.$emit('blur', event);
-        this.$refs.input.setCurrentValue(this.currentInputValue);
       },
       handleFocus(event) {
         this.$emit('focus', event);
@@ -220,19 +225,21 @@
         }
         if (newVal >= this.max) newVal = this.max;
         if (newVal <= this.min) newVal = this.min;
-        if (oldVal === newVal) {
-          this.$refs.input.setCurrentValue(this.currentInputValue);
-          return;
-        }
+        if (oldVal === newVal) return;
+        this.userInput = null;
         this.$emit('input', newVal);
         this.$emit('change', newVal, oldVal);
         this.currentValue = newVal;
       },
+      handleInput(value) {
+        this.userInput = value;
+      },
       handleInputChange(value) {
         const newVal = value === '' ? undefined : Number(value);
         if (!isNaN(newVal) || value === '') {
           this.setCurrentValue(newVal);
         }
+        this.userInput = null;
       },
       select() {
         this.$refs.input.select();

+ 26 - 34
packages/input/src/input.vue

@@ -28,7 +28,7 @@
         :disabled="inputDisabled"
         :readonly="readonly"
         :autocomplete="autoComplete || autocomplete"
-        :value="currentValue"
+        :value="nativeInputValue"
         ref="input"
         @compositionstart="handleComposition"
         @compositionupdate="handleComposition"
@@ -78,7 +78,7 @@
       v-else
       :tabindex="tabindex"
       class="el-textarea__inner"
-      :value="currentValue"
+      :value="nativeInputValue"
       @compositionstart="handleComposition"
       @compositionupdate="handleComposition"
       @compositionend="handleComposition"
@@ -102,7 +102,6 @@
   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';
 
   export default {
     name: 'ElInput',
@@ -124,14 +123,10 @@
 
     data() {
       return {
-        currentValue: this.value === undefined || this.value === null
-          ? ''
-          : this.value,
         textareaCalcStyle: {},
         hovering: false,
         focused: false,
-        isOnComposition: false,
-        valueBeforeComposition: null
+        isOnComposition: false
       };
     },
 
@@ -203,18 +198,24 @@
       inputDisabled() {
         return this.disabled || (this.elForm || {}).disabled;
       },
+      nativeInputValue() {
+        return this.value === null || this.value === undefined ? '' : this.value;
+      },
       showClear() {
         return this.clearable &&
           !this.inputDisabled &&
           !this.readonly &&
-          this.currentValue !== '' &&
+          this.nativeInputValue &&
           (this.focused || this.hovering);
       }
     },
 
     watch: {
-      value(val, oldValue) {
-        this.setCurrentValue(val);
+      value(val) {
+        this.$nextTick(this.resizeTextarea);
+        if (this.validateEvent) {
+          this.dispatch('ElFormItem', 'el.form.change', [val]);
+        }
       }
     },
 
@@ -240,7 +241,7 @@
         this.focused = false;
         this.$emit('blur', event);
         if (this.validateEvent) {
-          this.dispatch('ElFormItem', 'el.form.blur', [this.currentValue]);
+          this.dispatch('ElFormItem', 'el.form.blur', [this.value]);
         }
       },
       select() {
@@ -266,38 +267,30 @@
         this.$emit('focus', event);
       },
       handleComposition(event) {
+        if (event.type === 'compositionstart') {
+          this.isOnComposition = true;
+        }
         if (event.type === 'compositionend') {
           this.isOnComposition = false;
-          this.currentValue = this.valueBeforeComposition;
-          this.valueBeforeComposition = null;
           this.handleInput(event);
-        } else {
-          const text = event.target.value;
-          const lastCharacter = text[text.length - 1] || '';
-          this.isOnComposition = !isKorean(lastCharacter);
-          if (this.isOnComposition && event.type === 'compositionstart') {
-            this.valueBeforeComposition = text;
-          }
         }
       },
       handleInput(event) {
-        const value = event.target.value;
-        this.setCurrentValue(value);
         if (this.isOnComposition) return;
-        this.$emit('input', value);
+
+        // 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);
+
+        // set input's value, in case parent refuses the change
+        // see: https://github.com/ElemeFE/element/issues/12850
+        this.$nextTick(() => { this.$refs.input.value = this.value; });
       },
       handleChange(event) {
         this.$emit('change', event.target.value);
       },
-      setCurrentValue(value) {
-        if (this.isOnComposition && value === this.valueBeforeComposition) return;
-        this.currentValue = value;
-        if (this.isOnComposition) return;
-        this.$nextTick(this.resizeTextarea);
-        if (this.validateEvent && this.currentValue === this.value) {
-          this.dispatch('ElFormItem', 'el.form.change', [value]);
-        }
-      },
       calcIconOffset(place) {
         let elList = [].slice.call(this.$el.querySelectorAll(`.el-input__${place}`) || []);
         if (!elList.length) return;
@@ -329,7 +322,6 @@
         this.$emit('input', '');
         this.$emit('change', '');
         this.$emit('clear');
-        this.setCurrentValue('');
       }
     },
 

+ 19 - 58
packages/pagination/src/pagination.js

@@ -215,56 +215,36 @@ export default {
     Jumper: {
       mixins: [Locale],
 
+      components: { ElInput },
+
       data() {
         return {
-          oldValue: null
+          userInput: null
         };
       },
 
-      components: { ElInput },
-
       watch: {
-        '$parent.internalPageSize'() {
-          this.$nextTick(() => {
-            this.$refs.input.$el.querySelector('input').value = this.$parent.internalCurrentPage;
-          });
+        '$parent.internalCurrentPage'() {
+          this.userInput = null;
         }
       },
 
       methods: {
-        handleFocus(event) {
-          this.oldValue = event.target.value;
-        },
-        handleBlur({ target }) {
-          this.resetValueIfNeed(target.value);
-          this.reassignMaxValue(target.value);
-        },
         handleKeyup({ keyCode, target }) {
-          if (keyCode === 13 && this.oldValue && target.value !== this.oldValue) {
+          // Chrome, Safari, Firefox triggers change event on Enter
+          // Hack for IE: https://github.com/ElemeFE/element/issues/11710
+          // Drop this method when we no longer supports IE
+          if (keyCode === 13) {
             this.handleChange(target.value);
           }
         },
+        handleInput(value) {
+          this.userInput = value;
+        },
         handleChange(value) {
           this.$parent.internalCurrentPage = this.$parent.getValidCurrentPage(value);
           this.$parent.emitChange();
-          this.oldValue = null;
-          this.resetValueIfNeed(value);
-        },
-        resetValueIfNeed(value) {
-          const num = parseInt(value, 10);
-          if (!isNaN(num)) {
-            if (num < 1) {
-              this.$refs.input.setCurrentValue(1);
-            } else {
-              this.reassignMaxValue(value);
-            }
-          }
-        },
-        reassignMaxValue(value) {
-          const { internalPageCount } = this.$parent;
-          if (+value > internalPageCount) {
-            this.$refs.input.setCurrentValue(internalPageCount || 1);
-          }
+          this.userInput = null;
         }
       },
 
@@ -276,15 +256,12 @@ export default {
               class="el-pagination__editor is-in-pagination"
               min={ 1 }
               max={ this.$parent.internalPageCount }
-              value={ this.$parent.internalCurrentPage }
-              domPropsValue={ this.$parent.internalCurrentPage }
+              value={ this.userInput !== null ? this.userInput : this.$parent.internalCurrentPage }
               type="number"
-              ref="input"
               disabled={ this.$parent.disabled }
               nativeOnKeyup={ this.handleKeyup }
-              onChange={ this.handleChange }
-              onFocus={ this.handleFocus }
-              onBlur={ this.handleBlur }/>
+              onInput={ this.handleInput }
+              onChange={ this.handleChange }/>
             { this.t('el.pagination.pageClassifier') }
           </span>
         );
@@ -380,7 +357,7 @@ export default {
     currentPage: {
       immediate: true,
       handler(val) {
-        this.internalCurrentPage = val;
+        this.internalCurrentPage = this.getValidCurrentPage(val);
       }
     },
 
@@ -393,24 +370,8 @@ export default {
 
     internalCurrentPage: {
       immediate: true,
-      handler(newVal, oldVal) {
-        newVal = parseInt(newVal, 10);
-
-        /* istanbul ignore if */
-        if (isNaN(newVal)) {
-          newVal = oldVal || 1;
-        } else {
-          newVal = this.getValidCurrentPage(newVal);
-        }
-
-        if (newVal !== undefined) {
-          this.internalCurrentPage = newVal;
-          if (oldVal !== newVal) {
-            this.$emit('update:currentPage', newVal);
-          }
-        } else {
-          this.$emit('update:currentPage', newVal);
-        }
+      handler(newVal) {
+        this.$emit('update:currentPage', newVal);
         this.lastEmittedPage = -1;
       }
     },

+ 4 - 4
test/unit/specs/date-picker.spec.js

@@ -2159,16 +2159,16 @@ describe('DatePicker', () => {
         triggerEvent(rightCell, 'click', true);
 
         setTimeout(_ => {
-          triggerEvent(input2, 'input');
           input2.value = '1988-6-4';
+          triggerEvent(input2, 'input');
           triggerEvent(input2, 'change');
 
           setTimeout(_ => {
-            triggerEvent(input, 'input');
             input.value = '1989-6-4';
+            triggerEvent(input, 'input');
             triggerEvent(input, 'change', true);
             setTimeout(_ => {
-              expect(vm.picker.maxDate > vm.picker.minDate).to.true;
+              expect(vm.picker.maxDate >= vm.picker.minDate).to.true;
               done();
             }, DELAY);
           }, DELAY);
@@ -2213,7 +2213,7 @@ describe('DatePicker', () => {
       input.focus();
       setTimeout(_ => {
         // simulate user input of invalid date
-        vm.$refs.compo.picker.handleDateChange({ target: { value: '2000-09-01'} }, 'min');
+        vm.$refs.compo.picker.handleDateChange('2000-09-01', 'min');
         setTimeout(_ => {
           expect(vm.$refs.compo.picker.btnDisabled).to.equal(true); // invalid input disables button
           vm.$refs.compo.picker.handleConfirm();

+ 2 - 1
test/unit/specs/input.spec.js

@@ -194,7 +194,8 @@ describe('Input', () => {
       `,
       data() {
         return {
-          value: '1234'
+          value: '1234',
+          select: '1'
         };
       }
     }, true);

+ 0 - 1
test/unit/specs/pagination.spec.js

@@ -249,7 +249,6 @@ describe('Pagination', () => {
     const input = vm.inputer;
     const changeValue = (value) => {
       input.$emit('input', value);
-      input.setCurrentValue(value);
       input.$emit('change', value);
     };