nly-adminlte-vue
Version:
nly adminlte3 components
232 lines (227 loc) • 6.74 kB
JavaScript
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()]
);
}
});