Fix iOS keyboard suddenly hiding while editing budget amounts (#6583)

* Fix iOS keyboard suddenly hiding while editing budget amounts

The react-aria-components Buttons calls the onHoverStart even if there
is no fine pointing device attached to the client (iOS 26+), causing the
amount input to loose its focus.
This commits checks if there is any input with focus before auto focus
the menu Container.

* [autofix.ci] apply automated fixes

* Fix menu aria pattern by making excluding menu buttons from tab order

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Joelson Carvalho
2026-01-07 15:50:14 -03:00
committed by GitHub
parent d30f3aa36d
commit 8a1e1923dd
2 changed files with 74 additions and 47 deletions

View File

@@ -1,12 +1,13 @@
import {
type ReactNode,
useEffect,
useRef,
useState,
type ComponentProps,
type ComponentType,
type SVGProps,
type CSSProperties,
type KeyboardEvent,
useEffect,
useRef,
} from 'react';
import { Button } from './Button';
@@ -78,62 +79,81 @@ export function Menu<const NameType = string>({
}: MenuProps<NameType>) {
const elRef = useRef<HTMLDivElement>(null);
const items = allItems.filter(x => x);
const filteredItems = items.filter(
item => item && item !== Menu.line && item.type !== Menu.label,
);
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
const currentIndex = filteredItems.indexOf(items[hoveredIndex || 0]);
const transformIndex = (idx: number) => items.indexOf(filteredItems[idx]);
function hoverPrevious() {
setHoveredIndex(
hoveredIndex === null ? 0 : transformIndex(Math.max(currentIndex - 1, 0)),
);
}
function hoverNext() {
setHoveredIndex(
hoveredIndex === null
? 0
: transformIndex(Math.min(currentIndex + 1, filteredItems.length - 1)),
);
}
function selectItem() {
const item = items[hoveredIndex || 0];
if (
hoveredIndex !== null &&
item !== Menu.line &&
!isLabel(item) &&
!item.disabled
) {
onMenuSelect?.(item.name);
}
}
function onKeyDown(e: KeyboardEvent) {
switch (e.key) {
case 'ArrowUp':
e.preventDefault();
hoverPrevious();
break;
case 'ArrowDown':
e.preventDefault();
hoverNext();
break;
case 'Enter':
e.preventDefault();
selectItem();
break;
default:
}
}
useEffect(() => {
const activeElement = document.activeElement;
if (
activeElement &&
(['input', 'select', 'textarea'].includes(
activeElement.tagName.toLowerCase(),
) ||
activeElement.hasAttribute('contenteditable') ||
activeElement.getAttribute('role') === 'textbox')
) {
return;
}
const el = elRef.current;
el?.focus();
const onKeyDown = (e: KeyboardEvent) => {
const filteredItems = items.filter(
item => item && item !== Menu.line && item.type !== Menu.label,
);
const currentIndex = filteredItems.indexOf(items[hoveredIndex || 0]);
const transformIndex = (idx: number) => items.indexOf(filteredItems[idx]);
switch (e.key) {
case 'ArrowUp':
e.preventDefault();
setHoveredIndex(
hoveredIndex === null
? 0
: transformIndex(Math.max(currentIndex - 1, 0)),
);
break;
case 'ArrowDown':
e.preventDefault();
setHoveredIndex(
hoveredIndex === null
? 0
: transformIndex(
Math.min(currentIndex + 1, filteredItems.length - 1),
),
);
break;
case 'Enter':
e.preventDefault();
const item = items[hoveredIndex || 0];
if (hoveredIndex !== null && item !== Menu.line && !isLabel(item)) {
onMenuSelect?.(item.name);
}
break;
default:
}
};
el?.addEventListener('keydown', onKeyDown);
return () => {
el?.removeEventListener('keydown', onKeyDown);
};
}, [hoveredIndex]);
}, []);
return (
<View
className={className}
style={{ outline: 'none', borderRadius: 4, overflow: 'hidden', ...style }}
tabIndex={1}
onKeyDown={onKeyDown}
innerRef={elRef}
>
{header}
@@ -166,6 +186,7 @@ export function Menu<const NameType = string>({
return (
<Button
excludeFromTabOrder
key={String(item.name)}
variant="bare"
slot={slot}