fix(tooltip): show tooltips in top layer when inside modal dialog
Tooltips on relative dates (and other content) were invisible when a task was opened in the modal. The modal uses <dialog> opened via showModal(), which places it in the browser's top layer. floating-vue teleports tooltips to <body> by default, so they were rendered *below* the dialog backdrop and hidden behind it. Wrap the v-tooltip directive to detect the nearest <dialog> ancestor of the target and use it as the tooltip's container, keeping the tooltip in the same top-layer context as the modal it belongs to. Tooltips outside any dialog still teleport to <body> as before.
This commit is contained in:
parent
52f3dd6806
commit
941f6bb1be
|
|
@ -0,0 +1,49 @@
|
|||
import type {Directive, DirectiveBinding} from 'vue'
|
||||
import {vTooltip} from 'floating-vue'
|
||||
|
||||
// When a tooltip target lives inside a <dialog> opened via showModal(), the
|
||||
// dialog is in the browser's top layer. floating-vue teleports tooltips to
|
||||
// <body> by default, so they render *below* the dialog's ::backdrop and are
|
||||
// not visible. Teleporting them into the dialog keeps them in the top layer.
|
||||
function buildBinding(el: Element, binding: DirectiveBinding): DirectiveBinding {
|
||||
const dialog = el.closest('dialog')
|
||||
if (!dialog) {
|
||||
return binding
|
||||
}
|
||||
|
||||
const value = binding.value
|
||||
let normalized: Record<string, unknown>
|
||||
if (typeof value === 'string') {
|
||||
normalized = {content: value}
|
||||
} else if (value && typeof value === 'object') {
|
||||
normalized = {...value as Record<string, unknown>}
|
||||
} else {
|
||||
return binding
|
||||
}
|
||||
|
||||
if (normalized.container === undefined) {
|
||||
normalized.container = dialog
|
||||
}
|
||||
|
||||
return {...binding, value: normalized}
|
||||
}
|
||||
|
||||
// Bind via `mounted` rather than `beforeMount` so the element is already
|
||||
// attached to the DOM — otherwise `el.closest('dialog')` cannot find the
|
||||
// dialog ancestor.
|
||||
const tooltip: Directive<Element, unknown> = {
|
||||
mounted(el, binding) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
;(vTooltip as any).beforeMount(el, buildBinding(el, binding))
|
||||
},
|
||||
updated(el, binding) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
;(vTooltip as any).updated(el, buildBinding(el, binding))
|
||||
},
|
||||
beforeUnmount(el) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
;(vTooltip as any).beforeUnmount(el)
|
||||
},
|
||||
}
|
||||
|
||||
export default tooltip
|
||||
|
|
@ -38,7 +38,7 @@ if (window.API_URL.endsWith('/')) {
|
|||
|
||||
// directives
|
||||
import focus from '@/directives/focus'
|
||||
import {vTooltip} from 'floating-vue'
|
||||
import tooltip from '@/directives/tooltip'
|
||||
import 'floating-vue/dist/style.css'
|
||||
import shortcut from '@/directives/shortcut'
|
||||
import testid from '@/directives/testid'
|
||||
|
|
@ -66,7 +66,7 @@ setLanguage(browserLanguage).then(() => {
|
|||
app.use(Notifications)
|
||||
|
||||
app.directive('focus', focus)
|
||||
app.directive('tooltip', vTooltip)
|
||||
app.directive('tooltip', tooltip)
|
||||
app.directive('shortcut', shortcut)
|
||||
app.directive('cy', testid)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue