UNPKG

nly-adminlte-vue

Version:
232 lines (227 loc) 6.74 kB
import Vue from "../../../utils/vue"; import Popper from "popper.js"; import { getCS, select } from "../../../utils/dom"; import { HTMLElement, SVGElement } from "../../../utils/safe-types"; import { NlyToastTransition } from "../../../utils/nly-toast-transition"; const NAME = "NlyaPopper"; const AttachmentMap = { AUTO: "auto", TOP: "top", RIGHT: "right", BOTTOM: "bottom", LEFT: "left", TOPLEFT: "top", TOPRIGHT: "top", RIGHTTOP: "right", RIGHTBOTTOM: "right", BOTTOMLEFT: "bottom", BOTTOMRIGHT: "bottom", LEFTTOP: "left", LEFTBOTTOM: "left" }; const OffsetMap = { AUTO: 0, TOPLEFT: -1, TOP: 0, TOPRIGHT: +1, RIGHTTOP: -1, RIGHT: 0, RIGHTBOTTOM: +1, BOTTOMLEFT: -1, BOTTOM: 0, BOTTOMRIGHT: +1, LEFTTOP: -1, LEFT: 0, LEFTBOTTOM: +1 }; // @vue/component export const NlyaPopper = Vue.extend({ name: NAME, props: { target: { // Element that the tooltip/popover is positioned relative to type: [HTMLElement, SVGElement], default: null }, placement: { type: String, default: "top" }, fallbackPlacement: { type: [String, Array], default: "flip" }, offset: { type: Number, default: 0 }, boundary: { // 'scrollParent', 'viewport', 'window', or Element type: [String, HTMLElement], default: "scrollParent" }, boundaryPadding: { // Tooltip/popover will try and stay away from // boundary edge by this many pixels type: Number, default: 5 }, arrowPadding: { // The minimum distance (in `px`) from the edge of the // tooltip/popover that the arrow can be positioned type: Number, default: 6 } }, data() { return { // reactive props set by parent noFade: false, // State related data localShow: true, attachment: this.getAttachment(this.placement) }; }, computed: { templateType() /* istanbul ignore next */ { // Overridden by template component return "unknown"; }, popperConfig() { const placement = this.placement; return { placement: this.getAttachment(placement), modifiers: { offset: { offset: this.getOffset(placement) }, flip: { behavior: this.fallbackPlacement }, // `arrow.element` can also be a reference to an HTML Element // maybe we should make this a `$ref` in the templates? arrow: { element: ".arrow" }, preventOverflow: { padding: this.boundaryPadding, boundariesElement: this.boundary } }, onCreate: data => { // Handle flipping arrow classes if (data.originalPlacement !== data.placement) { /* istanbul ignore next: can't test in JSDOM */ this.popperPlacementChange(data); } }, onUpdate: data => { // Handle flipping arrow classes this.popperPlacementChange(data); } }; } }, created() { // Note: We are created on-demand, and should be guaranteed that // DOM is rendered/ready by the time the created hook runs this.$_popper = null; // Ensure we show as we mount this.localShow = true; // Create popper instance before shown this.$on("show", el => { this.popperCreate(el); }); // Self destruct once hidden this.$on("hidden", () => { this.$nextTick(this.$destroy); }); // If parent is destroyed, ensure we are destroyed this.$parent.$once("hook:destroyed", this.$destroy); }, beforeMount() { // Ensure that the attachment position is correct before mounting // as our propsData is added after `new Template({...})` this.attachment = this.getAttachment(this.placement); }, mounted() { // TBD }, updated() { // Update popper if needed // TODO: Should this be a watcher on `this.popperConfig` instead? this.popperUpdate(); }, beforeDestroy() { this.popperDestroy(); }, destroyed() { // Make sure template is removed from DOM const el = this.$el; el && el.parentNode && el.parentNode.removeChild(el); }, methods: { // "Public" method to trigger hide template hide() { this.localShow = false; }, // Private getAttachment(placement) { return AttachmentMap[String(placement).toUpperCase()] || "auto"; }, getOffset(placement) { if (!this.offset) { // Could set a ref for the arrow element const arrow = this.$refs.arrow || select(".arrow", this.$el); const arrowOffset = (parseFloat(getCS(arrow).width) || 0) + (parseFloat(this.arrowPadding) || 0); switch (OffsetMap[String(placement).toUpperCase()] || 0) { case +1: /* istanbul ignore next: can't test in JSDOM */ return `+50%p - ${arrowOffset}px`; case -1: /* istanbul ignore next: can't test in JSDOM */ return `-50%p + ${arrowOffset}px`; default: return 0; } } /* istanbul ignore next */ return this.offset; }, popperCreate(el) { this.popperDestroy(); // We use `el` rather than `this.$el` just in case the original // mountpoint root element type was changed by the template this.$_popper = new Popper(this.target, el, this.popperConfig); }, popperDestroy() { this.$_popper && this.$_popper.destroy(); this.$_popper = null; }, popperUpdate() { this.$_popper && this.$_popper.scheduleUpdate(); }, popperPlacementChange(data) { // Callback used by popper to adjust the arrow placement this.attachment = this.getAttachment(data.placement); }, renderTemplate(h) /* istanbul ignore next */ { // Will be overridden by templates return h("div"); } }, render(h) { // Note: `show` and 'fade' classes are only appled during transition return h( NlyToastTransition, { // Transitions as soon as mounted props: { appear: true, noFade: this.noFade }, on: { // Events used by parent component/instance beforeEnter: el => this.$emit("show", el), afterEnter: el => this.$emit("shown", el), beforeLeave: el => this.$emit("hide", el), afterLeave: el => this.$emit("hidden", el) } }, [this.localShow ? this.renderTemplate(h) : h()] ); } });