From b51562d0e3110be319b80bc2244c9d4958fe6f36 Mon Sep 17 00:00:00 2001 From: Hampus Date: Mon, 24 Aug 2026 12:53:47 +0200 Subject: [PATCH] fix(app): keep focus and its ring on a spoiler after it is revealed (#1961) --- .../app/components/shared/SpoilerOverlay.tsx | 20 ++++++++- .../renderers/common/FormattingElements.tsx | 43 +++++++------------ 2 files changed, 35 insertions(+), 28 deletions(-) diff --git a/fluxer_app/src/features/app/components/shared/SpoilerOverlay.tsx b/fluxer_app/src/features/app/components/shared/SpoilerOverlay.tsx index c9a617c44..75018ee7d 100644 --- a/fluxer_app/src/features/app/components/shared/SpoilerOverlay.tsx +++ b/fluxer_app/src/features/app/components/shared/SpoilerOverlay.tsx @@ -6,6 +6,7 @@ import {msg} from '@lingui/core/macro'; import {useLingui} from '@lingui/react/macro'; import {clsx} from 'clsx'; import type {FC, ReactNode} from 'react'; +import {useCallback, useLayoutEffect, useRef} from 'react'; const REVEAL_SPOILER_DESCRIPTOR = msg({ message: 'Reveal spoiler', @@ -27,9 +28,26 @@ interface SpoilerOverlayProps { export const SpoilerOverlay: FC = ({hidden, onReveal, children, label, inline, className}) => { const {i18n} = useLingui(); + const wrapperElementRef = useRef(null); + const pendingFocusHandoffRef = useRef(false); const ariaLabel = label ?? i18n._(REVEAL_SPOILER_DESCRIPTOR); + const handleRevealRequest = useCallback(() => { + pendingFocusHandoffRef.current = true; + onReveal(); + }, [onReveal]); + useLayoutEffect(() => { + if (hidden) { + pendingFocusHandoffRef.current = false; + return; + } + if (!pendingFocusHandoffRef.current) return; + pendingFocusHandoffRef.current = false; + wrapperElementRef.current?.focus({preventScroll: true}); + }, [hidden]); return (