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;