nly-adminlte-vue
Version:
nly adminlte3 components
328 lines (325 loc) • 9.83 kB
JavaScript
import Vue from "../../utils/vue";
import getScopId from "../../utils/get-scope-id";
import { isArray, arrayIncludes } from "../../utils/array";
import { getComponentConfig } from "../../utils/config";
import { isString, isUndefinedOrNull } from "../../utils/inspect";
import { HTMLElement, SVGElement } from "../../utils/safe-types";
import { NlyaTooltip } from "./plugin/nlya-tooltip";
const NAME = "NlyTooltip";
// @vue/component
export const NlyTooltip = Vue.extend({
name: NAME,
props: {
title: {
type: String
// default: undefined
},
// Added in by BPopover
// content: {
// type: String,
// default: undefined
// },
target: {
type: [String, HTMLElement, SVGElement, Function, Object],
required: true
},
triggers: {
type: [String, Array],
default: "hover focus"
},
placement: {
type: String,
default: "top"
},
fallbackPlacement: {
type: [String, Array],
default: "flip",
validator(value) {
return (
(isArray(value) && value.every(v => isString(v))) ||
arrayIncludes(["flip", "clockwise", "counterclockwise"], value)
);
}
},
variant: {
type: String,
default: () => getComponentConfig(NAME, "variant")
},
customClass: {
type: String,
default: () => getComponentConfig(NAME, "customClass")
},
delay: {
type: [Number, Object, String],
default: () => getComponentConfig(NAME, "delay")
},
boundary: {
// String: scrollParent, window, or viewport
// Element: element reference
// Object: Vue component
type: [String, HTMLElement, Object],
default: () => getComponentConfig(NAME, "boundary")
},
boundaryPadding: {
type: [Number, String],
default: () => getComponentConfig(NAME, "boundaryPadding")
},
offset: {
type: [Number, String],
default: 0
},
noFade: {
type: Boolean,
default: false
},
container: {
// String: HTML ID of container, if null body is used (default)
// HTMLElement: element reference reference
// Object: Vue Component
type: [String, HTMLElement, Object]
// default: undefined
},
show: {
type: Boolean,
default: false
},
noninteractive: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
id: {
// ID to use for tooltip element
// If not provided on will automatically be generated
type: String,
default: null
}
},
data() {
return {
localShow: this.show,
localTitle: "",
localContent: ""
};
},
computed: {
templateData() {
// Data that will be passed to the template and popper
return {
// We use massaged versions of the title and content props/slots
title: this.localTitle,
content: this.localContent,
// Pass these props as is
target: this.target,
triggers: this.triggers,
placement: this.placement,
fallbackPlacement: this.fallbackPlacement,
variant: this.variant,
customClass: this.customClass,
container: this.container,
boundary: this.boundary,
boundaryPadding: this.boundaryPadding,
delay: this.delay,
offset: this.offset,
noFade: this.noFade,
interactive: !this.noninteractive,
disabled: this.disabled,
id: this.id
};
},
templateTitleContent() {
// Used to watch for changes to the title and content props
return {
title: this.title,
content: this.content
};
}
},
watch: {
show(show, oldVal) {
if (show !== oldVal && show !== this.localShow && this.$_nly_toolpop) {
if (show) {
this.$_nly_toolpop.show();
} else {
// We use `forceHide()` to override any active triggers
this.$_nly_toolpop.forceHide();
}
}
},
disabled(newVal) {
if (newVal) {
this.doDisable();
} else {
this.doEnable();
}
},
localShow(newVal) {
// TODO: May need to be done in a `$nextTick()`
this.$emit("update:show", newVal);
},
templateData() {
this.$nextTick(() => {
if (this.$_nly_toolpop) {
this.$_nly_toolpop.updateData(this.templateData);
}
});
},
// Watchers for title/content props (prop changes do not trigger the `updated()` hook)
templateTitleContent() {
this.$nextTick(this.updateContent);
}
},
created() {
// Non reactive properties
this.$_nly_toolpop = null;
},
updated() {
// Update the `propData` object
// Done in a `$nextTick()` to ensure slot(s) have updated
this.$nextTick(this.updateContent);
},
beforeDestroy() {
// Shutdown our local event listeners
this.$off("open", this.doOpen);
this.$off("close", this.doClose);
this.$off("disable", this.doDisable);
this.$off("enable", this.doEnable);
// Destroy the tip instance
this.$_nly_toolpop && this.$_nly_toolpop.$destroy();
this.$_nly_toolpop = null;
},
mounted() {
// Instantiate a new nlyTooltip instance
// Done in a `$nextTick()` to ensure DOM has completed rendering
// so that target can be found
this.$nextTick(() => {
// Load the on demand child instance
const Component = this.getComponent();
// Ensure we have initial content
this.updateContent();
// Pass down the scoped style attribute if available
const scopeId = getScopId(this) || getScopId(this.$parent);
// Create the instance
const $toolpop = (this.$_nly_toolpop = new Component({
parent: this,
// Pass down the scoped style ID
_scopeId: scopeId || undefined
}));
// Set the initial data
$toolpop.updateData(this.templateData);
// Set listeners
$toolpop.$on("show", this.onShow);
$toolpop.$on("shown", this.onShown);
$toolpop.$on("hide", this.onHide);
$toolpop.$on("hidden", this.onHidden);
$toolpop.$on("disabled", this.onDisabled);
$toolpop.$on("enabled", this.onEnabled);
// Initially disabled?
if (this.disabled) {
// Initially disabled
this.doDisable();
}
// Listen to open signals from others
this.$on("open", this.doOpen);
// Listen to close signals from others
this.$on("close", this.doClose);
// Listen to disable signals from others
this.$on("disable", this.doDisable);
// Listen to enable signals from others
this.$on("enable", this.doEnable);
// Initially show tooltip?
if (this.localShow) {
this.$_nly_toolpop && this.$_nly_toolpop.show();
}
});
},
methods: {
getComponent() {
// Overridden by BPopover
return NlyaTooltip;
},
updateContent() {
// Overridden by BPopover
// Tooltip: Default slot is `title`
// Popover: Default slot is `content`, `title` slot is title
// We pass a scoped slot function reference by default (Vue v2.6x)
// And pass the title prop as a fallback
this.setTitle(this.$scopedSlots.default || this.title);
},
// Helper methods for `updateContent()`
setTitle(val) {
val = isUndefinedOrNull(val) ? "" : val;
// We only update the value if it has changed
if (this.localTitle !== val) {
this.localTitle = val;
}
},
setContent(val) {
val = isUndefinedOrNull(val) ? "" : val;
// We only update the value if it has changed
if (this.localContent !== val) {
this.localContent = val;
}
},
// --- Template event handlers ---
onShow(nlyaEvt) {
// Placeholder
this.$emit("show", nlyaEvt);
if (nlyaEvt) {
this.localShow = !nlyaEvt.defaultPrevented;
}
},
onShown(nlyaEvt) {
// Tip is now showing
this.localShow = true;
this.$emit("shown", nlyaEvt);
},
onHide(nlyaEvt) {
this.$emit("hide", nlyaEvt);
},
onHidden(nlyaEvt) {
// Tip is no longer showing
this.$emit("hidden", nlyaEvt);
this.localShow = false;
},
onDisabled(nlyaEvt) {
// Prevent possible endless loop if user mistakenly
// fires `disabled` instead of `disable`
if (nlyaEvt && nlyaEvt.type === "disabled") {
this.$emit("update:disabled", true);
this.$emit("disabled", nlyaEvt);
}
},
onEnabled(nlyaEvt) {
// Prevent possible endless loop if user mistakenly
// fires `enabled` instead of `enable`
if (nlyaEvt && nlyaEvt.type === "enabled") {
this.$emit("update:disabled", false);
this.$emit("enabled", nlyaEvt);
}
},
// --- Local event listeners ---
doOpen() {
!this.localShow && this.$_nly_toolpop && this.$_nly_toolpop.show();
},
doClose() {
this.localShow && this.$_nly_toolpop && this.$_nly_toolpop.hide();
},
doDisable() {
this.$_nly_toolpop && this.$_nly_toolpop.disable();
},
doEnable() {
this.$_nly_toolpop && this.$_nly_toolpop.enable();
}
},
render(h) {
// Always renders a comment node
// TODO:
// Future: Possibly render a target slot (single root element)
// which we can apply the listeners to (pass `this.$el` to nlyTooltip)
return h();
}
});