File tree Expand file tree Collapse file tree 3 files changed +64
-4
lines changed Expand file tree Collapse file tree 3 files changed +64
-4
lines changed Original file line number Diff line number Diff line change @@ -81,7 +81,9 @@ function ThreadCard({ frameSync, thread }: ThreadCardProps) {
8181 ) }
8282 data-testid = "thread-card"
8383 elementRef = { cardRef }
84- tabIndex = { - 1 }
84+ tabIndex = { 0 }
85+ role = "article"
86+ aria-label = "Press Enter to scroll annotation into view"
8587 onClick = { e => {
8688 // Prevent click events intended for another action from
8789 // triggering a page scroll.
@@ -91,6 +93,20 @@ function ThreadCard({ frameSync, thread }: ThreadCardProps) {
9193 } }
9294 onMouseEnter = { ( ) => setThreadHovered ( thread . annotation ?? null ) }
9395 onMouseLeave = { ( ) => setThreadHovered ( null ) }
96+ onKeyDown = { e => {
97+ // Simulate default button behavior, where `Enter` and `Space` trigger
98+ // click action
99+ if (
100+ // Trigger event only if the target is the card itself, so that we do
101+ // not scroll to the annotation while editing it, or if the key is
102+ // pressed to interact with a child button or link.
103+ e . target === cardRef . current &&
104+ [ 'Enter' , ' ' ] . includes ( e . key ) &&
105+ thread . annotation
106+ ) {
107+ scrollToAnnotation ( thread . annotation ) ;
108+ }
109+ } }
94110 key = { thread . id }
95111 >
96112 < CardContent > { threadContent } </ CardContent >
Original file line number Diff line number Diff line change 1- import { ListenerCollection } from '@hypothesis/frontend-shared' ;
1+ import {
2+ ListenerCollection ,
3+ useArrowKeyNavigation ,
4+ } from '@hypothesis/frontend-shared' ;
25import classnames from 'classnames' ;
36import debounce from 'lodash.debounce' ;
4- import { useEffect , useLayoutEffect , useMemo , useState } from 'preact/hooks' ;
7+ import {
8+ useEffect ,
9+ useLayoutEffect ,
10+ useMemo ,
11+ useRef ,
12+ useState ,
13+ } from 'preact/hooks' ;
514
615import type { Annotation , EPUBContentSelector } from '../../types/api' ;
716import type { Thread } from '../helpers/build-thread' ;
@@ -300,11 +309,18 @@ export default function ThreadList({ threads }: ThreadListProps) {
300309 } ) ;
301310 } , [ visibleThreads ] ) ;
302311
312+ const listRef = useRef < HTMLDivElement | null > ( null ) ;
313+ useArrowKeyNavigation ( listRef , {
314+ selector : 'div[role="article"]' ,
315+ horizontal : false ,
316+ loop : false ,
317+ } ) ;
318+
303319 return (
304320 // We use role="list"/role="listitem" rather than unstyled ul/li because
305321 // some screen readers do not treat them as lists if they don't explicitly
306322 // have bullets
307- < div role = "list" >
323+ < div role = "list" ref = { listRef } >
308324 < div style = { { height : offscreenUpperHeight } } />
309325 { visibleThreads . map ( ( child , index ) => (
310326 < div
Original file line number Diff line number Diff line change @@ -169,6 +169,34 @@ describe('ThreadCard', () => {
169169 } ) ;
170170 } ) ;
171171
172+ describe ( 'key down' , ( ) => {
173+ [
174+ { key : 'Enter' , shouldScroll : true } ,
175+ { key : ' ' , shouldScroll : true } ,
176+ { key : 'ArrowUp' , shouldScroll : false } ,
177+ { key : 'Escape' , shouldScroll : false } ,
178+ ] . forEach ( ( { key, shouldScroll } ) => {
179+ it ( 'scrolls to the annotation when Enter or Space are pressed on the `ThreadCard`' , ( ) => {
180+ const wrapper = createComponent ( ) ;
181+
182+ wrapper . find ( threadCardSelector ) . simulate ( 'keydown' , { key } ) ;
183+
184+ assert . equal ( fakeFrameSync . scrollToAnnotation . called , shouldScroll ) ;
185+ } ) ;
186+ } ) ;
187+
188+ it ( 'does not scroll to annotation when key is pressed in `ThreadCard` targeting other element' , ( ) => {
189+ const wrapper = createComponent ( ) ;
190+
191+ wrapper
192+ . find ( threadCardSelector )
193+ . props ( )
194+ . onKeyDown ( { key : 'Enter' , target : document . createElement ( 'a' ) } ) ;
195+
196+ assert . notCalled ( fakeFrameSync . scrollToAnnotation ) ;
197+ } ) ;
198+ } ) ;
199+
172200 it (
173201 'should pass a11y checks' ,
174202 checkAccessibility ( {
You can’t perform that action at this time.
0 commit comments