diff --git a/craft/src/utils/a11y.test.ts b/craft/src/utils/a11y.test.ts index a3cbba7..1643b40 100644 --- a/craft/src/utils/a11y.test.ts +++ b/craft/src/utils/a11y.test.ts @@ -48,4 +48,35 @@ describe('clickableProps', () => { expect(onActivate).not.toHaveBeenCalled(); expect(e.preventDefault).not.toHaveBeenCalled(); }); + + test('onKeyDown activates when the event target is the row itself', () => { + const onActivate = vi.fn(); + const props = clickableProps(onActivate); + const rowEl = {}; + const e = { + key: 'Enter', + target: rowEl, + currentTarget: rowEl, + preventDefault: vi.fn(), + } as unknown as Parameters['onKeyDown']>[0]; + props.onKeyDown(e); + expect(onActivate).toHaveBeenCalledTimes(1); + expect(e.preventDefault).toHaveBeenCalledTimes(1); + }); + + test('onKeyDown ignores a bubbled event from a nested control (e.g. a child button)', () => { + const onActivate = vi.fn(); + const props = clickableProps(onActivate); + const rowEl = {}; + const childButtonEl = {}; + const e = { + key: 'Enter', + target: childButtonEl, + currentTarget: rowEl, + preventDefault: vi.fn(), + } as unknown as Parameters['onKeyDown']>[0]; + props.onKeyDown(e); + expect(onActivate).not.toHaveBeenCalled(); + expect(e.preventDefault).not.toHaveBeenCalled(); + }); }); diff --git a/craft/src/utils/a11y.ts b/craft/src/utils/a11y.ts index b71325e..5e72c6b 100644 --- a/craft/src/utils/a11y.ts +++ b/craft/src/utils/a11y.ts @@ -21,6 +21,12 @@ export function clickableProps(onActivate: () => void): ClickableProps { tabIndex: 0, onClick: onActivate, onKeyDown: (e: KeyboardEvent) => { + // Ignore keydown events that bubbled up from a nested interactive + // element (e.g. a child