diff --git a/frontend/src/modules/channels/TagFilterChips.dom.test.tsx b/frontend/src/modules/channels/TagFilterChips.dom.test.tsx index cd0c08ee..e4aa7161 100644 --- a/frontend/src/modules/channels/TagFilterChips.dom.test.tsx +++ b/frontend/src/modules/channels/TagFilterChips.dom.test.tsx @@ -152,12 +152,14 @@ describe("TagFilterChips focus on a toggle that moves the chip", () => { // React gives the focus back to it after the commit. Only a remount (row ↔ fold) or a chip that // leaves the list loses it. - it("out of the fold: picked in the popover, it is focused on the row", () => { + it("out of the fold: picked in the popover, it is focused on the row", async () => { fit.visible = 1; render(); fireEvent.click(moreBtn()!); const inFold = named("beta"); press(inFold); + // The closing popover hands the focus back to "+N" a microtask later: past that too. + await act(async () => {}); const onRow = named("beta"); expect(onRow).not.toBe(inFold); expect(document.activeElement).toBe(onRow); diff --git a/frontend/src/modules/channels/TagFilterChips.tsx b/frontend/src/modules/channels/TagFilterChips.tsx index c9a0c0eb..d7af8ba4 100644 --- a/frontend/src/modules/channels/TagFilterChips.tsx +++ b/frontend/src/modules/channels/TagFilterChips.tsx @@ -136,13 +136,14 @@ export default function TagFilterChips({ // closed fold, or onto the chip now in its old place when it is gone. A toggle in the open fold // closes it, so the focus never sits outside a popover left open. Held for the frame, since the fit // re-decides in layout effects after the toggle's render. - const follow = useRef<{ key: string; at: number } | null>(null); + const follow = useRef<{ key: string; at: number; closed: boolean } | null>(null); const foldRef = useRef(null); const moreRef = useRef(null); const onToggled = (chipKey: string, from: HTMLElement) => { - follow.current = { key: chipKey, at: chips.findIndex((c) => c.key === chipKey) }; + const closed = !!foldRef.current?.contains(from); + follow.current = { key: chipKey, at: chips.findIndex((c) => c.key === chipKey), closed }; requestAnimationFrame(() => (follow.current = null)); - if (foldRef.current?.contains(from)) setOpen(false); + if (closed) setOpen(false); }; useLayoutEffect(() => { const target = follow.current; @@ -158,6 +159,9 @@ export default function TagFilterChips({ find(inBox(foldRef.current)) ?? (gone ? (row[Math.min(target.at, row.length - 1)] ?? moreRef.current) : moreRef.current); if (el && document.activeElement !== el) el.focus(); + // The closing popover gives the focus back to "+N" in a microtask it queued as it unmounted + // (seen in Chrome): this one, queued after it, has the last word. + if (el && target.closed) queueMicrotask(() => el.isConnected && el.focus()); }); const setMore = (el: HTMLButtonElement | null) => { moreRef.current = el;