From acc1392a539f97c895cab9bc8f452fbf09b02903 Mon Sep 17 00:00:00 2001 From: Hampus Date: Thu, 8 Oct 2026 20:33:04 +0200 Subject: [PATCH] fix(ui): reopen a popout clicked while it is closing (#3318) --- fluxer_app/src/app/App.tsx | 4 +--- .../src/features/ui/popover/PopoverPopout.tsx | 2 +- .../src/features/ui/popover/PopoverPopouts.tsx | 14 ++++++++++++-- fluxer_app/src/features/ui/state/Popout.ts | 5 +++++ 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/fluxer_app/src/app/App.tsx b/fluxer_app/src/app/App.tsx index b4f13d9be..742693f31 100644 --- a/fluxer_app/src/app/App.tsx +++ b/fluxer_app/src/app/App.tsx @@ -94,9 +94,7 @@ export const AppWrapper = observer(({children}: AppWrapperProps) => { () => ({variant: layoutVariant, setVariant: setLayoutVariant}), [layoutVariant], ); - const popouts = Popout.getPopouts(); - const topPopout = popouts.length ? popouts[popouts.length - 1] : null; - const topPopoutRequiresBackdrop = Boolean(topPopout && !topPopout.disableBackdrop); + const topPopoutRequiresBackdrop = Popout.requiresBackdrop(); const hasBlockingModal = Modal.hasModalOpen(); const room = MediaEngine.room; const ringsContainerRef = useRef(null); diff --git a/fluxer_app/src/features/ui/popover/PopoverPopout.tsx b/fluxer_app/src/features/ui/popover/PopoverPopout.tsx index 970a99057..c2bf6fca2 100644 --- a/fluxer_app/src/features/ui/popover/PopoverPopout.tsx +++ b/fluxer_app/src/features/ui/popover/PopoverPopout.tsx @@ -309,7 +309,7 @@ export const Popout = React.forwardRef((props, ref) => ); const toggle = useCallback( (clickPos?: number) => { - if (PopoutState.isOpen(state.id)) { + if (PopoutState.isOpen(state.id) && !PopoutState.isClosing(state.id)) { if (props.hoverDelay != null && openModeRef.current === 'hover') { clearTimers(); open(clickPos, 'click'); diff --git a/fluxer_app/src/features/ui/popover/PopoverPopouts.tsx b/fluxer_app/src/features/ui/popover/PopoverPopouts.tsx index 99802425c..53a3e2ec4 100644 --- a/fluxer_app/src/features/ui/popover/PopoverPopouts.tsx +++ b/fluxer_app/src/features/ui/popover/PopoverPopouts.tsx @@ -240,11 +240,21 @@ const PopoutItem: React.FC = observer( setTargetInDOM(true); } }, [target, ownerDocument, keepOpenOnTargetUnmount, beginClose]); + const wasClosingRequestedRef = useRef(isClosingRequested); useEffect(() => { + const wasClosingRequested = wasClosingRequestedRef.current; + wasClosingRequestedRef.current = isClosingRequested; if (isClosingRequested) { beginClose(); + return; } - }, [isClosingRequested, beginClose]); + if (!wasClosingRequested) return; + if (closeTimerRef.current != null && ownerWindow != null) { + ownerWindow.clearTimeout(closeTimerRef.current); + closeTimerRef.current = null; + } + setIsVisible(true); + }, [isClosingRequested, beginClose, ownerWindow]); useEffect(() => { return () => { if (closeTimerRef.current != null && ownerWindow != null) { @@ -460,7 +470,7 @@ export const Popouts: React.FC = observer(({ownerDocument}) => { const portalHost = scopePortalHostToDocument(activePortalHost, scopeDocument); const popouts = PopoutState.getPopouts(scopeDocument); const topPopout = popouts.length ? popouts[popouts.length - 1] : null; - const needsBackdrop = Boolean(topPopout && !topPopout.disableBackdrop); + const needsBackdrop = PopoutState.requiresBackdrop(scopeDocument); useEffect(() => { const currentKeys = new Set(popouts.map((popout) => popout.key.toString())); const prevKeys = prevPopoutKeysRef.current; diff --git a/fluxer_app/src/features/ui/state/Popout.ts b/fluxer_app/src/features/ui/state/Popout.ts index baa9e64c7..61b22f688 100644 --- a/fluxer_app/src/features/ui/state/Popout.ts +++ b/fluxer_app/src/features/ui/state/Popout.ts @@ -218,6 +218,11 @@ class Popout { ); } + requiresBackdrop(ownerDocument: Document = document): boolean { + const owner = this.getPopouts(ownerDocument).findLast((popout) => !this.isClosing(popout.key)); + return owner != null && !owner.disableBackdrop; + } + private getParentPopoutChain(dependsOnKey: string): Record { const result: Record = {}; let currentKey: string | undefined = dependsOnKey;