fix(ui): Back finds the item again past the focus keeper; nested drags stay in their list
Deep review of the debt burn, each measured in Chrome: - Back from a sub-view whose focused control the swap removed: the focus keeper put the focus on a stand-in (the returning list's first item) and `restoreNavPosition`, finding the focus "already somewhere", gave up — Plex show → Back landed on tile 0, not the tile it was opened from. A stand-in the keeper placed now counts as untouched, like <body>. The keeper's walk up also stops at its container: a slot left with nothing focusable no longer sends the focus to the first control of the whole document. - DataTable enabled both layouts' keyboard lists with the same flag, and `useListNav` reads its ref only when the flag changes: after a resize across `md`, the layout that mounted was never registered (no j/k) and the one that left stayed registered. Each is enabled with its layout. - A Plex playlist nests blocks and each show's episodes in one DndContext; closestCenter and the keyboard coordinates weighed every droppable, so a movie landed "over" an episode — announced as a place in the other list, dropped somewhere else or nowhere. `sameListCollision` and `sameListKeyboardCoordinates` keep a drag among its siblings; onDragEnd's episode → block resolution is gone with the case. - dnd-kit reports the item over itself in the pick-up's own tick; announced, "moved to position 1" replaced "picked up … use the arrow keys" before a screen reader read it.
This commit is contained in:
@@ -99,6 +99,36 @@ describe("DataTable · keyboardNav", () => {
|
||||
expect(narrow.container.querySelector("table")).toBeNull();
|
||||
});
|
||||
|
||||
// `useListNav` reads its container when its `enabled` changes. With the flag left on across a
|
||||
// breakpoint, the layout mounted by a resize was never registered — `j` did nothing — while the
|
||||
// unmounted one stayed registered (deep review, debt burn).
|
||||
it("keeps the keys working on whichever layout a resize mounts", () => {
|
||||
let wide = true;
|
||||
const listeners = new Set<() => void>();
|
||||
vi.stubGlobal("matchMedia", (query: string) => ({
|
||||
get matches() {
|
||||
return query === "(min-width: 48rem)" ? wide : false;
|
||||
},
|
||||
media: query,
|
||||
addEventListener: (_: string, l: () => void) => listeners.add(l),
|
||||
removeEventListener: (_: string, l: () => void) => listeners.delete(l),
|
||||
}));
|
||||
const resize = (next: boolean) => {
|
||||
wide = next;
|
||||
act(() => listeners.forEach((l) => l()));
|
||||
};
|
||||
table(() => {});
|
||||
resize(false);
|
||||
expect(document.querySelectorAll("[data-nav-list]")).toHaveLength(1);
|
||||
press("j");
|
||||
expect(focused()).toBe("DIV a");
|
||||
resize(true);
|
||||
expect(document.querySelectorAll("[data-nav-list]")).toHaveLength(1);
|
||||
(document.activeElement as HTMLElement).blur();
|
||||
press("j");
|
||||
expect(focused()).toBe("TR a");
|
||||
});
|
||||
|
||||
it("leaves the rows out of the keyboard without keyboardNav", () => {
|
||||
const { container } = table(() => {}, false);
|
||||
expect(container.querySelector("[data-nav-index]")).toBeNull();
|
||||
|
||||
Reference in New Issue
Block a user