diff --git a/.changeset/fruity-bags-love.md b/.changeset/fruity-bags-love.md new file mode 100644 index 00000000..347a0a04 --- /dev/null +++ b/.changeset/fruity-bags-love.md @@ -0,0 +1,5 @@ +--- +'react-resource-router': patch +--- + +pass down onKeyDown to Link diff --git a/package.json b/package.json index 95ae5e01..3df63b1c 100644 --- a/package.json +++ b/package.json @@ -141,4 +141,4 @@ "engines": { "node": ">=16.0" } -} +} \ No newline at end of file diff --git a/src/common/utils/event/index.ts b/src/common/utils/event/index.ts index 5f10f93b..ee76c8fe 100644 --- a/src/common/utils/event/index.ts +++ b/src/common/utils/event/index.ts @@ -1,3 +1,5 @@ +import type { KeyboardEvent } from 'react'; + export const isModifiedEvent = (event: { [key: string]: any }) => !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey); diff --git a/src/ui/link/index.tsx b/src/ui/link/index.tsx index 8e0997eb..52c3c001 100644 --- a/src/ui/link/index.tsx +++ b/src/ui/link/index.tsx @@ -31,6 +31,7 @@ const Link = forwardRef( href = undefined, to = undefined, onClick = undefined, + onKeyDown = undefined, onMouseEnter = undefined, onMouseLeave = undefined, onPointerDown = undefined, @@ -103,6 +104,7 @@ const Link = forwardRef( const handleLinkPress = (e: MouseEvent | KeyboardEvent) => handleNavigation(e, { onClick, + onKeyDown, target, replace, routerActions, diff --git a/src/ui/link/test.tsx b/src/ui/link/test.tsx index d6f518c8..7314f8aa 100644 --- a/src/ui/link/test.tsx +++ b/src/ui/link/test.tsx @@ -362,7 +362,9 @@ describe('', () => { describe('when the link has focus, and a keypress is fired', () => { it('should navigate if the key was an `enter`', async () => { const user = userEvent.setup(); - renderInRouter('my link', { href: newPath }); + const mockKeyHandler = jest.fn(); + + renderInRouter('my link', { href: newPath, onKeyDown: mockKeyHandler }); const linkElement = screen.getByRole('link', { name: 'my link' }); linkElement.focus(); @@ -370,6 +372,7 @@ describe('', () => { expect(HistoryMock.push).toHaveBeenCalledTimes(1); expect(HistoryMock.push).toHaveBeenCalledWith(newPath, undefined); + expect(mockKeyHandler).not.toHaveBeenCalled(); }); it('should not navigate for any other key', async () => { @@ -382,6 +385,19 @@ describe('', () => { expect(HistoryMock.push).not.toHaveBeenCalled(); }); + + it('should respect onKeyDown as long as it is not {Enter}', async () => { + const user = userEvent.setup(); + const mockKeyHandler = jest.fn(); + renderInRouter('my link', { href: newPath, onKeyDown: mockKeyHandler }); + + const linkElement = screen.getByRole('link', { name: 'my link' }); + linkElement.focus(); + await user.keyboard('{a}'); + + expect(HistoryMock.push).not.toHaveBeenCalled(); + expect(mockKeyHandler).toHaveBeenCalled(); + }); }); describe('when styles are passed into Link, element should be rendered with styles', () => { diff --git a/src/ui/link/utils/handle-navigation.tsx b/src/ui/link/utils/handle-navigation.tsx index 76eb3fef..07711889 100644 --- a/src/ui/link/utils/handle-navigation.tsx +++ b/src/ui/link/utils/handle-navigation.tsx @@ -1,6 +1,6 @@ -import { KeyboardEvent, MouseEvent } from 'react'; +import type { KeyboardEvent, MouseEvent } from 'react'; -import { Route } from '../../../common/types'; +import type { Route } from '../../../common/types'; import { isKeyboardEvent, isModifiedEvent } from '../../../common/utils/event'; type LinkNavigationEvent = MouseEvent | KeyboardEvent; @@ -16,15 +16,27 @@ type LinkPressArgs = { replace: boolean; href: string; onClick?: (e: LinkNavigationEvent) => void; + onKeyDown?: (e: KeyboardEvent) => void; to: [Route, any] | void; state?: unknown; }; export const handleNavigation = ( event: any, - { onClick, target, replace, routerActions, href, to, state }: LinkPressArgs + { + onClick, + onKeyDown, + target, + replace, + routerActions, + href, + to, + state, + }: LinkPressArgs ): void => { if (isKeyboardEvent(event) && event.key !== 'Enter') { + onKeyDown?.(event as KeyboardEvent); + return; }