element Input源码
<template> <div :class="[ type === 'textarea' ? 'el-textarea' : 'el-input', inputSize ? 'el-input--' + inputSize : '', { 'is-disabled': inputDisabled, 'is-exceed': inputExceed, 'el-input-group': $slots.prepend || $slots.append, '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 } ]" @mouseenter="hovering = true" @mouseleave="hovering = false" > <template v-if="type !== 'textarea'"> <!-- 前置元素 --> <div class="el-input-group__prepend" v-if="$slots.prepend"> <slot name="prepend"></slot> </div> <input :tabindex="tabindex" v-if="type !== 'textarea'" class="el-input__inner" v-bind="$attrs" :type="showPassword ? (passwordVisible ? 'text': 'password') : type" :disabled="inputDisabled" :readonly="readonly" :autocomplete="autoComplete || autocomplete" ref="input" @compositionstart="handleCompositionStart" @compositionend="handleCompositionEnd" @input="handleInput" @focus="handleFocus" @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> </span> <!-- 后置内容 --> <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> </template> <i v-if="showClear" class="el-input__icon el-icon-circle-close el-input__clear" @click="clear" ></i> <i v-if="showPwdVisible" class="el-input__icon el-icon-view el-input__clear" @click="handlePasswordVisible" ></i> <span v-if="isWordLimitVisible" class="el-input__count"> <span class="el-input__count-inner"> {{ textLength }}/{{ upperLimit }} </span> </span> </span> <i class="el-input__icon" v-if="validateState" :class="['el-input__validateIcon', validateIcon]"> </i> </span> <!-- 后置元素 --> <div class="el-input-group__append" v-if="$slots.append"> <slot name="append"></slot> </div> </template> <textarea v-else :tabindex="tabindex" class="el-textarea__inner" @compositionstart="handleCompositionStart" @compositionend="handleCompositionEnd" @input="handleInput" ref="textarea" v-bind="$attrs" :disabled="inputDisabled" :readonly="readonly" :autocomplete="autoComplete || autocomplete" :style="textareaStyle" @focus="handleFocus" @blur="handleBlur" @change="handleChange" :aria-label="label" > </textarea> <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'; export default { name: 'ElInput', componentName: 'ElInput', mixins: [emitter, Migrating], inheritAttrs: false, inject: { elForm: { default: '' }, elFormItem: { default: '' } }, data() { return { textareaCalcStyle: {}, hovering: false, focused: false, isComposing: false, passwordVisible: false }; }, props: { // value / v-model 绑定值 string / number — value: [String, Number], // 输入框尺寸,只在 type!="textarea" 时有效 string medium / small / mini size: String, // 控制是否能被用户缩放 string none, both, horizontal, vertical resize: String, // 原生属性 string form: String, // disabled 禁用 boolean disabled: Boolean, // 原生属性,是否只读 readonly: Boolean, // 类型 string text,textarea 和其他 原生 input 的 type 值 type: { type: String, default: 'text' }, // 自适应内容高度,只对 type="textarea" 有效,可传入对象,如,{ minRows: 2, maxRows: 6 } boolean / object autosize: { type: [Boolean, Object], default: false }, // 原生属性,自动补全 string on, off autocomplete: { type: String, default: 'off' }, /** @Deprecated in next major version */ // 下个主版本弃用 string on, off autoComplete: { 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.'); return true; } }, // 输入时是否触发表单的校验 validateEvent: { type: Boolean, default: true }, // 输入框尾部图标 suffixIcon: String, // 输入框头部图标 prefixIcon: String, // 输入框关联的label文字 label: String, // 是否可清空 clearable: { type: Boolean, default: false }, // 是否显示切换密码图标 showPassword: { type: Boolean, default: false }, // 是否显示输入字数统计,只在 type = "text" 或 type = "textarea" 时有效 showWordLimit: { type: Boolean, default: false }, // 输入框的tabindex tabindex: String }, computed: { // 尺寸大小 _elFormItemSize() { return (this.elFormItem || {}).elFormItemSize; }, // 验证状态 validateState() { return this.elFormItem ? this.elFormItem.validateState : ''; }, needStatusIcon() { return this.elForm ? this.elForm.statusIcon : false; }, // 验证的icon validateIcon() { return { validating: 'el-icon-loading', success: 'el-icon-circle-check', error: 'el-icon-circle-close' }[this.validateState]; }, // 输入框textarea的样式 textareaStyle() { return merge({}, this.textareaCalcStyle, { resize: this.resize }); }, // 输入框的尺寸 inputSize() { return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size; }, // 是否禁用 inputDisabled() { return this.disabled || (this.elForm || {}).disabled; }, // 监听原生值 nativeInputValue() { return this.value === null || this.value === undefined ? '' : String(this.value); }, // 是否显示清空 showClear() { return this.clearable && !this.inputDisabled && !this.readonly && this.nativeInputValue && (this.focused || this.hovering); }, // 是否显示可视密码图标 showPwdVisible() { return this.showPassword && !this.inputDisabled && !this.readonly && (!!this.nativeInputValue || this.focused); }, // 是否显示字数统计 isWordLimitVisible() { return this.showWordLimit && this.$attrs.maxlength && (this.type === 'text' || this.type === 'textarea') && !this.inputDisabled && !this.readonly && !this.showPassword; }, // 最大长度 upperLimit() { return this.$attrs.maxlength; }, // 输入框文本的长度 textLength() { if (typeof this.value === 'number') { return String(this.value).length; } return (this.value || '').length; }, inputExceed() { // show exceed style if length of initial value greater then maxlength // 如果初始值的长度大于maxlength,则显示最大输入长度 return this.isWordLimitVisible && (this.textLength > this.upperLimit); } }, watch: { value(val) { this.$nextTick(this.resizeTextarea); 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 // 原生value改变 nativeInputValue() { this.setNativeInputValue(); }, // when change between <input> and <textarea>, // update DOM dependent value and styles // https://github.com/ElemeFE/element/issues/14857 // type值改变 type() { this.$nextTick(() => { this.setNativeInputValue(); this.resizeTextarea(); this.updateIconOffset(); }); } }, methods: { // 聚焦 focus() { this.getInput().focus(); }, // 失焦 blur() { this.getInput().blur(); }, getMigratingConfig() { return { props: { 'icon': 'icon is removed, use suffix-icon / prefix-icon instead.', 'on-icon-click': 'on-icon-click is removed.' }, events: { 'click': 'click is removed.' } }; }, // 失焦 handleBlur(event) { this.focused = false; this.$emit('blur', event); if (this.validateEvent) { this.dispatch('ElFormItem', 'el.form.blur', [this.value]); } }, // 选中 select() { this.getInput().select(); }, // textarea变化执行函数 resizeTextarea() { if (this.$isServer) return; const { autosize, type } = this; if (type !== 'textarea') return; if (!autosize) { // 样式 this.textareaCalcStyle = { // 动态计算textarea的最小高度 minHeight: calcTextareaHeight(this.$refs.textarea).minHeight }; return; } const minRows = autosize.minRows; const maxRows = autosize.maxRows; this.textareaCalcStyle = calcTextareaHeight(this.$refs.textarea, minRows, maxRows); }, // 设置原生nativeInputValue为value 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); }, // 监听输入法开始 handleCompositionStart() { this.isComposing = true; }, // 监听输入法输入完毕事件 handleCompositionEnd(event) { this.isComposing = false; // 触发input事件 this.handleInput(event); }, handleInput(event) { // 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); // ensure native input value is controlled // see: https://github.com/ElemeFE/element/issues/12850 this.$nextTick(this.setNativeInputValue); }, // 内容改变事件 handleChange(event) { this.$emit('change', event.target.value); }, // 计算icon的宽度 calcIconOffset(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++) { if (elList[i].parentNode === this.$el) { el = elList[i]; break; } } if (!el) return; const pendantMap = { suffix: 'append', prefix: 'prepend' }; const pendant = pendantMap[place]; if (this.$slots[pendant]) { el.style.transform = `translateX(${place === 'suffix' ? '-' : ''}${this.$el.querySelector(`.el-input-group__${pendant}`).offsetWidth}px)`; } else { el.removeAttribute('style'); } }, // 更新icon位置 updateIconOffset() { this.calcIconOffset('prefix'); this.calcIconOffset('suffix'); }, // 点击清空图标 clear() { // 触发父级事件 /* blur 在 Input 失去焦点时触发 (event: Event) focus 在 Input 获得焦点时触发 (event: Event) change 在 Input 值改变时触发 (value: string | number) clear 在点击由 clearable 属性生成的清空按钮时触发 — */ this.$emit('input', ''); this.$emit('change', ''); this.$emit('clear'); }, // 点击可视/不可视按钮 handlePasswordVisible() { this.passwordVisible = !this.passwordVisible; this.focus(); }, // 获取inptu或者textarea元素 getInput() { return this.$refs.input || this.$refs.textarea; }, // 判断后缀是否显示 getSuffixVisible() { return this.$slots.suffix || this.suffixIcon || this.showClear || this.showPassword || this.isWordLimitVisible || (this.validateState && this.needStatusIcon); } }, created() { // 接收inputSelect事件, this.$on('inputSelect', this.select); }, mounted() { this.setNativeInputValue(); this.resizeTextarea(); this.updateIconOffset(); }, updated() { this.$nextTick(this.updateIconOffset); } }; </script>