element radio源码

src/radio.vue

<template>
  <label
    class="el-radio"
    :class="[
      border && radioSize ? 'el-radio--' + radioSize : '',
      { 'is-disabled': isDisabled },
      { 'is-focus': focus },
      { 'is-bordered': border },
      { 'is-checked': model === label }
    ]"
    role="radio"
    :aria-checked="model === label"
    :aria-disabled="isDisabled"
    :tabindex="tabIndex"
    @keydown.space.stop.prevent="model = isDisabled ? model : label"
  >
    <span class="el-radio__input"
      :class="{
        'is-disabled': isDisabled,
        'is-checked': model === label
      }"
    >
      <span class="el-radio__inner"></span>
      <input
        class="el-radio__original"
        :value="label"
        type="radio"
        aria-hidden="true"
        v-model="model"
        @focus="focus = true"
        @blur="focus = false"
        @change="handleChange"
        :name="name"
        :disabled="isDisabled"
        tabindex="-1"
      >
    </span>
    <span class="el-radio__label" @keydown.stop>
      <slot></slot>
      <template v-if="!$slots.default">{{label}}</template>
    </span>
  </label>
</template>
<script>
  import Emitter from 'element-ui/src/mixins/emitter';

  export default {
    name: 'ElRadio',

    mixins: [Emitter],

    inject: {
      elForm: {
        default: ''
      },

      elFormItem: {
        default: ''
      }
    },

    componentName: 'ElRadio',

    props: {
      // value / v-model    绑定值    string / number / boolean
      value: {},
      // Radio 的 value    string / number / boolean
      label: {},
      // 是否禁用    boolean    —    false
      disabled: Boolean,
      // 原生 name 属性    string
      name: String,
      // Radio 的尺寸,仅在 border 为真时有效    string    medium / small / mini
      border: Boolean,
      // 是否显示边框    boolean    —    false
      size: String
    },

    data() {
      return {
        focus: false
      };
    },
    computed: {
      // 是否是radioGroup
      isGroup() {
        let parent = this.$parent;
        while (parent) {
          if (parent.$options.componentName !== 'ElRadioGroup') {
            parent = parent.$parent;
          } else {
            this._radioGroup = parent;
            return true;
          }
        }
        return false;
      },
      model: {
        // 取值
        get() {
          return this.isGroup ? this._radioGroup.value : this.value;
        },
        // 设置值
        set(val) {
          if (this.isGroup) {
            this.dispatch('ElRadioGroup', 'input', [val]);
          } else {
            this.$emit('input', val);
          }
        }
      },
      _elFormItemSize() {
        return (this.elFormItem || {}).elFormItemSize;
      },
      // 大小
      radioSize() {
        const temRadioSize = this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
        return this.isGroup
          ? this._radioGroup.radioGroupSize || temRadioSize
          : temRadioSize;
      },
      // 是否禁用
      isDisabled() {
        return this.isGroup
          ? this._radioGroup.disabled || this.disabled || (this.elForm || {}).disabled
          : this.disabled || (this.elForm || {}).disabled;
      },
      tabIndex() {
        return (this.isDisabled || (this.isGroup && this.model !== this.label)) ? -1 : 0;
      }
    },

    methods: {
      handleChange() {
        this.$nextTick(() => {
          // 向外抛出chang事件并传值
          this.$emit('change', this.model);
          // 如果是group,通知ElRadioGroup,执行handleChange事件,并传值this.model
          this.isGroup && this.dispatch('ElRadioGroup', 'handleChange', this.model);
        });
      }
    }
  };
</script>

src/radio-group.vue

<template>
  <div
    class="el-radio-group"
    role="radiogroup"
    @keydown="handleKeydown"
  >
    <slot></slot>
  </div>
</template>
<script>
  import Emitter from 'element-ui/src/mixins/emitter';
  // Object.freeze,冻结对象
  const keyCode = Object.freeze({
    LEFT: 37,
    UP: 38,
    RIGHT: 39,
    DOWN: 40
  });
  export default {
    name: 'ElRadioGroup',

    componentName: 'ElRadioGroup',

    inject: {
      elFormItem: {
        default: ''
      }
    },

    mixins: [Emitter],

    props: {
      // 绑定值    string / number / boolean    
      value: {},
      // 单选框组尺寸,仅对按钮形式的 Radio 或带有边框的 Radio 有效    string    medium / small / mini    
      size: String,
      // 按钮形式的 Radio 激活时的填充色和边框色    string    —    #409EFF
      fill: String,
      // 按钮形式的 Radio 激活时的文本颜色    string    —    #ffffff
      textColor: String,
      // 是否禁用
      disabled: Boolean
    },

    computed: {
      _elFormItemSize() {
        return (this.elFormItem || {}).elFormItemSize;
      },
      // 大小
      radioGroupSize() {
        return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
      }
    },

    created() {
      // 接收handleChange事件
      this.$on('handleChange', value => {
        // 向外抛出change并传值
        this.$emit('change', value);
      });
    },
    mounted() {
      // 当radioGroup没有默认选项时,第一个可以选中Tab导航
      const radios = this.$el.querySelectorAll('[type=radio]');
      const firstLabel = this.$el.querySelectorAll('[role=radio]')[0];
      if (![].some.call(radios, radio => radio.checked) && firstLabel) {
        firstLabel.tabIndex = 0;
      }
    },
    methods: {
      handleKeydown(e) { // 左右上下按键 可以在radio组内切换不同选项
        const target = e.target;
        const className = target.nodeName === 'INPUT' ? '[type=radio]' : '[role=radio]';
        const radios = this.$el.querySelectorAll(className);
        const length = radios.length;
        const index = [].indexOf.call(radios, target);
        const roleRadios = this.$el.querySelectorAll('[role=radio]');
        switch (e.keyCode) {
          // 点击左箭头和上箭头
          case keyCode.LEFT:
          case keyCode.UP:
            e.stopPropagation();
            e.preventDefault();
            // 如果是第一个,选中最后一个兵聚焦
            if (index === 0) {
              roleRadios[length - 1].click();
              roleRadios[length - 1].focus();
            } else {
              // 向前选
              roleRadios[index - 1].click();
              roleRadios[index - 1].focus();
            }
            break;
          // 点击下箭头和右箭头
          case keyCode.RIGHT:
          case keyCode.DOWN:
          // 如果是最后一个
            if (index === (length - 1)) {
              e.stopPropagation();
              e.preventDefault();
              // 选中第一个并聚焦
              roleRadios[0].click();
              roleRadios[0].focus();
            } else {
              // 向后选
              roleRadios[index + 1].click();
              roleRadios[index + 1].focus();
            }
            break;
          default:
            break;
        }
      }
    },
    watch: {
      value(value) {
        // 通知ElFormItem组件,执行el.form.change方法,并传值[this.value]
        this.dispatch('ElFormItem', 'el.form.change', [this.value]);
      }
    }
  };
</script>

src/radio-button.vue

<template>
  <label
    class="el-radio-button"
    :class="[
      size ? 'el-radio-button--' + size : '',
      { 'is-active': value === label },
      { 'is-disabled': isDisabled },
      { 'is-focus': focus }
    ]"
    role="radio"
    :aria-checked="value === label"
    :aria-disabled="isDisabled"
    :tabindex="tabIndex"
    @keydown.space.stop.prevent="value = isDisabled ? value : label"
  >
    <input
      class="el-radio-button__orig-radio"
      :value="label"
      type="radio"
      v-model="value"
      :name="name"
      @change="handleChange"
      :disabled="isDisabled"
      tabindex="-1"
      @focus="focus = true"
      @blur="focus = false"
    >
    <span
      class="el-radio-button__inner"
      :style="value === label ? activeStyle : null"
      @keydown.stop>
      <slot></slot>
      <template v-if="!$slots.default">{{label}}</template>
    </span>
  </label>
</template>
<script>
  import Emitter from 'element-ui/src/mixins/emitter';

  export default {
    name: 'ElRadioButton',

    mixins: [Emitter],

    inject: {
      elForm: {
        default: ''
      },
      elFormItem: {
        default: ''
      }
    },

    props: {
      // Radio 的 value    string / number
      label: {},
      // 是否禁用    boolean    —    false
      disabled: Boolean,
      // 原生 name 属性    string
      name: String
    },
    data() {
      return {
        focus: false
      };
    },
    computed: {
      // 动态计算value
      value: {
        // 取值
        get() {
          return this._radioGroup.value;
        },
        // 设置值
        set(value) {
          this._radioGroup.$emit('input', value);
        }
      },
      // radioGroup组件
      _radioGroup() {
        let parent = this.$parent;
        while (parent) {
          if (parent.$options.componentName !== 'ElRadioGroup') {
            parent = parent.$parent;
          } else {
            return parent;
          }
        }
        return false;
      },
      // 激活的样式
      activeStyle() {
        return {
          backgroundColor: this._radioGroup.fill || '',
          borderColor: this._radioGroup.fill || '',
          boxShadow: this._radioGroup.fill ? `-1px 0 0 0 ${this._radioGroup.fill}` : '',
          color: this._radioGroup.textColor || ''
        };
      },
      // elFormItem大小
      _elFormItemSize() {
        return (this.elFormItem || {}).elFormItemSize;
      },
      // 大小
      size() {
        return this._radioGroup.radioGroupSize || this._elFormItemSize || (this.$ELEMENT || {}).size;
      },
      // 是否禁用
      isDisabled() {
        return this.disabled || this._radioGroup.disabled || (this.elForm || {}).disabled;
      },
      tabIndex() {
        return (this.isDisabled || (this._radioGroup && this.value !== this.label)) ? -1 : 0;
      }
    },

    methods: {
      // 改变事件
      handleChange() {
        this.$nextTick(() => {
          // 通知ElRadioGroup组件触发handleChange事件,传值this.value
          this.dispatch('ElRadioGroup', 'handleChange', this.value);
        });
      }
    }
  };
</script>

 

posted on 2019-06-04 20:28  心痛随缘  阅读(939)  评论(0编辑  收藏  举报