mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 19:51:50 +00:00
Svelte: QoL fixes for hover popovers (#62974)
- Adds a hoverDelay parameter to Popover so each consumer doesn't need to do this manually - Makes hovers only show when the mouse comes to rest - Closes the popover when the window loses focus so we don't miss a mouseleave event and have popovers get stuck open - Adds a delay to popover closing when the mouse leaves one of the target elements so there is room for error when moving the mouse from the trigger to the popover - Adds preloading back to the file tree because the popover now does not create the element until the delay has passed - Makes it so clicking the target closes the popover when hover is enabled.
This commit is contained in:
parent
4e903e5a55
commit
819b2d0e68
@ -8,20 +8,28 @@
|
||||
/**
|
||||
* Show the popover when hovering over the trigger.
|
||||
*/
|
||||
export let showOnHover = false
|
||||
export let showOnHover: boolean = false
|
||||
export let hoverDelay: number = 500
|
||||
export let hoverCloseDelay: number = 150
|
||||
|
||||
let isOpen = false
|
||||
let trigger: HTMLElement | null
|
||||
let target: HTMLElement | undefined
|
||||
let popoverContainer: HTMLElement | null
|
||||
let delayTimer: ReturnType<typeof setTimeout>
|
||||
|
||||
function toggle(open?: boolean): void {
|
||||
isOpen = open === undefined ? !isOpen : open
|
||||
}
|
||||
|
||||
function close(): void {
|
||||
clearTimeout(delayTimer)
|
||||
toggle(false)
|
||||
}
|
||||
|
||||
function handleClickOutside(event: { detail: HTMLElement }): void {
|
||||
if (event.detail !== trigger && !trigger?.contains(event.detail)) {
|
||||
isOpen = false
|
||||
if (!showOnHover && event.detail !== trigger && !trigger?.contains(event.detail)) {
|
||||
toggle(false)
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,19 +41,29 @@
|
||||
trigger = node
|
||||
|
||||
function handleMouseEnterTrigger(): void {
|
||||
isOpen = true
|
||||
clearTimeout(delayTimer)
|
||||
delayTimer = setTimeout(() => toggle(true), hoverDelay)
|
||||
}
|
||||
|
||||
function handleMouseLeaveTrigger(event: MouseEvent): void {
|
||||
// It should be possible to move the mouse from the trigger to the popover without closing it
|
||||
if (event.relatedTarget && !popoverContainer?.contains(event.relatedTarget as Node)) {
|
||||
isOpen = false
|
||||
clearTimeout(delayTimer)
|
||||
delayTimer = setTimeout(() => toggle(false), hoverCloseDelay)
|
||||
}
|
||||
}
|
||||
|
||||
function handleMouseMoveTrigger(): void {
|
||||
clearTimeout(delayTimer)
|
||||
delayTimer = setTimeout(() => toggle(true), hoverDelay)
|
||||
}
|
||||
|
||||
if (showOnHover) {
|
||||
node.addEventListener('mouseenter', handleMouseEnterTrigger)
|
||||
node.addEventListener('mouseleave', handleMouseLeaveTrigger)
|
||||
node.addEventListener('mousemove', handleMouseMoveTrigger)
|
||||
node.addEventListener('click', close)
|
||||
window.addEventListener('blur', close)
|
||||
}
|
||||
|
||||
return {
|
||||
@ -53,6 +71,9 @@
|
||||
trigger = null
|
||||
node.removeEventListener('mouseenter', handleMouseEnterTrigger)
|
||||
node.removeEventListener('mouseleave', handleMouseLeaveTrigger)
|
||||
node.removeEventListener('mousemove', handleMouseMoveTrigger)
|
||||
node.removeEventListener('click', close)
|
||||
window.removeEventListener('blur', close)
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -62,15 +83,23 @@
|
||||
function handleMouseLeavePopover(event: MouseEvent): void {
|
||||
// It should be possible to move the mouse from the popover to the trigger without closing it
|
||||
if (event.relatedTarget && !trigger?.contains(event.relatedTarget as Node)) {
|
||||
toggle(false)
|
||||
delayTimer = setTimeout(() => toggle(false), hoverCloseDelay)
|
||||
}
|
||||
}
|
||||
|
||||
function handleMouseEnterPopover(): void {
|
||||
// When the mouse enters the popover, cancel any pending close events
|
||||
clearTimeout(delayTimer)
|
||||
}
|
||||
|
||||
if (showOnHover) {
|
||||
node.addEventListener('mouseenter', handleMouseEnterPopover)
|
||||
node.addEventListener('mouseleave', handleMouseLeavePopover)
|
||||
}
|
||||
return {
|
||||
destroy() {
|
||||
popoverContainer = null
|
||||
node.removeEventListener('mouseenter', handleMouseEnterPopover)
|
||||
node.removeEventListener('mouseleave', handleMouseLeavePopover)
|
||||
},
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
import { getSidebarFileTreeStateForRepo } from '$lib/repo/stores'
|
||||
import { replaceRevisionInURL } from '$lib/shared'
|
||||
import TreeView, { setTreeContext } from '$lib/TreeView.svelte'
|
||||
import { createForwardStore, delay } from '$lib/utils'
|
||||
import { createForwardStore } from '$lib/utils'
|
||||
import { Alert } from '$lib/wildcard'
|
||||
|
||||
export let repoName: string
|
||||
@ -131,6 +131,8 @@
|
||||
tabindex={-1}
|
||||
data-go-up={isRoot ? true : undefined}
|
||||
use:registerTrigger
|
||||
on:mouseover={/* Preload */ () =>
|
||||
fetchPopoverData({ repoName, revision, filePath: entry.path })}
|
||||
>
|
||||
{#if entry.isDirectory}
|
||||
<Icon svgPath={getDirectoryIconPath(entry, expanded)} inline />
|
||||
@ -140,7 +142,7 @@
|
||||
{isRoot ? '..' : entry.name}
|
||||
</a>
|
||||
<svelte:fragment slot="content">
|
||||
{#await delay(fetchPopoverData({ repoName, revision, filePath: entry.path }), 300) then entry}
|
||||
{#await fetchPopoverData({ repoName, revision, filePath: entry.path }) then entry}
|
||||
<FilePopover {repoName} {revision} {entry} />
|
||||
{/await}
|
||||
</svelte:fragment>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user