Fix flickering tooltip when hovering modified in FileExplorer. Use the new, improved tooltip also in gallery to show full name, modified and size of an item.

This commit is contained in:
Leo Vasanko
2026-01-22 00:49:50 +00:00
parent 5bea8cbb7c
commit cff453802c
3 changed files with 113 additions and 56 deletions
+86
View File
@@ -0,0 +1,86 @@
<template>
<Teleport to="body">
<div v-if="visible" class="cursor-tooltip" :style="tooltipStyle">
<slot></slot>
</div>
</Teleport>
</template>
<script lang="ts">
// Global activation state - shared across all instances
let globalActive = false
let globalDeactivateTimer: ReturnType<typeof setTimeout> | null = null
</script>
<script setup lang="ts">
import { computed, ref } from 'vue'
const props = defineProps<{
text: string
delay?: number
}>()
const visible = ref(false)
const mouseX = ref(0)
const mouseY = ref(0)
let hoverTimer: ReturnType<typeof setTimeout> | null = null
const tooltipStyle = computed(() => ({
left: `${mouseX.value + 12}px`,
top: `${mouseY.value + 12}px`,
}))
const startHover = (e: MouseEvent) => {
mouseX.value = e.clientX
mouseY.value = e.clientY
// Clear any pending deactivation
if (globalDeactivateTimer) {
clearTimeout(globalDeactivateTimer)
globalDeactivateTimer = null
}
const delay = globalActive ? 0 : (props.delay ?? 800)
hoverTimer = setTimeout(() => {
visible.value = true
globalActive = true
}, delay)
}
const updatePosition = (e: MouseEvent) => {
mouseX.value = e.clientX
mouseY.value = e.clientY
}
const endHover = () => {
if (hoverTimer) {
clearTimeout(hoverTimer)
hoverTimer = null
}
visible.value = false
// Deactivate global state after a short delay if no new tooltip started
if (globalDeactivateTimer) clearTimeout(globalDeactivateTimer)
globalDeactivateTimer = setTimeout(() => {
globalActive = false
}, 500)
}
defineExpose({
startHover,
updatePosition,
endHover,
})
</script>
<style scoped>
.cursor-tooltip {
position: fixed;
z-index: 10000;
padding: .5rem 1rem;
border-radius: 3rem 0 3rem 0;
box-shadow: 0 0 1rem var(--accent-color);
background-color: var(--accent-color);
color: var(--primary-color);
white-space: nowrap;
pointer-events: none;
font-size: 1rem;
}
</style>
+7 -54
View File
@@ -2,15 +2,11 @@
<td class="modified right">
<time
:datetime=datetime
@mouseenter="startHover"
@mousemove="updatePosition"
@mouseleave="endHover"
@mouseenter="tooltip?.startHover"
@mousemove="tooltip?.updatePosition"
@mouseleave="tooltip?.endHover"
>{{ modified }}</time>
<Teleport to="body">
<div v-if="showTooltip" class="cursor-tooltip" :style="tooltipStyle">
{{ tooltipText }}
</div>
</Teleport>
<CursorTooltip ref="tooltip" :text="tooltipText">{{ tooltipText }}</CursorTooltip>
</td>
</template>
@@ -18,12 +14,15 @@
import { Doc } from '@/repositories/Document'
import { formatUnixDate } from '@/utils'
import { computed, ref } from 'vue'
import CursorTooltip from './CursorTooltip.vue'
const props = defineProps<{
doc: Doc
now: number
}>()
const tooltip = ref<InstanceType<typeof CursorTooltip> | null>(null)
// Reference props.now to trigger reactivity when time updates
const modified = computed(() => {
props.now // trigger reactivity
@@ -37,50 +36,4 @@ const datetime = computed(() =>
const tooltipText = computed(() =>
datetime.value.replace('T', ' ').replace('Z', ' UTC')
)
const showTooltip = ref(false)
const mouseX = ref(0)
const mouseY = ref(0)
let hoverTimer: ReturnType<typeof setTimeout> | null = null
const tooltipStyle = computed(() => ({
left: `${mouseX.value + 12}px`,
top: `${mouseY.value + 12}px`,
}))
const startHover = (e: MouseEvent) => {
mouseX.value = e.clientX
mouseY.value = e.clientY
hoverTimer = setTimeout(() => {
showTooltip.value = true
}, 500)
}
const updatePosition = (e: MouseEvent) => {
mouseX.value = e.clientX
mouseY.value = e.clientY
}
const endHover = () => {
if (hoverTimer) {
clearTimeout(hoverTimer)
hoverTimer = null
}
showTooltip.value = false
}
</script>
<style scoped>
.cursor-tooltip {
position: fixed;
z-index: 10000;
padding: .5rem 1rem;
border-radius: 3rem 0 3rem 0;
box-shadow: 0 0 1rem var(--accent-color);
background-color: var(--accent-color);
color: var(--primary-color);
white-space: pre;
pointer-events: none;
font-size: 1rem;
}
</style>
+20 -2
View File
@@ -4,6 +4,9 @@
@contextmenu.stop
@focus.stop="store.cursor = doc.key"
@click=onclick
@mouseenter="tooltip?.startHover"
@mousemove="tooltip?.updatePosition"
@mouseleave="tooltip?.endHover"
>
<figure>
<slot></slot>
@@ -15,19 +18,24 @@
</template>
<template v-else>
<SelectBox :doc=doc @click="store.cursor = doc.key"/>
<span :title="doc.name + '\n' + doc.modified + '\n' + doc.sizedisp">{{ doc.name }}</span>
<span>{{ doc.name }}</span>
<div class=namespacer></div>
</template>
</figcaption>
</figure>
<CursorTooltip ref="tooltip" :text="tooltipText">
<div class="tooltip-name">{{ doc.name }}</div>
<div class="tooltip-details">{{ doc.modified }} {{ doc.sizedisp }}</div>
</CursorTooltip>
</a>
</template>
<script setup lang=ts>
import { ref } from 'vue'
import { ref, computed } from 'vue'
import { useMainStore } from '@/stores/main'
import { Doc } from '@/repositories/Document'
import MediaPreview from '@/components/MediaPreview.vue'
import CursorTooltip from './CursorTooltip.vue'
const store = useMainStore()
type EditingProp = {
@@ -40,6 +48,9 @@ const props = defineProps<{
editing?: EditingProp,
}>()
const m = ref<typeof MediaPreview | null>(null)
const tooltip = ref<InstanceType<typeof CursorTooltip> | null>(null)
const tooltipText = computed(() => props.doc.key)
const onclick = (ev: Event) => {
if (m.value!.play()) ev.preventDefault()
@@ -48,6 +59,13 @@ const onclick = (ev: Event) => {
</script>
<style scoped>
.tooltip-name {
font-weight: 600;
text-align: center;
}
.tooltip-details {
text-align: center;
}
figure {
max-height: 15em;
position: relative;