From 519a5c8bc70b44a7417e83c26c7b9c58b2c4fb9c Mon Sep 17 00:00:00 2001 From: Diego Imbert <70353967+diegoimbert@users.noreply.github.com> Date: Mon, 7 Sep 2026 18:57:53 +0200 Subject: [PATCH] fix(frontend): stop hover flicker on asset nodes shared with an overflow popover (#10996) Claude-Session: https://claude.ai/code/session_01HNugALVxFkkAeM5mce4CFQ Co-authored-by: Claude Fable 5.1 --- .../nodes/AssetsOverflowedNode.svelte | 50 +++++++++++-------- .../components/meltComponents/Popover.svelte | 4 ++ 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/frontend/src/lib/components/graph/renderers/nodes/AssetsOverflowedNode.svelte b/frontend/src/lib/components/graph/renderers/nodes/AssetsOverflowedNode.svelte index 8183be16d6..db5abfde20 100644 --- a/frontend/src/lib/components/graph/renderers/nodes/AssetsOverflowedNode.svelte +++ b/frontend/src/lib/components/graph/renderers/nodes/AssetsOverflowedNode.svelte @@ -7,7 +7,7 @@ import Popover from '$lib/components/meltComponents/Popover.svelte' import AssetNode from './AssetNode.svelte' import type { FlowGraphAssetContext } from '$lib/components/flows/types' - import { getContext } from 'svelte' + import { getContext, untrack } from 'svelte' import { assetEq } from '$lib/components/assets/lib' import { getNodeColorClasses } from '../../util' @@ -24,15 +24,17 @@ data.overflowedAssets.some((asset) => assetEq(flowGraphAssetsCtx?.val.selectedAsset, asset)) ) - let wasOpenedBecauseOfExternalSelected = false + // Open while a sibling asset node is hovered and one of the hidden assets is the same asset. + let openedByHover = $state(false) $effect(() => { - if (includesSelected && !isOpen) { - isOpen = true - wasOpenedBecauseOfExternalSelected = true - } - if (wasOpenedBecauseOfExternalSelected && !includesSelected) { + if (includesSelected) { + if (!untrack(() => isOpen)) { + isOpen = true + openedByHover = true + } + } else if (untrack(() => openedByHover)) { isOpen = false - wasOpenedBecauseOfExternalSelected = false + openedByHover = false } }) const colors = $derived(getNodeColorClasses(undefined, includesSelected)) @@ -41,9 +43,17 @@ {#snippet children({ darkMode })} + {#snippet trigger()} - - +{data.overflowedAssets.length} - - {/snippet} + +{data.overflowedAssets.length} + {/snippet} {#snippet content()} - -
    - {#each data.overflowedAssets as asset} -
  • - -
  • - {/each} -
- - {/snippet} +
    + {#each data.overflowedAssets as asset} +
  • + +
  • + {/each} +
+ {/snippet}
{/snippet}
diff --git a/frontend/src/lib/components/meltComponents/Popover.svelte b/frontend/src/lib/components/meltComponents/Popover.svelte index f850e53f6c..6a6b7d681b 100644 --- a/frontend/src/lib/components/meltComponents/Popover.svelte +++ b/frontend/src/lib/components/meltComponents/Popover.svelte @@ -60,6 +60,8 @@ documentationLink?: string | undefined disableFocusTrap?: boolean openFocus?: string | HTMLElement | (() => HTMLElement | null) | null | undefined + /** Element to focus when the popover closes; defaults to the trigger, `null` leaves focus alone. */ + closeFocus?: string | HTMLElement | (() => HTMLElement | null) | null | undefined escapeBehavior?: EscapeBehaviorType enableFlyTransition?: boolean onKeyDown?: (e: KeyboardEvent) => void @@ -99,6 +101,7 @@ documentationLink = undefined, disableFocusTrap = false, openFocus = undefined, + closeFocus = undefined, escapeBehavior = 'close', enableFlyTransition = false, onKeyDown = () => {}, @@ -133,6 +136,7 @@ disableFocusTrap: untrack(() => disableFocusTrap), escapeBehavior: untrack(() => escapeBehavior), openFocus: untrack(() => openFocus), + closeFocus: untrack(() => closeFocus), onOpenChange: ({ curr, next }) => { if (curr != next) { dispatch('openChange', next)