Fix ConfirmButton Enter/Space keydown bubbling to parent row handler

The button's onClick stopped mouse event propagation, but keyboard
Enter/Space fired a separate keydown event that bubbled to the
combatant row's onKeyDown, opening the stat block side panel instead
of arming/confirming the button.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lukas
2026-03-11 10:04:27 +01:00
parent 236c3bf64a
commit 0c903bc9a5
2 changed files with 27 additions and 0 deletions

View File

@@ -195,4 +195,24 @@ describe("ConfirmButton", () => {
fireEvent.blur(button); fireEvent.blur(button);
expect(screen.getByTestId("x-icon")).toBeTruthy(); expect(screen.getByTestId("x-icon")).toBeTruthy();
}); });
it("Enter/Space keydown stops propagation to prevent parent handlers", () => {
const parentHandler = vi.fn();
render(
// biome-ignore lint/a11y/noStaticElementInteractions: test wrapper
<div onKeyDown={parentHandler}>
<ConfirmButton
icon={<XIcon />}
label="Remove combatant"
onConfirm={vi.fn()}
/>
</div>,
);
const button = screen.getByRole("button");
fireEvent.keyDown(button, { key: "Enter" });
fireEvent.keyDown(button, { key: " " });
expect(parentHandler).not.toHaveBeenCalled();
});
}); });

View File

@@ -67,6 +67,12 @@ export function ConfirmButton({
}; };
}, [isConfirming, revert]); }, [isConfirming, revert]);
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
if (e.key === "Enter" || e.key === " ") {
e.stopPropagation();
}
}, []);
const handleClick = useCallback( const handleClick = useCallback(
(e: React.MouseEvent) => { (e: React.MouseEvent) => {
e.stopPropagation(); e.stopPropagation();
@@ -95,6 +101,7 @@ export function ConfirmButton({
"bg-destructive text-primary-foreground rounded-md animate-confirm-pulse hover:bg-destructive hover:text-primary-foreground", "bg-destructive text-primary-foreground rounded-md animate-confirm-pulse hover:bg-destructive hover:text-primary-foreground",
)} )}
onClick={handleClick} onClick={handleClick}
onKeyDown={handleKeyDown}
onBlur={revert} onBlur={revert}
disabled={disabled} disabled={disabled}
aria-label={isConfirming ? `Confirm ${label.toLowerCase()}` : label} aria-label={isConfirming ? `Confirm ${label.toLowerCase()}` : label}