fix(a11y): chips, grips, the panel tab and Back to top keep the focus somewhere real
- F5: a chip's × pressed from the keyboard hands the focus to the next chip (else the previous one, else what follows) before it unmounts — in DismissibleChip, so all five chip rows get it (`focusNeighbour` in lib/useFocusKeeper). - F4: a drag grip names what it lifts (`common.order.dragNamed`: panel groups, personal views); the two generic "Drag to reorder" keys are gone. - F6: the side panel's edge tab is out of the Tab order while the card covers it, names its panel, and a keyboard pin moves the focus into the card. - F7: Back to top is no Tab stop while hidden, and a keyboard press leaves the focus on the page.
This commit is contained in:
@@ -29,6 +29,21 @@ function firstFocusable(node: Node | null, fromEnd = false): HTMLElement | null
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Move the focus off `el`, which is about to go, to its nearest focusable neighbour — the next one,
|
||||
* else the previous one, widening to the parent's neighbours — so its removal does not drop the
|
||||
* focus to <body>. For a removal the caller makes itself (a chip's ×) anywhere, including a modal
|
||||
* or a side panel, where the keeper below does not watch. False when nothing was found. */
|
||||
export function focusNeighbour(el: HTMLElement): boolean {
|
||||
for (let at: HTMLElement | null = el; at && at !== document.body; at = at.parentElement) {
|
||||
const hit = firstFocusable(at.nextSibling) ?? firstFocusable(at.previousSibling, true);
|
||||
if (hit) {
|
||||
hit.focus({ preventScroll: true });
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// The element the keeper last put the focus on, while it still holds it. A Back that swaps the page
|
||||
// or a sub-view removes the held control too, and the keeper's stand-in then sat where
|
||||
// `restoreNavPosition` looks for an untouched <body>: the restore gave up and Back left the focus on
|
||||
|
||||
Reference in New Issue
Block a user