From 8456295eee0ddbb1f1663a127252d4f31acb1f75 Mon Sep 17 00:00:00 2001 From: Tommaso Casaburi Date: Thu, 11 Jun 2026 16:35:18 +0700 Subject: [PATCH] fix(reply-modal): blur TeX button when preview closes Blur the TeX preview trigger on close so Escape does not leave a lingering focus outline or tooltip on the button. --- .../__tests__/reply-modal.test.tsx | 27 +++++++++++++++++++ .../reply-modal/reply-modal.module.css | 8 +----- src/components/reply-modal/reply-modal.tsx | 9 ++++++- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/components/reply-modal/__tests__/reply-modal.test.tsx b/src/components/reply-modal/__tests__/reply-modal.test.tsx index 7c3ac1a0..3b0cf8df 100644 --- a/src/components/reply-modal/__tests__/reply-modal.test.tsx +++ b/src/components/reply-modal/__tests__/reply-modal.test.tsx @@ -544,6 +544,33 @@ describe('ReplyModal', () => { expect(texPreview?.textContent).toContain('tex_preview_title'); }); + it('clears TeX button focus after closing the TeX preview with Escape', async () => { + testState.directoryByAddress['science-and-math.bso'] = { + address: 'science-and-math.bso', + directoryCode: 'sci', + features: {}, + title: '/sci/ - Science & Math', + }; + testState.communities['science-and-math.bso'] = { address: 'science-and-math.bso' }; + + await renderReplyModal('/sci/thread/post-1', 'science-and-math.bso'); + + const texButton = container.querySelector('button[aria-label="preview_tex_equations"]'); + expect(texButton).toBeTruthy(); + + await act(async () => { + texButton?.focus(); + texButton?.dispatchEvent(new MouseEvent('click', { bubbles: true })); + }); + expect(document.activeElement).toBe(texButton); + + await act(async () => { + document.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, cancelable: true, key: 'Escape' })); + }); + + expect(document.activeElement).not.toBe(texButton); + }); + it('shows a flag selector on flag boards and publishes the default geographic request', async () => { await renderReplyModal('/pol/thread/post-1', 'politically-incorrect.bso'); diff --git a/src/components/reply-modal/reply-modal.module.css b/src/components/reply-modal/reply-modal.module.css index 58f80383..f474fc8e 100644 --- a/src/components/reply-modal/reply-modal.module.css +++ b/src/components/reply-modal/reply-modal.module.css @@ -58,17 +58,11 @@ text-decoration: none; } -.texButton:hover, -.texButton:focus-visible { +.texButton:hover { color: var(--button-desktop-text-color-hover); text-decoration: none; } -.texButton:focus-visible { - outline: 1px dotted currentColor; - outline-offset: 1px; -} - .texButton sub { font-size: 80%; pointer-events: none; diff --git a/src/components/reply-modal/reply-modal.tsx b/src/components/reply-modal/reply-modal.tsx index cf4393a8..9cb4aaf5 100644 --- a/src/components/reply-modal/reply-modal.tsx +++ b/src/components/reply-modal/reply-modal.tsx @@ -127,7 +127,13 @@ const ReplyModal = ({ closeModal, showReplyModal, parentCid, parentNumber, threa const [isBbcodePreviewing, setIsBbcodePreviewing] = useState(false); const [bbcodePreviewContent, setBbcodePreviewContent] = useState(''); const [showTexPreview, setShowTexPreview] = useState(false); - const closeTexPreview = useCallback(() => setShowTexPreview(false), []); + const texButtonRef = useRef(null); + // Blur in the close handler so the TeX button doesn't keep a lingering focus state + // (focus-visible promotion on Escape, focus-triggered tooltip) after the preview closes. + const closeTexPreview = useCallback(() => { + setShowTexPreview(false); + texButtonRef.current?.blur(); + }, []); const checkContentLengthRef = useRef( debounce((content: string, t: TFunction, options: string, directoryCode: string | undefined) => { @@ -550,6 +556,7 @@ const ReplyModal = ({ closeModal, showReplyModal, parentCid, parentNumber, threa {showTexButton && !isMobile && (