diff options
Diffstat (limited to 'app/assets/javascripts/tooltips')
-rw-r--r-- | app/assets/javascripts/tooltips/components/tooltips.vue | 70 | ||||
-rw-r--r-- | app/assets/javascripts/tooltips/index.js | 58 |
2 files changed, 128 insertions, 0 deletions
diff --git a/app/assets/javascripts/tooltips/components/tooltips.vue b/app/assets/javascripts/tooltips/components/tooltips.vue new file mode 100644 index 00000000000..4edfddd0f85 --- /dev/null +++ b/app/assets/javascripts/tooltips/components/tooltips.vue @@ -0,0 +1,70 @@ +<script> +import { GlTooltip, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui'; +import { uniqueId } from 'lodash'; + +const getTooltipTitle = element => { + return element.getAttribute('title') || element.dataset.title; +}; + +const newTooltip = (element, config = {}) => { + const { placement, container, boundary, html, triggers } = element.dataset; + const title = getTooltipTitle(element); + + return { + id: uniqueId('gl-tooltip'), + target: element, + title, + html, + placement, + container, + boundary, + triggers, + disabled: !title, + ...config, + }; +}; + +export default { + components: { + GlTooltip, + }, + directives: { + SafeHtml, + }, + data() { + return { + tooltips: [], + }; + }, + methods: { + addTooltips(elements, config) { + const newTooltips = elements + .filter(element => !this.tooltipExists(element)) + .map(element => newTooltip(element, config)); + + this.tooltips.push(...newTooltips); + }, + tooltipExists(element) { + return this.tooltips.some(tooltip => tooltip.target === element); + }, + }, +}; +</script> +<template> + <div> + <gl-tooltip + v-for="(tooltip, index) in tooltips" + :id="tooltip.id" + :key="index" + :target="tooltip.target" + :triggers="tooltip.triggers" + :placement="tooltip.placement" + :container="tooltip.container" + :boundary="tooltip.boundary" + :disabled="tooltip.disabled" + > + <span v-if="tooltip.html" v-safe-html="tooltip.title"></span> + <span v-else>{{ tooltip.title }}</span> + </gl-tooltip> + </div> +</template> diff --git a/app/assets/javascripts/tooltips/index.js b/app/assets/javascripts/tooltips/index.js new file mode 100644 index 00000000000..985f3350a62 --- /dev/null +++ b/app/assets/javascripts/tooltips/index.js @@ -0,0 +1,58 @@ +import Vue from 'vue'; +import Tooltips from './components/tooltips.vue'; + +let app; + +const EVENTS_MAP = { + hover: 'mouseenter', + click: 'click', + focus: 'focus', +}; + +const DEFAULT_TRIGGER = 'hover focus'; + +const tooltipsApp = () => { + if (!app) { + app = new Vue({ + render(h) { + return h(Tooltips, { + props: { + elements: this.elements, + }, + ref: 'tooltips', + }); + }, + }).$mount(); + } + + return app; +}; + +const isTooltip = (node, selector) => node.matches && node.matches(selector); + +const addTooltips = (elements, config) => { + tooltipsApp().$refs.tooltips.addTooltips(Array.from(elements), config); +}; + +const handleTooltipEvent = (rootTarget, e, selector, config = {}) => { + for (let { target } = e; target && target !== rootTarget; target = target.parentNode) { + if (isTooltip(target, selector)) { + addTooltips([target], { + show: true, + ...config, + }); + break; + } + } +}; + +export const initTooltips = (selector, config = {}) => { + const triggers = config?.triggers || DEFAULT_TRIGGER; + const events = triggers.split(' ').map(trigger => EVENTS_MAP[trigger]); + + events.forEach(event => { + document.addEventListener(event, e => handleTooltipEvent(document, e, selector, config), true); + }); + + return tooltipsApp(); +}; |