all files / src/popover/ popover.vue

68.75% Statements 33/48
57.14% Branches 16/28
78.57% Functions 11/14
68.75% Lines 33/48
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
<template>
    <div class="am-popover-wrapper" ref="popoverWrapper" :data-zindex="zIndex">
        <transition name="fade">
            <div class="am-popover-content" ref="popoverContent" v-if="isVisible" :class="`position-${position}`">
                <slot name="popover" :close="close"></slot>
            </div>
        </transition>
        <div class="am-popover-trigger" ref="trigger">
            <slot></slot>
        </div>
    </div>
</template>
 
<script>
    export default {
        name: "amPopover",
        props: {
            position: {
                type: String,
                default: "top",
                validator(val) {
                    return ["top", "right", "bottom", "left"].indexOf(val) >= 0;
                }
            },
            trigger: {
                type: String,
                default: "click",
                validator(val) {
                    return ["click", "hover"].indexOf(val) >= 0;
                }
            },
            container: {
                type: Element
            },
            disabled: {
                type: Boolean,
                default: false
            },
            zIndex: {
                type: [String, Number],
                default: 10
            }
        },
        data() {
            return {
                isVisible: false
            };
        },
        mounted() {
            if (this.disabled) {
                return false;
            }
            this.addPopoverListeners();
        },
        beforeDestroy() {
            this.putBackContent();
            if (!this.disabled) {
                this.removePopoverListeners();
            }
        },
        methods: {
            addPopoverListeners() {
                if (this.trigger === "click") {
                    this.$refs.popoverWrapper.addEventListener("click", this.onClick);
                } else {
                    this.$refs.popoverWrapper.addEventListener("mouseenter", this.open);
                    this.$refs.popoverWrapper.addEventListener("mouseleave", this.close);
                }
            },
            removePopoverListeners() {
                if (this.trigger === "click") {
                    this.$refs.popoverWrapper.removeEventListener("click", this.onClick);
                } else {
                    this.$refs.popoverWrapper.removeEventListener("mouseenter", this.open);
                    this.$refs.popoverWrapper.removeEventListener("mouseleave", this.close);
                }
            },
            putBackContent() {
                const {popoverContent, popoverWrapper} = this.$refs;
                if (!popoverContent) {
                    return;
                }
                if (popoverWrapper.nodeType === Node.ELEMENT_NODE) {
                    popoverWrapper.appendChild(popoverContent);
                }
            },
            positionContent() {
                const {popoverContent, trigger} = this.$refs;
                (this.container || document.body).appendChild(popoverContent);
                const {left, top, width, height} = trigger.getBoundingClientRect();
                let {height: height2} = popoverContent.getBoundingClientRect();
 
                const positions = {
                    top: {
                        top: top + window.scrollY,
                        left: left + window.scrollX
                    },
                    right: {
                        top: top + (height - height2) / 2 + window.scrollY,
                        left: left + width + window.scrollX
                    },
                    bottom: {
                        top: top + height + window.scrollY,
                        left: left + window.scrollX
                    },
                    left: {
                        top: top + (height - height2) / 2 + window.scrollY,
                        left: left + window.scrollX
                    }
                };
                popoverContent.style.top = positions[this.position]["top"] + "px";
                popoverContent.style.left = positions[this.position]["left"] + "px";
                popoverContent.style.zIndex = this.zIndex;
            },
            onClickDocument(e) {
            I    if (
                    this.$refs.popoverWrapper &&
                    (this.$refs.popoverWrapper.contains(e.target) ||
                        this.$refs.popoverWrapper === e.target)
                ) {
                    return;
                }
            I    if (
                    this.$refs.popoverContent &&
                    (this.$refs.popoverContent === e.target ||
                        this.$refs.popoverContent.contains(e.target))
                ) {
                    return;
                }
                this.close();
            },
            open() {
                this.isVisible = true;
                this.$emit("open");
                this.$nextTick(() => {
                    this.positionContent();
                    document.documentElement.addEventListener(
                        "click",
                        this.onClickDocument
                    );
                });
            },
            close() {
                this.isVisible = false;
                this.$emit("close");
                document.documentElement.removeEventListener(
                    "click",
                    this.onClickDocument
                );
            },
            onClick(event) {
                let isContains = this.$refs.trigger.contains(event.target);
            E    if (isContains) {
                I    if (this.isVisible === true) {
                        this.close();
                    } else {
                        this.open();
                    }
                }
            }
        }
    };
</script>
<style lang="scss" scoped>
    @import "var";
 
    .am-popover-wrapper {
        display: inline-block;
        vertical-align: top;
        position: relative;
 
        .am-popover-trigger {
            display: inline-block;
        }
    }
 
    .am-popover-content {
        position: absolute;
        padding: 0.5em 1em;
        max-width: 20em;
        border-radius: $border-radius;
        border: 1px solid $border-color;
        background: #fff;
        filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.5));
        word-break: break-all;
 
        &:before,
        &:after {
            content: "";
            position: absolute;
            width: 0;
            height: 0;
            border: 10px solid transparent;
        }
 
        &.position-top {
            margin-top: -10px;
            transform: translateY(-100%);
 
            &:before,
            &:after {
                left: 10px;
                border-bottom: 0;
            }
 
            &:before {
                top: 100%;
                border-top-color: #333;
            }
 
            &:after {
                border-top-color: white;
                top: calc(100% - 1px);
            }
        }
 
        &.position-bottom {
            margin-top: 10px;
 
            &:before,
            &:after {
                left: 10px;
                border-top: 0;
            }
 
            &:before {
                bottom: 100%;
                border-bottom-color: #333;
            }
 
            &:after {
                border-bottom-color: white;
                bottom: calc(100% - 1px);
            }
        }
 
        &.position-right {
            margin-left: 10px;
 
            &:before,
            &:after {
                top: 50%;
                border-left: 0;
                transform: translateY(-50%);
            }
 
            &:before {
                right: 100%;
                border-right-color: #333;
            }
 
            &:after {
                border-right-color: white;
                right: calc(100% - 1px);
            }
        }
 
        &.position-left {
            margin-left: -10px;
            transform: translateX(-100%);
 
            &:before,
            &:after {
                border-right: 0;
                top: 50%;
                transform: translateY(-50%);
            }
 
            &:before {
                left: 100%;
                border-left-color: #333;
            }
 
            &:after {
                border-left-color: white;
                left: calc(100% - 1px);
            }
        }
    }
</style>