index.js
import Popover from './src/main'; import directive from './src/directive'; import Vue from 'vue'; // 自定义指令 Vue.directive('popover', directive); /** * eg: * <el-popover ref="popover" placement="right" title="标题" width="200" trigger="focus" content="这是一段内容,这是一段内容,这是一段内容,这是一段内容。"> </el-popover> <el-button v-popover:popover>focus 激活</el-button> */ /* istanbul ignore next */ Popover.install = function (Vue) { Vue.directive('popover', directive); Vue.component(Popover.name, Popover); }; Popover.directive = directive; export default Popover;
directive.js
const getReference = (el, binding, vnode) => { const _ref = binding.expression ? binding.value : binding.arg; const popper = vnode.context.$refs[_ref]; if (popper) { if (Array.isArray(popper)) { popper[0].$refs.reference = el; } else { popper.$refs.reference = el; } } }; export default { // 绑定,只执行一次 bind (el, binding, vnode) { getReference(el, binding, vnode); }, // 插入父级 inserted (el, binding, vnode) { getReference(el, binding, vnode); } };
main.vue
<template> <span> <!-- after-enter 显示动画播放完毕后触发 --> <!-- after-leave 隐藏动画播放完毕后触发 --> <transition :name="transition" @after-enter="handleAfterEnter" @after-leave="handleAfterLeave"> <div class="el-popover el-popper" :class="[popperClass, content && 'el-popover--plain']" ref="popper" v-show="!disabled && showPopper" :style="{ width + 'px' }" role="tooltip" :id="tooltipId" :aria-hidden="(disabled || !showPopper) ? 'true' : 'false'" > <div class="el-popover__title" v-if="title" v-text="title"></div> <slot>{{ content }}</slot> </div> </transition> <slot name="reference"></slot> </span> </template> <script> import Popper from 'element-ui/src/utils/vue-popper'; import { on, off } from 'element-ui/src/utils/dom'; import { addClass, removeClass } from 'element-ui/src/utils/dom'; import { generateId } from 'element-ui/src/utils/util'; export default { name: 'ElPopover', mixins: [Popper], props: { // 触发方式 String click/focus/hover/manual click trigger: { type: String, default: 'click', validator: value => ['click', 'focus', 'hover', 'manual'].indexOf(value) > -1 }, // 触发方式为 hover 时的显示延迟,单位为毫秒 Number openDelay: { type: Number, default: 0 }, // title 标题 String title: String, // Popover 是否可用 Boolean disabled: Boolean, // 显示的内容,也可以通过 slot 传入 DOM content: String, // 触发 Popover 显示的 HTML 元素 reference: {}, // popper-class 为 popper 添加类名 String popperClass: String, // width 宽度 String, Number — 最小宽度 150px {}, // 是否显示 Tooltip 箭头,更多参数可见Vue-popper Boolean — true visibleArrow: { default: true }, // 出现位置的偏移量 Number — 0 arrowOffset: { type: Number, default: 0 }, // 定义渐变动画 String — fade-in-linear transition: { type: String, default: 'fade-in-linear' }, // Popover 组件的 tabindex number — 0 tabindex: { type: Number, default: 0 } }, computed: { tooltipId() { return `el-popover-${generateId()}`; } }, watch: { // 是否显示popper showPopper(val) { if (this.disabled) { return; } // show 显示时触发 // hide 隐藏时触发 val ? this.$emit('show') : this.$emit('hide'); } }, mounted() { let reference = this.referenceElm = this.reference || this.$refs.reference; const popper = this.popper || this.$refs.popper; if (!reference && this.$slots.reference && this.$slots.reference[0]) { reference = this.referenceElm = this.$slots.reference[0].elm; } // 可访问性 if (reference) { addClass(reference, 'el-popover__reference'); reference.setAttribute('aria-describedby', this.tooltipId); reference.setAttribute('tabindex', this.tabindex); // tab序列 popper.setAttribute('tabindex', 0); if (this.trigger !== 'click') { // 添加事件 on(reference, 'focusin', () => { this.handleFocus(); const instance = reference.__vue__; if (instance && typeof instance.focus === 'function') { instance.focus(); } }); on(popper, 'focusin', this.handleFocus); on(reference, 'focusout', this.handleBlur); on(popper, 'focusout', this.handleBlur); } on(reference, 'keydown', this.handleKeydown); on(reference, 'click', this.handleClick); } // 如果是点击触发 if (this.trigger === 'click') { // 绑定切换 on(reference, 'click', this.doToggle); // 点击文档,隐藏popper on(document, 'click', this.handleDocumentClick); } else if (this.trigger === 'hover') { // 增加鼠标移入移出事件 on(reference, 'mouseenter', this.handleMouseEnter); on(popper, 'mouseenter', this.handleMouseEnter); on(reference, 'mouseleave', this.handleMouseLeave); on(popper, 'mouseleave', this.handleMouseLeave); } else if (this.trigger === 'focus') { if (this.tabindex < 0) { console.warn('[Element Warn][Popover]a negative taindex means that the element cannot be focused by tab key'); } if (reference.querySelector('input, textarea')) { on(reference, 'focusin', this.doShow); on(reference, 'focusout', this.doClose); } else { on(reference, 'mousedown', this.doShow); on(reference, 'mouseup', this.doClose); } } }, // 销毁前 beforeDestroy() { this.cleanup(); }, // 销毁后 deactivated() { this.cleanup(); }, methods: { // 切换显示/隐藏 doToggle() { this.showPopper = !this.showPopper; }, // 显示 doShow() { this.showPopper = true; }, // 关闭 doClose() { this.showPopper = false; }, // 聚焦 handleFocus() { addClass(this.referenceElm, 'focusing'); if (this.trigger === 'click' || this.trigger === 'focus') this.showPopper = true; }, // 点击事件 handleClick() { removeClass(this.referenceElm, 'focusing'); }, // 失焦 handleBlur() { removeClass(this.referenceElm, 'focusing'); if (this.trigger === 'click' || this.trigger === 'focus') this.showPopper = false; }, // 鼠标移入 handleMouseEnter() { clearTimeout(this._timer); if (this.openDelay) { this._timer = setTimeout(() => { this.showPopper = true; }, this.openDelay); } else { this.showPopper = true; } }, // 键盘按下事件 handleKeydown(ev) { if (ev.keyCode === 27 && this.trigger !== 'manual') { // esc this.doClose(); } }, // 鼠标移出 handleMouseLeave() { clearTimeout(this._timer); this._timer = setTimeout(() => { this.showPopper = false; }, 200); }, // 点击文档事件 handleDocumentClick(e) { let reference = this.reference || this.$refs.reference; const popper = this.popper || this.$refs.popper; if (!reference && this.$slots.reference && this.$slots.reference[0]) { reference = this.referenceElm = this.$slots.reference[0].elm; } if (!this.$el || !reference || this.$el.contains(e.target) || reference.contains(e.target) || !popper || popper.contains(e.target)) return; this.showPopper = false; }, // 动画进入完成 handleAfterEnter() { // 向外部暴露after-enter事件 this.$emit('after-enter'); }, // 动画离开完成 handleAfterLeave() { // 向外部暴露after-leave事件 this.$emit('after-leave'); // 销毁 this.doDestroy(); }, // 清除 cleanup() { // 有延时,清除定时器 if (this.openDelay) { clearTimeout(this._timer); } } }, destroyed() { const reference = this.reference; // 卸载事件 off(reference, 'click', this.doToggle); off(reference, 'mouseup', this.doClose); off(reference, 'mousedown', this.doShow); off(reference, 'focusin', this.doShow); off(reference, 'focusout', this.doClose); off(reference, 'mousedown', this.doShow); off(reference, 'mouseup', this.doClose); off(reference, 'mouseleave', this.handleMouseLeave); off(reference, 'mouseenter', this.handleMouseEnter); off(document, 'click', this.handleDocumentClick); } }; </script>