UNPKG

@ishitatsuyuki/oruga-next

Version:

UI components for Vue.js and CSS framework agnostic

332 lines (326 loc) 12 kB
'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;