index.vue 7.27 KB
<template>
  <div class="zui-input">
    <input
      class="zui-input__input"
      :type="inputType"
      :name="name"
      v-model="inputBindValue"
      :placeholder="inputPlaceholder"
      :disabled="disabled"
      :readonly="readonly"
      :maxlength="isInputFormative ? '' : maxlength"
      autocomplete="off"
      @focus="$_onFocus"
      @blur="$_onBlur"
      @keyup="$_onKeyup"
      @keydown="$_onKeydown"
      @input="$_onInput"
    />
    <div class="zui-input__clear" v-if="clearable && !disabled && !readonly" v-show="!isInputEmpty" @click="$_clearInput">
      <slot v-if="$slots.clear" name="clear"></slot>
      <zui-icon v-else name="close"></zui-icon>
    </div>
    <slot name="right"></slot>
  </div>
</template>

<script>
import ZuiIcon from '../icon';
import { getCursorsPosition, setCursorsPosition } from './cursor';
import { formatValueByGapRule, formatValueByGapStep, trimValue } from './formate-value';

export default {
  name: 'Input',
  components: {
    ZuiIcon,
  },
  props: {
    value: {
      type: [String, Number],
      default: '',
    },
    placeholder: {
      type: String,
      default: '',
    },
    maxlength: {
      type: [String, Number],
      default: '',
    },
    type: {
      // text, bankCard, password, phone, money, digit
      type: String,
      default: 'text',
    },
    name: {
      type: [String, Number],
    },
    clearable: {
      type: Boolean,
      default: false,
    },
    disabled: {
      type: Boolean,
      default: false,
    },
    readonly: {
      type: Boolean,
      default: false,
    },
    isFormative: {
      type: Boolean,
      default: false,
    },
    isTitleLatent: {
      type: Boolean,
      default: false,
    },
    formation: {
      type: Function,
      default: function () {},
    },
  },
  data() {
    return {
      inputValue: '',
      inputBindValue: '',
      isInputFocus: false,
    };
  },
  computed: {
    inputItemType() {
      return this.type || 'text';
    },
    inputType() {
      let inputType = this.inputItemType || 'text';
      if (inputType === 'bankCard' || inputType === 'phone' || inputType === 'digit') {
        inputType = 'tel';
      } else if (inputType === 'money') {
        inputType = 'text';
      }
      return inputType;
    },
    isInputEmpty() {
      return !this.inputValue.length;
    },
    isInputFormative() {
      const type = this.inputItemType;
      return this.isFormative || type === 'bankCard' || type === 'phone' || type === 'money' || type === 'digit';
    },
    inputPlaceholder() {
      return this.isTitleLatent && this.isInputActive ? '' : this.placeholder;
    },
    isInputActive() {
      return !this.isInputEmpty || this.isInputFocus;
    },
  },
  watch: {
    value(val) {
      // Filter out two-way binding
      if (val !== this.$_trimValue(this.inputValue)) {
        this.inputValue = this.$_formateValue(this.$_subValue(val + '')).value;
      }
    },
    inputValue(val) {
      this.inputBindValue = val;
      val = this.isInputFormative ? this.$_trimValue(val) : val;
      if (val !== this.value) {
        this.$emit('input', val);
        this.$emit('change', this.name, val);
      }
    },
  },
  created() {
    this.inputValue = this.$_formateValue(this.$_subValue(this.value + '')).value;
  },
  methods: {
    // MARK: private methods
    $_formateValue(curValue, curPos = 0) {
      const type = this.inputItemType;
      const name = this.name;
      const oldValue = this.inputValue;
      const isAdd = oldValue.length > curValue.length ? -1 : 1;

      let formateValue = { value: curValue, range: curPos };

      // no format
      if (!this.isInputFormative || curValue === '') {
        return formateValue;
      }

      // custom format by user
      const customValue = this.formation(name, curValue, curPos);

      if (customValue) {
        return customValue;
      }

      // default format by component
      let gap = ' ';
      switch (type) {
        case 'bankCard':
          curValue = this.$_subValue(trimValue(curValue.replace(/\D/g, '')));
          formateValue = formatValueByGapStep(4, curValue, gap, 'left', curPos, isAdd, oldValue);
          break;
        case 'phone':
          curValue = this.$_subValue(trimValue(curValue.replace(/\D/g, '')));
          formateValue = formatValueByGapRule('3|4|4', curValue, gap, curPos, isAdd);
          break;
        case 'money':
          gap = ',';
          curValue = this.$_subValue(trimValue(curValue.replace(/[^\d.]/g, '')));
          // curValue = curValue.replace(/\D/g, '')
          // eslint-disable-next-line no-case-declarations
          const dotPos = curValue.indexOf('.');
          // format if no dot or new add dot or insert befor dot
          // eslint-disable-next-line no-case-declarations
          const moneyCurValue = curValue.split('.')[0];
          // eslint-disable-next-line no-case-declarations
          const moneyCurDecimal = ~dotPos ? `.${curValue.split('.')[1]}` : '';

          formateValue = formatValueByGapStep(3, trimValue(moneyCurValue, gap), gap, 'right', curPos, isAdd, oldValue.split('.')[0]);
          formateValue.value += moneyCurDecimal;
          break;
        case 'digit':
          curValue = this.$_subValue(trimValue(curValue.replace(/[^\d.]/g, '')));
          formateValue.value = curValue;
          break;
        /* istanbul ignore next */
        default:
          break;
      }

      return formateValue;
    },
    $_subValue(val) {
      const len = this.inputMaxLength;
      if (len !== '') {
        return val.substring(0, len);
      } else {
        return val;
      }
    },
    $_onFocus() {
      this.isInputFocus = true;
      this.$emit('focus', this.name);
    },
    $_onBlur() {
      setTimeout(() => {
        this.isInputFocus = false;
        this.$emit('blur', this.name);
      }, 100);
    },
    $_onKeyup(event) {
      this.$emit('keyup', this.name, event);
      if (+event.keyCode === 13 || +event.keyCode === 108) {
        this.$emit('confirm', this.name, this.inputValue);
      }
    },
    $_onKeydown(event) {
      this.$emit('keydown', this.name, event);
    },
    $_onInput(event) {
      const formateValue = this.$_formateValue(event.target.value, this.isInputFormative ? getCursorsPosition(event.target) : 0);

      this.inputValue = formateValue.value;
      this.inputBindValue = formateValue.value;

      if (this.isInputFormative) {
        this.$nextTick(() => {
          setCursorsPosition(event.target, formateValue.range);
        });
      }
    },
    $_trimValue(val) {
      return trimValue(val, '\\s|,');
    },
    $_clearInput() {
      this.inputValue = '';
    },
    // MARK: public methods
    focus() {
      this.$el.querySelector('.zui-input__input').focus();
      setTimeout(() => {
        this.isInputFocus = true;
      }, 200);
    },
    blur() {
      this.$el.querySelector('.zui-input__input').blur();
      this.isInputFocus = false;
    },
    getValue() {
      return this.inputValue;
    },
  },
};
</script>

<style lang="scss">
.zui-input {
  min-height: 1.5rem;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: space-between;
  &__input {
    border: none;
    outline: none;
    flex: auto;
    width: 100%;
  }
  &__clear {
    white-space: nowrap;
    word-break: break-all;
    display: inline-block;
    color: $color-minor;
  }
}
</style>