fix(focus): a removed control's focus skips a neighbour the CSS has hidden

A display:none button refuses focus(), so handing the focus to a hidden
column's header button dropped it to <body>.
This commit is contained in:
2026-09-27 16:22:26 +02:00
parent 595c29e17c
commit 27483ae1d6
2 changed files with 35 additions and 3 deletions
+7 -2
View File
@@ -15,12 +15,17 @@ import { useEffect, type RefObject } from "react";
const FOCUSABLE =
'button:not([disabled]), a[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
// A focusable element the CSS has taken off screen (a `tabletHidden` column's header button) refuses
// `focus()`, and the focus it was meant to catch fell to <body> (E28 F2, measured in Chrome). An engine
// without `checkVisibility` (jsdom) paints nothing, so there everything counts as shown.
const shown = (el: HTMLElement) => !el.checkVisibility || el.checkVisibility();
function firstFocusable(node: Node | null, fromEnd = false): HTMLElement | null {
let n: Node | null = node;
while (n) {
if (n instanceof HTMLElement) {
if (n.matches(FOCUSABLE)) return n;
const all = n.querySelectorAll<HTMLElement>(FOCUSABLE);
if (n.matches(FOCUSABLE) && shown(n)) return n;
const all = [...n.querySelectorAll<HTMLElement>(FOCUSABLE)].filter(shown);
const hit = fromEnd ? all[all.length - 1] : all[0];
if (hit) return hit;
}