Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/sidebar-mobile-portal-focus.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cloudflare/kumo": patch
---

Keep the mobile Sidebar open when focus moves to portaled interactive content.
6 changes: 3 additions & 3 deletions packages/kumo/src/components/sidebar/sidebar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -683,7 +683,7 @@ describe("Sidebar mobile behavior", () => {
await waitFor(() => expect(document.activeElement).toBe(toggle));
});

it("should close on focus leave without stealing focus back", async () => {
it("should NOT close when focus moves outside the sidebar (e.g. to portaled content)", async () => {
setMobileMatchMedia(true);
const user = userEvent.setup();
render(<MobileTest />);
Expand All @@ -700,7 +700,7 @@ describe("Sidebar mobile behavior", () => {
afterSidebar.focus();
fireEvent.focusOut(nav, { relatedTarget: afterSidebar });

await waitFor(() => expect(nav.getAttribute("aria-hidden")).toBe("true"));
expect(document.activeElement).toBe(afterSidebar);
expect(nav.getAttribute("aria-hidden")).toBe("false");
expect(nav.hasAttribute("inert")).toBe(false);
});
});
11 changes: 1 addition & 10 deletions packages/kumo/src/components/sidebar/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -477,27 +477,18 @@ const SidebarRoot = forwardRef<HTMLElement, SidebarRootProps>(
const mobileNodeRef = useRef<HTMLElement | null>(null);
const shouldRestoreFocusRef = useRef(false);

// Escape key and focus-leave close the mobile sidebar
// Escape key closes the mobile sidebar
useEffect(() => {
if (!isMobile || !openMobile) return;
const node = mobileNodeRef.current;
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") {
shouldRestoreFocusRef.current = true;
setOpenMobile(false);
}
};
const handleFocusOut = (e: FocusEvent) => {
if (node && !node.contains(e.relatedTarget as Node)) {
shouldRestoreFocusRef.current = false;
setOpenMobile(false);
}
};
document.addEventListener("keydown", handleKeyDown);
node?.addEventListener("focusout", handleFocusOut);
return () => {
document.removeEventListener("keydown", handleKeyDown);
node?.removeEventListener("focusout", handleFocusOut);
};
}, [isMobile, openMobile, setOpenMobile]);

Expand Down
Loading