fix(channels): a pick in the +N fold keeps the focus on its chip after the fold closes
The closing popover hands the focus back to "+N" in a microtask it queues as it unmounts, after the chip was focused (seen in Chrome; jsdom does not show it). A second focus, queued after it, wins.
This commit is contained in:
@@ -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
|
// React gives the focus back to it after the commit. Only a remount (row ↔ fold) or a chip that
|
||||||
// leaves the list loses it.
|
// 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;
|
fit.visible = 1;
|
||||||
render(<Row start={[]} />);
|
render(<Row start={[]} />);
|
||||||
fireEvent.click(moreBtn()!);
|
fireEvent.click(moreBtn()!);
|
||||||
const inFold = named("beta");
|
const inFold = named("beta");
|
||||||
press(inFold);
|
press(inFold);
|
||||||
|
// The closing popover hands the focus back to "+N" a microtask later: past that too.
|
||||||
|
await act(async () => {});
|
||||||
const onRow = named("beta");
|
const onRow = named("beta");
|
||||||
expect(onRow).not.toBe(inFold);
|
expect(onRow).not.toBe(inFold);
|
||||||
expect(document.activeElement).toBe(onRow);
|
expect(document.activeElement).toBe(onRow);
|
||||||
|
|||||||
@@ -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
|
// 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
|
// 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.
|
// 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<HTMLDivElement | null>(null);
|
const foldRef = useRef<HTMLDivElement | null>(null);
|
||||||
const moreRef = useRef<HTMLButtonElement | null>(null);
|
const moreRef = useRef<HTMLButtonElement | null>(null);
|
||||||
const onToggled = (chipKey: string, from: HTMLElement) => {
|
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));
|
requestAnimationFrame(() => (follow.current = null));
|
||||||
if (foldRef.current?.contains(from)) setOpen(false);
|
if (closed) setOpen(false);
|
||||||
};
|
};
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const target = follow.current;
|
const target = follow.current;
|
||||||
@@ -158,6 +159,9 @@ export default function TagFilterChips({
|
|||||||
find(inBox(foldRef.current)) ??
|
find(inBox(foldRef.current)) ??
|
||||||
(gone ? (row[Math.min(target.at, row.length - 1)] ?? moreRef.current) : moreRef.current);
|
(gone ? (row[Math.min(target.at, row.length - 1)] ?? moreRef.current) : moreRef.current);
|
||||||
if (el && document.activeElement !== el) el.focus();
|
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) => {
|
const setMore = (el: HTMLButtonElement | null) => {
|
||||||
moreRef.current = el;
|
moreRef.current = el;
|
||||||
|
|||||||
Reference in New Issue
Block a user