@ishitatsuyuki/oruga-next
Version:
UI components for Vue.js and CSS framework agnostic
332 lines (326 loc) • 12 kB
JavaScript
'use strict';
var vue = require('vue');
var helpers = require('./helpers.js');
var config = require('./config.js');
var BaseComponentMixin = require('./BaseComponentMixin-a03c02e3.js');
/**
* Display a brief helper text to your user
* @displayName Tooltip
* @style _tooltip.scss
*/
var script = vue.defineComponent({
name: 'OTooltip',
mixins: [BaseComponentMixin.BaseComponentMixin],
configField: 'tooltip',
emits: ['open', 'close'],
props: {
/** Whether tooltip is active or not, use v-model:active to make it two-way binding */
active: {
type: Boolean,
default: true
},
/** Tooltip text */
label: String,
/** Tooltip delay before it appears (number in ms) */
delay: Number,
/**
* Tooltip position in relation to the element
* @values top, bottom, left, right
*/
position: {
type: String,
default: () => { return helpers.getValueByPath(config.getOptions(), 'tooltip.position', 'top'); },
validator: (value) => {
return [
'top',
'bottom',
'left',
'right'
].indexOf(value) > -1;
}
},
/**
* Tooltip trigger events
* @values hover, click, focus, contextmenu
*/
triggers: {
type: Array,
default: () => { return helpers.getValueByPath(config.getOptions(), 'tooltip.triggers', ['hover']); }
},
/** Tooltip will be always active */
always: Boolean,
/** Tooltip will have an animation */
animated: {
type: Boolean,
default: true
},
/** Tooltip default animation */
animation: {
type: String,
default: () => { return helpers.getValueByPath(config.getOptions(), 'tooltip.animation', 'fade'); }
},
/**
* Tooltip auto close options
* @values true, false, 'inside', 'outside'
*/
autoClose: {
type: [Array, Boolean],
default: true
},
/** Tooltip will be multilined */
multiline: Boolean,
/** Append tooltip content to body */
appendToBody: Boolean,
/**
* Color of the tooltip
* @values primary, info, success, warning, danger, and any other custom color
*/
variant: [String, Function, Array],
rootClass: [String, Function, Array],
contentClass: [String, Function, Array],
orderClass: [String, Function, Array],
triggerClass: [String, Function, Array],
multilineClass: [String, Function, Array],
alwaysClass: [String, Function, Array],
variantClass: [String, Function, Array],
arrowClass: [String, Function, Array],
arrowOrderClass: [String, Function, Array]
},
data() {
return {
isActive: false,
triggerStyle: {},
bodyEl: undefined // Used to append to body
};
},
computed: {
rootClasses() {
return [
this.computedClass('rootClass', 'o-tip')
];
},
triggerClasses() {
return [
this.computedClass('triggerClass', 'o-tip__trigger'),
];
},
arrowClasses() {
return [
this.computedClass('arrowClass', 'o-tip__arrow'),
{ [this.computedClass('arrowOrderClass', 'o-tip__arrow--', this.position)]: this.position },
{ [this.computedClass('variantArrowClass', 'o-tip__arrow--', this.variant)]: this.variant },
];
},
contentClasses() {
return [
this.computedClass('contentClass', 'o-tip__content'),
{ [this.computedClass('orderClass', 'o-tip__content--', this.position)]: this.position },
{ [this.computedClass('variantClass', 'o-tip__content--', this.variant)]: this.variant },
{ [this.computedClass('multilineClass', 'o-tip__content--multiline')]: this.multiline },
{ [this.computedClass('alwaysClass', 'o-tip__content--always')]: this.always }
];
},
newAnimation() {
return this.animated ? this.animation : undefined;
}
},
watch: {
isActive(value) {
this.$emit(this.isActive ? 'open' : 'close');
if (value && this.appendToBody) {
this.updateAppendToBody();
}
}
},
methods: {
updateAppendToBody() {
const tooltip = this.$refs.tooltip;
const trigger = this.$refs.trigger;
if (tooltip && trigger) {
// update wrapper tooltip
const tooltipEl = this.$data.bodyEl.children[0];
tooltipEl.classList.forEach((item) => tooltipEl.classList.remove(...item.split(' ')));
if (this.$vnode && this.$vnode.data && this.$vnode.data.staticClass) {
tooltipEl.classList.add(this.$vnode.data.staticClass);
}
this.rootClasses.forEach((item) => {
if (typeof item === 'object') {
Object.keys(item).filter(key => key && item[key]).forEach(key => tooltipEl.classList.add(key));
}
else {
tooltipEl.classList.add(...item.split(' '));
}
});
tooltipEl.style.width = `${trigger.clientWidth}px`;
tooltipEl.style.height = `${trigger.clientHeight}px`;
const rect = trigger.getBoundingClientRect();
const top = rect.top + window.scrollY;
const left = rect.left + window.scrollX;
const wrapper = this.$data.bodyEl;
wrapper.style.position = 'absolute';
wrapper.style.top = `${top}px`;
wrapper.style.left = `${left}px`;
wrapper.style.zIndex = this.isActive || this.always ? '99' : '-1';
this.triggerStyle = { zIndex: this.isActive || this.always ? '100' : undefined };
}
},
onClick() {
if (this.triggers.indexOf('click') < 0)
return;
// if not active, toggle after clickOutside event
// this fixes toggling programmatic
this.$nextTick(() => {
setTimeout(() => this.open());
});
},
onHover() {
if (this.triggers.indexOf('hover') < 0)
return;
this.open();
},
onFocus() {
if (this.triggers.indexOf('focus') < 0)
return;
this.open();
},
onContextMenu(event) {
if (this.triggers.indexOf('contextmenu') < 0)
return;
event.preventDefault();
this.open();
},
open() {
if (this.delay) {
this.timer = setTimeout(() => {
this.isActive = true;
this.timer = null;
}, this.delay);
}
else {
this.isActive = true;
}
},
close() {
if (typeof this.autoClose === 'boolean') {
this.isActive = !this.autoClose;
}
if (this.autoClose && this.timer)
clearTimeout(this.timer);
},
/**
* Close tooltip if clicked outside.
*/
clickedOutside(event) {
if (this.isActive) {
if (Array.isArray(this.autoClose)) {
if (this.autoClose.indexOf('outside') >= 0) {
if (!this.isInWhiteList(event.target))
this.isActive = false;
}
if (this.autoClose.indexOf('inside') >= 0) {
if (this.isInWhiteList(event.target))
this.isActive = false;
}
}
}
},
/**
* Keypress event that is bound to the document
*/
keyPress({ key }) {
if (this.isActive && (key === 'Escape' || key === 'Esc')) {
if (Array.isArray(this.autoClose)) {
if (this.autoClose.indexOf('escape') >= 0)
this.isActive = false;
}
}
},
/**
* White-listed items to not close when clicked.
*/
isInWhiteList(el) {
if (el === this.$refs.content)
return true;
// All chidren from content
if (this.$refs.content !== undefined) {
const children = this.$refs.content.querySelectorAll('*');
for (const child of children) {
if (el === child) {
return true;
}
}
}
return false;
}
},
mounted() {
if (this.appendToBody) {
this.$data.bodyEl = helpers.createAbsoluteElement(this.$refs.content);
this.updateAppendToBody();
}
},
created() {
if (typeof window !== 'undefined') {
document.addEventListener('click', this.clickedOutside);
document.addEventListener('keyup', this.keyPress);
}
},
beforeUnmount() {
if (typeof window !== 'undefined') {
document.removeEventListener('click', this.clickedOutside);
document.removeEventListener('keyup', this.keyPress);
}
if (this.appendToBody) {
helpers.removeElement(this.$data.bodyEl);
}
}
});
function render(_ctx, _cache, $props, $setup, $data, $options) {
return vue.openBlock(), vue.createBlock("div", {
ref: "tooltip",
class: _ctx.rootClasses
}, [vue.createVNode(vue.Transition, {
name: _ctx.newAnimation
}, {
default: vue.withCtx(() => [vue.withDirectives(vue.createVNode("div", {
ref: "content",
class: _ctx.contentClasses
}, [vue.createVNode("span", {
class: _ctx.arrowClasses
}, null, 2
/* CLASS */
), _ctx.label ? (vue.openBlock(), vue.createBlock(vue.Fragment, {
key: 0
}, [vue.createTextVNode(vue.toDisplayString(_ctx.label), 1
/* TEXT */
)], 64
/* STABLE_FRAGMENT */
)) : _ctx.$slots.default ? vue.renderSlot(_ctx.$slots, "content", {
key: 1
}) : vue.createCommentVNode("v-if", true)], 2
/* CLASS */
), [[vue.vShow, _ctx.active && (_ctx.isActive || _ctx.always)]])]),
_: 1
}, 8
/* PROPS */
, ["name"]), vue.createVNode("div", {
ref: "trigger",
class: _ctx.triggerClasses,
style: _ctx.triggerStyle,
onClick: _cache[1] || (_cache[1] = (...args) => _ctx.onClick(...args)),
onContextmenu: _cache[2] || (_cache[2] = (...args) => _ctx.onContextMenu(...args)),
onMouseenter: _cache[3] || (_cache[3] = (...args) => _ctx.onHover(...args)),
onFocusCapture: _cache[4] || (_cache[4] = (...args) => _ctx.onFocus(...args)),
onBlurCapture: _cache[5] || (_cache[5] = (...args) => _ctx.close(...args)),
onMouseleave: _cache[6] || (_cache[6] = (...args) => _ctx.close(...args))
}, [vue.renderSlot(_ctx.$slots, "default", {
ref: "slot"
})], 38
/* CLASS, STYLE, HYDRATE_EVENTS */
)], 2
/* CLASS */
);
}
script.render = render;
script.__file = "src/components/tooltip/Tooltip.vue";
exports.script = script;