feat: use popper.js v2 vue3 version of v-tooltip (#1038)
Co-authored-by: Dominik Pschenitschni <mail@celement.de> Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/1038 Reviewed-by: konrad <k@knt.li> Co-authored-by: dpschen <dpschen@noreply.kolaente.de> Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
This commit is contained in:
parent
d95fc32d67
commit
91580f97a1
5 changed files with 34 additions and 127 deletions
|
@ -40,6 +40,7 @@
|
||||||
"register-service-worker": "1.7.2",
|
"register-service-worker": "1.7.2",
|
||||||
"snake-case": "3.0.4",
|
"snake-case": "3.0.4",
|
||||||
"ufo": "0.7.9",
|
"ufo": "0.7.9",
|
||||||
|
"v-tooltip": "4.0.0-beta.2",
|
||||||
"vue": "3.2.22",
|
"vue": "3.2.22",
|
||||||
"vue-advanced-cropper": "2.7.0",
|
"vue-advanced-cropper": "2.7.0",
|
||||||
"vue-drag-resize": "2.0.3",
|
"vue-drag-resize": "2.0.3",
|
||||||
|
|
|
@ -1,83 +0,0 @@
|
||||||
const calculateTop = (coords, tooltip) => {
|
|
||||||
// Bottom tooltip use the exact inverse calculation compared to the default.
|
|
||||||
if (tooltip.classList.contains('bottom')) {
|
|
||||||
return coords.top + tooltip.offsetHeight + 5
|
|
||||||
}
|
|
||||||
|
|
||||||
// The top position of the tooltip is the coordinates of the bound element - the height of the tooltip -
|
|
||||||
// 5px spacing for the arrow (which is exactly 5px high)
|
|
||||||
return coords.top - tooltip.offsetHeight - 5
|
|
||||||
}
|
|
||||||
|
|
||||||
const calculateArrowTop = (top, tooltip) => {
|
|
||||||
if (tooltip.classList.contains('bottom')) {
|
|
||||||
return `${top - 5}px` // 5px arrow height
|
|
||||||
}
|
|
||||||
return `${top + tooltip.offsetHeight}px`
|
|
||||||
}
|
|
||||||
|
|
||||||
// This global object holds all created tooltip elements (and their arrows) using the element they were created for as
|
|
||||||
// key. This allows us to find the tooltip elements if the element the tooltip was created for is unbound so that
|
|
||||||
// we can remove the tooltip element.
|
|
||||||
const createdTooltips = {}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
mounted(el, {value, modifiers}) {
|
|
||||||
// First, we create the tooltip and arrow elements
|
|
||||||
const tooltip = document.createElement('div')
|
|
||||||
tooltip.style.position = 'fixed'
|
|
||||||
tooltip.innerText = value
|
|
||||||
tooltip.classList.add('tooltip')
|
|
||||||
const arrow = document.createElement('div')
|
|
||||||
arrow.classList.add('tooltip-arrow')
|
|
||||||
arrow.style.position = 'fixed'
|
|
||||||
|
|
||||||
if (typeof modifiers.bottom !== 'undefined') {
|
|
||||||
tooltip.classList.add('bottom')
|
|
||||||
arrow.classList.add('bottom')
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't append the element until hovering over it because that's the most reliable way to determine
|
|
||||||
// where the parent elemtent is located at the time the user hovers over it.
|
|
||||||
el.addEventListener('mouseover', () => {
|
|
||||||
// Appending the element right away because we can only calculate the height of the element if it is
|
|
||||||
// already in the DOM.
|
|
||||||
document.body.appendChild(tooltip)
|
|
||||||
document.body.appendChild(arrow)
|
|
||||||
|
|
||||||
const coords = el.getBoundingClientRect()
|
|
||||||
const top = calculateTop(coords, tooltip)
|
|
||||||
// The left position of the tooltip is calculated so that the middle point of the tooltip
|
|
||||||
// (where the arrow will be) is the middle of the bound element
|
|
||||||
const left = coords.left - (tooltip.offsetWidth / 2) + (el.offsetWidth / 2)
|
|
||||||
// Now setting all the values
|
|
||||||
tooltip.style.top = `${top}px`
|
|
||||||
tooltip.style.left = `${coords.left}px`
|
|
||||||
tooltip.style.left = `${left}px`
|
|
||||||
|
|
||||||
arrow.style.left = `${left + (tooltip.offsetWidth / 2) - (arrow.offsetWidth / 2)}px`
|
|
||||||
arrow.style.top = calculateArrowTop(top, tooltip)
|
|
||||||
|
|
||||||
// And finally make it visible to the user. This will also trigger a nice fade-in animation through
|
|
||||||
// css transitions
|
|
||||||
tooltip.classList.add('visible')
|
|
||||||
arrow.classList.add('visible')
|
|
||||||
})
|
|
||||||
|
|
||||||
el.addEventListener('mouseout', () => {
|
|
||||||
tooltip.classList.remove('visible')
|
|
||||||
arrow.classList.remove('visible')
|
|
||||||
})
|
|
||||||
|
|
||||||
createdTooltips[el] = {
|
|
||||||
tooltip: tooltip,
|
|
||||||
arrow: arrow,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
unmounted(el) {
|
|
||||||
if (typeof createdTooltips[el] !== 'undefined') {
|
|
||||||
createdTooltips[el].tooltip.remove()
|
|
||||||
createdTooltips[el].arrow.remove()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
11
src/main.ts
11
src/main.ts
|
@ -55,20 +55,21 @@ app.use(Notifications)
|
||||||
|
|
||||||
// directives
|
// directives
|
||||||
import focus from '@/directives/focus'
|
import focus from '@/directives/focus'
|
||||||
import tooltip from '@/directives/tooltip'
|
import { VTooltip } from 'v-tooltip'
|
||||||
|
import 'v-tooltip/dist/v-tooltip.css'
|
||||||
import shortcut from '@/directives/shortcut'
|
import shortcut from '@/directives/shortcut'
|
||||||
import cypress from '@/directives/cypress'
|
import cypress from '@/directives/cypress'
|
||||||
|
|
||||||
app.directive('focus', focus)
|
app.directive('focus', focus)
|
||||||
app.directive('tooltip', tooltip)
|
app.directive('tooltip', VTooltip)
|
||||||
app.directive('shortcut', shortcut)
|
app.directive('shortcut', shortcut)
|
||||||
app.directive('cy', cypress)
|
app.directive('cy', cypress)
|
||||||
|
|
||||||
// global components
|
// global components
|
||||||
import FontAwesomeIcon from './icons'
|
import FontAwesomeIcon from './icons'
|
||||||
import Button from './components/input/button.vue'
|
import Button from '@/components/input/button.vue'
|
||||||
import Modal from './components/modal/modal.vue'
|
import Modal from '@/components/modal/modal.vue'
|
||||||
import Card from './components/misc/card.vue'
|
import Card from '@/components/misc/card.vue'
|
||||||
|
|
||||||
app.component('icon', FontAwesomeIcon)
|
app.component('icon', FontAwesomeIcon)
|
||||||
app.component('x-button', Button)
|
app.component('x-button', Button)
|
||||||
|
|
|
@ -1,42 +1,12 @@
|
||||||
// FIXME: https://www.bram.us/2021/09/13/dont-attach-tooltips-to-document-body/
|
.v-popper--theme-tooltip .v-popper__inner {
|
||||||
|
padding: 5px 10px;
|
||||||
.tooltip {
|
font-size: 0.8rem;
|
||||||
visibility: collapse;
|
|
||||||
z-index: 10000;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
text-align: center;
|
|
||||||
background: var(--dark);
|
background: var(--dark);
|
||||||
color: white;
|
color: var(--white);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding: 5px 10px 5px;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity $transition;
|
|
||||||
|
|
||||||
// If the tooltip is multiline, it would make the height calculations needed to properly position it a lot harder.
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
&-arrow {
|
|
||||||
opacity: 0;
|
|
||||||
content: '';
|
|
||||||
visibility: collapse;
|
|
||||||
position: absolute;
|
|
||||||
transition: opacity $transition;
|
|
||||||
z-index: 10000;
|
|
||||||
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
border-left: 5px solid transparent;
|
|
||||||
border-right: 5px solid transparent;
|
|
||||||
border-top: 5px solid var(--dark);
|
|
||||||
|
|
||||||
&.bottom {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.visible, &-arrow.visible {
|
|
||||||
opacity: 1;
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark .v-popper--theme-tooltip .v-popper__inner {
|
||||||
|
background: var(--white);
|
||||||
|
color: var(--dark);
|
||||||
|
}
|
18
yarn.lock
18
yarn.lock
|
@ -2794,6 +2794,11 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@octokit/openapi-types" "^11.2.0"
|
"@octokit/openapi-types" "^11.2.0"
|
||||||
|
|
||||||
|
"@popperjs/core@^2.10.2":
|
||||||
|
version "2.10.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.10.2.tgz#0798c03351f0dea1a5a4cabddf26a55a7cbee590"
|
||||||
|
integrity sha512-IXf3XA7+XyN7CP9gGh/XB0UxVMlvARGEgGXLubFICsUMGz6Q+DU+i4gGlpOxTjKvXjkJDJC8YdqdKkDj9qZHEQ==
|
||||||
|
|
||||||
"@rollup/plugin-babel@^5.2.0":
|
"@rollup/plugin-babel@^5.2.0":
|
||||||
version "5.3.0"
|
version "5.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz#9cb1c5146ddd6a4968ad96f209c50c62f92f9879"
|
resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz#9cb1c5146ddd6a4968ad96f209c50c62f92f9879"
|
||||||
|
@ -13772,6 +13777,14 @@ uuid@^8.0.0, uuid@^8.3.2:
|
||||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||||
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
||||||
|
|
||||||
|
v-tooltip@4.0.0-beta.2:
|
||||||
|
version "4.0.0-beta.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/v-tooltip/-/v-tooltip-4.0.0-beta.2.tgz#c5cb8d4b703207f588965e32bbec54ada0e43643"
|
||||||
|
integrity sha512-T1cMnFwjRy41YFcoRTNTwXI2TnkdIxwmDVjzUpCxyK5KJAB29FCwuW+usXOaZHJouEI5NOZ/3LnAgxP18xFkww==
|
||||||
|
dependencies:
|
||||||
|
"@popperjs/core" "^2.10.2"
|
||||||
|
vue-resize "^2.0.0-alpha.1"
|
||||||
|
|
||||||
v8-compile-cache@^2.0.3:
|
v8-compile-cache@^2.0.3:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
|
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
|
||||||
|
@ -14044,6 +14057,11 @@ vue-i18n@9.2.0-beta.20:
|
||||||
"@intlify/vue-devtools" "9.2.0-beta.20"
|
"@intlify/vue-devtools" "9.2.0-beta.20"
|
||||||
"@vue/devtools-api" "^6.0.0-beta.13"
|
"@vue/devtools-api" "^6.0.0-beta.13"
|
||||||
|
|
||||||
|
vue-resize@^2.0.0-alpha.1:
|
||||||
|
version "2.0.0-alpha.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/vue-resize/-/vue-resize-2.0.0-alpha.1.tgz#43eeb79e74febe932b9b20c5c57e0ebc14e2df3a"
|
||||||
|
integrity sha512-7+iqOueLU7uc9NrMfrzbG8hwMqchfVfSzpVlCMeJQe4pyibqyoifDNbKTZvwxZKDvGkB+PdFeKvnGZMoEb8esg==
|
||||||
|
|
||||||
vue-router@4.0.12:
|
vue-router@4.0.12:
|
||||||
version "4.0.12"
|
version "4.0.12"
|
||||||
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.0.12.tgz#8dc792cddf5bb1abcc3908f9064136de7e13c460"
|
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.0.12.tgz#8dc792cddf5bb1abcc3908f9064136de7e13c460"
|
||||||
|
|
Loading…
Reference in a new issue