Let library prompt previews escape the video tile.
The info popover was clipped to the card, so the prompt sat inside the thumbnail frame instead of over the page. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,5 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="group relative shrink-0" @wheel.prevent="onIconWheel">
|
<div
|
||||||
|
ref="root"
|
||||||
|
class="relative shrink-0"
|
||||||
|
@mouseenter="open"
|
||||||
|
@mouseleave="scheduleClose"
|
||||||
|
@focusin="open"
|
||||||
|
@focusout="onFocusOut"
|
||||||
|
@wheel.prevent="onIconWheel"
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="relative z-10 flex h-6 w-6 items-center justify-center rounded-full text-zinc-500 hover:text-amber-200"
|
class="relative z-10 flex h-6 w-6 items-center justify-center rounded-full text-zinc-500 hover:text-amber-200"
|
||||||
@@ -12,14 +20,19 @@
|
|||||||
<path d="M12 16v-4M12 8h.01" stroke-linecap="round" />
|
<path d="M12 16v-4M12 8h.01" stroke-linecap="round" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<div class="invisible absolute right-0 top-full z-20 -mt-1 pt-2 group-hover:visible group-focus-within:visible">
|
<Teleport to="body">
|
||||||
<div
|
<div
|
||||||
|
v-if="show"
|
||||||
ref="panel"
|
ref="panel"
|
||||||
class="pointer-events-auto max-h-48 w-64 max-w-[calc(100vw-2rem)] overflow-y-auto overscroll-contain whitespace-pre-wrap break-words rounded-xl border border-white/10 bg-zinc-950 p-3 text-xs leading-relaxed text-zinc-300 shadow-2xl"
|
class="fixed z-[80] max-h-[min(20rem,50vh)] w-[min(28rem,calc(100vw-1.5rem))] overflow-y-auto overscroll-contain whitespace-pre-wrap break-words rounded-xl border border-white/10 bg-zinc-950 p-3 text-xs leading-relaxed text-zinc-300 shadow-2xl"
|
||||||
|
:style="panelStyle"
|
||||||
|
@mouseenter="open"
|
||||||
|
@mouseleave="scheduleClose"
|
||||||
|
@click.stop
|
||||||
>
|
>
|
||||||
{{ text }}
|
{{ text }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Teleport>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -33,11 +46,75 @@ const emit = defineEmits<{
|
|||||||
copy: [text: string]
|
copy: [text: string]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const root = ref<HTMLElement | null>(null)
|
||||||
const panel = ref<HTMLElement | null>(null)
|
const panel = ref<HTMLElement | null>(null)
|
||||||
|
const show = ref(false)
|
||||||
|
const panelStyle = ref<Record<string, string>>({})
|
||||||
|
let closeTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
|
|
||||||
|
function place() {
|
||||||
|
const el = root.value
|
||||||
|
if (!el) return
|
||||||
|
const rect = el.getBoundingClientRect()
|
||||||
|
const width = Math.min(448, window.innerWidth - 24)
|
||||||
|
const maxHeight = Math.min(320, window.innerHeight * 0.5)
|
||||||
|
let left = rect.right - width
|
||||||
|
left = Math.min(Math.max(12, left), window.innerWidth - width - 12)
|
||||||
|
const below = rect.bottom + 8
|
||||||
|
const above = rect.top - 8 - maxHeight
|
||||||
|
const top = below + maxHeight <= window.innerHeight - 12 ? below : Math.max(12, above)
|
||||||
|
panelStyle.value = {
|
||||||
|
top: `${Math.round(top)}px`,
|
||||||
|
left: `${Math.round(left)}px`,
|
||||||
|
width: `${Math.round(width)}px`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function open() {
|
||||||
|
if (closeTimer) {
|
||||||
|
clearTimeout(closeTimer)
|
||||||
|
closeTimer = null
|
||||||
|
}
|
||||||
|
show.value = true
|
||||||
|
nextTick(place)
|
||||||
|
}
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
show.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleClose() {
|
||||||
|
if (closeTimer) clearTimeout(closeTimer)
|
||||||
|
closeTimer = setTimeout(close, 120)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onFocusOut(event: FocusEvent) {
|
||||||
|
const next = event.relatedTarget as Node | null
|
||||||
|
if (root.value?.contains(next) || panel.value?.contains(next)) return
|
||||||
|
scheduleClose()
|
||||||
|
}
|
||||||
|
|
||||||
function onIconWheel(event: WheelEvent) {
|
function onIconWheel(event: WheelEvent) {
|
||||||
const el = panel.value
|
const el = panel.value
|
||||||
if (!el) return
|
if (!el) return
|
||||||
el.scrollTop += event.deltaY
|
el.scrollTop += event.deltaY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch(show, (value) => {
|
||||||
|
if (typeof window === 'undefined') return
|
||||||
|
if (value) {
|
||||||
|
window.addEventListener('resize', place)
|
||||||
|
window.addEventListener('scroll', place, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
window.removeEventListener('resize', place)
|
||||||
|
window.removeEventListener('scroll', place, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (closeTimer) clearTimeout(closeTimer)
|
||||||
|
if (typeof window === 'undefined') return
|
||||||
|
window.removeEventListener('resize', place)
|
||||||
|
window.removeEventListener('scroll', place, true)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user