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: 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 width: {}, // 是否顯示 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>