@@ -173,6 +173,81 @@ function findAncestorByTagName(el, tagName) {
173173 return el ;
174174}
175175
176+ function isFocusableCell ( cell ) {
177+ return cell && cell . getAttribute ( "tabindex" ) == 0 ;
178+ }
179+
180+ function focusCell ( cell ) {
181+ if ( ! isFocusableCell ( cell ) )
182+ return false ;
183+
184+ cell . focus ( { focusVisible : true } ) ;
185+ return true ;
186+ }
187+
188+ function getBodyRows ( table ) {
189+ return Array . from ( table . querySelectorAll ( "tbody tr" ) ) ;
190+ }
191+
192+ function getRowCells ( row ) {
193+ return row ? Array . from ( row . querySelectorAll ( "td" ) ) : [ ] ;
194+ }
195+
196+ function getNavigableCells ( table ) {
197+ return Array . from ( table . querySelectorAll ( QUERYSELECTOR_ALL_COLUMNS ) ) . filter ( isFocusableCell ) ;
198+ }
199+
200+ function findClosestNavigableCell ( rowCells , preferredIndex ) {
201+ if ( ! rowCells || rowCells . length === 0 )
202+ return null ;
203+
204+ const startIndex = Math . max ( 0 , Math . min ( preferredIndex , rowCells . length - 1 ) ) ;
205+
206+ for ( let offset = 0 ; offset < rowCells . length ; offset ++ ) {
207+ const leftIndex = startIndex - offset ;
208+ if ( leftIndex >= 0 && isFocusableCell ( rowCells [ leftIndex ] ) )
209+ return rowCells [ leftIndex ] ;
210+
211+ const rightIndex = startIndex + offset ;
212+ if ( offset > 0 && rightIndex < rowCells . length && isFocusableCell ( rowCells [ rightIndex ] ) )
213+ return rowCells [ rightIndex ] ;
214+ }
215+
216+ return null ;
217+ }
218+
219+ function getPageJumpRowCount ( table , rows ) {
220+ if ( ! rows || rows . length === 0 )
221+ return 1 ;
222+
223+ const container = findScrollContainer ( table ) ;
224+ if ( ! container )
225+ return 1 ;
226+
227+ let totalHeight = 0 ;
228+ let measuredRows = 0 ;
229+
230+ for ( const row of rows ) {
231+ const rowHeight = row . offsetHeight ;
232+ if ( ! rowHeight )
233+ continue ;
234+
235+ totalHeight += rowHeight ;
236+ measuredRows += 1 ;
237+ }
238+
239+ if ( measuredRows === 0 )
240+ return 1 ;
241+
242+ const averageRowHeight = totalHeight / measuredRows ;
243+ const containerHeight = container . clientHeight || table . parentElement ?. clientHeight || 0 ;
244+
245+ if ( ! averageRowHeight || ! containerHeight )
246+ return 1 ;
247+
248+ return Math . max ( 1 , Math . floor ( containerHeight / averageRowHeight ) ) ;
249+ }
250+
176251function clickCellNavigation ( e ) {
177252 // Do not hijack clicks coming from interactive elements
178253 const interactiveClosest = e . target . closest ( 'input,select,textarea,button,label,a,[role="button"],[role="checkbox"],[contenteditable="true"]' ) ;
@@ -209,24 +284,32 @@ function KeyDownCellNavigation(e) {
209284 }
210285 const TAG_NAMES_INPUT = [ "INPUT" , "SELECT" , "TEXTAREA" ] ;
211286 const TAG_NAME_TABLE_COLUMN = "TD" ;
212- const QUERYSELECTOR_FIRST_ROW_COLUMNS = "tbody tr:first-child td" ;
213287
214- let isLeft = e . keyCode == 37 ;
215- let isUp = e . keyCode == 38 ;
216- let isRight = e . keyCode == 39 ;
217- let isDown = e . keyCode == 40 ;
218- let isArrow = isLeft | isUp | isRight | isDown ;
288+ let isLeft = e . key === "ArrowLeft" || e . keyCode == 37 ;
289+ let isUp = e . key === "ArrowUp" || e . keyCode == 38 ;
290+ let isRight = e . key === "ArrowRight" || e . keyCode == 39 ;
291+ let isDown = e . key === "ArrowDown" || e . keyCode == 40 ;
292+ let isPageUp = e . key === "PageUp" || e . keyCode == 33 ;
293+ let isPageDown = e . key === "PageDown" || e . keyCode == 34 ;
294+ let isEnd = e . key === "End" || e . keyCode == 35 ;
295+ let isHome = e . key === "Home" || e . keyCode == 36 ;
296+ let isArrow = isLeft || isUp || isRight || isDown ;
297+ let isNavigationKey = isArrow || isPageUp || isPageDown || isHome || isEnd ;
219298 let isEnterKey = e . keyCode == 13 ;
220299 let isEscKey = e . keyCode == 27 ;
221300
222301 let focusedElement = document . activeElement ;
223- let allCells = element . querySelectorAll ( QUERYSELECTOR_ALL_COLUMNS ) ;
302+ let allCells = Array . from ( element . querySelectorAll ( QUERYSELECTOR_ALL_COLUMNS ) ) ;
303+ let navigableCells = getNavigableCells ( element ) ;
304+ let focusedCell = focusedElement ?. tagName === TAG_NAME_TABLE_COLUMN
305+ ? focusedElement
306+ : focusedElement ?. closest ( TAG_NAME_TABLE_COLUMN . toLowerCase ( ) ) ;
224307
225- if ( ! allCells || allCells . length == 0 ) {
308+ if ( ! allCells || allCells . length == 0 || ! navigableCells || navigableCells . length == 0 ) {
226309 return ;
227310 }
228311
229- let index = [ ] . indexOf . call ( allCells , focusedElement ) ;
312+ let index = focusedCell ? allCells . indexOf ( focusedCell ) : - 1 ;
230313 let isInputFocused = focusedElement && TAG_NAMES_INPUT . includes ( focusedElement . tagName ) ;
231314
232315 if ( isInputFocused && ( isEnterKey || isEscKey ) ) {
@@ -237,11 +320,9 @@ function KeyDownCellNavigation(e) {
237320 if ( ! inputStillExists ) {
238321
239322 let tdElement = findAncestorByTagName ( focusedElement , TAG_NAME_TABLE_COLUMN ) ;
240-
241- let index = [ ] . indexOf . call ( allCells , tdElement ) ;
242- let toFocus = element . querySelectorAll ( QUERYSELECTOR_ALL_COLUMNS ) [ index - 1 ] ;
243- if ( toFocus && toFocus . getAttribute ( "tabindex" ) == 0 ) {
244- toFocus . focus ( ) ;
323+ let tdIndex = tdElement ? allCells . indexOf ( tdElement ) : - 1 ;
324+ let toFocus = tdIndex > 0 ? allCells [ tdIndex - 1 ] : null ;
325+ if ( focusCell ( toFocus ) ) {
245326 return ;
246327 }
247328 }
@@ -250,69 +331,88 @@ function KeyDownCellNavigation(e) {
250331 } ) ;
251332 }
252333
334+ if ( isInputFocused ) {
335+ return ;
336+ }
337+
253338 if ( index == - 1 ) {
254- if ( isArrow && ! isInputFocused ) {
255- while ( index < allCells . length - 1 ) {
256- let toFocus = allCells [ index + 1 ] ;
257- if ( toFocus . getAttribute ( "tabindex" ) == 0 ) {
258- toFocus . focus ( ) ;
259- return ;
260- }
261- index += 1 ;
262- }
339+ if ( isNavigationKey ) {
340+ let initialCell = isEnd ? navigableCells [ navigableCells . length - 1 ] : navigableCells [ 0 ] ;
341+ focusCell ( initialCell ) ;
263342 }
264343 return ;
265344 }
266345
267- if ( isArrow ) {
346+ if ( isNavigationKey ) {
268347 e . preventDefault ( ) ;
269348 }
270349
350+ let currentRow = focusedCell ?. closest ( "tr" ) ;
351+ let rows = getBodyRows ( element ) . filter ( row => getRowCells ( row ) . some ( isFocusableCell ) ) ;
352+ let rowIndex = currentRow ? rows . indexOf ( currentRow ) : - 1 ;
353+ let rowCells = getRowCells ( currentRow ) ;
354+ let cellIndex = rowCells . indexOf ( focusedCell ) ;
355+ let navigableCellIndex = navigableCells . indexOf ( focusedCell ) ;
356+
357+ if ( rowIndex < 0 || cellIndex < 0 || navigableCellIndex < 0 ) {
358+ return ;
359+ }
271360
272361 if ( isLeft ) {
273- while ( index > 0 ) {
274- let toFocus = allCells [ index - 1 ] ;
275- if ( toFocus . getAttribute ( "tabindex" ) == 0 ) {
276- toFocus . focus ( ) ;
277- return ;
278- }
279- index -= 1 ;
280- }
362+ focusCell ( navigableCells [ navigableCellIndex - 1 ] ) ;
281363
282364 return ;
283365 }
284366
285367 if ( isUp ) {
286- let rowCount = element . querySelectorAll ( QUERYSELECTOR_FIRST_ROW_COLUMNS ) . length ;
287- let toFocus = allCells [ index - rowCount ] ;
288- if ( toFocus && toFocus . getAttribute ( "tabindex" ) == 0 ) {
289- toFocus . focus ( ) ;
290- }
368+ let targetRowIndex = Math . max ( 0 , rowIndex - 1 ) ;
369+ let toFocus = findClosestNavigableCell ( getRowCells ( rows [ targetRowIndex ] ) , cellIndex ) ;
370+ focusCell ( toFocus ) ;
291371 return ;
292372 }
293373
294374 if ( isRight ) {
295-
296- while ( index < allCells . length - 1 ) {
297- let toFocus = allCells [ index + 1 ] ;
298- if ( toFocus . getAttribute ( "tabindex" ) == 0 ) {
299- toFocus . focus ( ) ;
300- return ;
301- }
302- index += 1 ;
303- }
375+ focusCell ( navigableCells [ navigableCellIndex + 1 ] ) ;
304376
305377 return ;
306378 }
307379
308380 if ( isDown ) {
309- let rowCount = element . querySelectorAll ( QUERYSELECTOR_FIRST_ROW_COLUMNS ) . length ;
310- let toFocus = allCells [ index + rowCount ] ;
311- if ( toFocus && toFocus . getAttribute ( "tabindex" ) == 0 ) {
312- toFocus . focus ( ) ;
313- }
381+ let targetRowIndex = Math . min ( rows . length - 1 , rowIndex + 1 ) ;
382+ let toFocus = findClosestNavigableCell ( getRowCells ( rows [ targetRowIndex ] ) , cellIndex ) ;
383+ focusCell ( toFocus ) ;
314384 return ;
315385 }
316386
387+ if ( isPageUp ) {
388+ let pageJump = getPageJumpRowCount ( element , rows ) ;
389+ let targetRowIndex = Math . max ( 0 , rowIndex - pageJump ) ;
390+ let toFocus = findClosestNavigableCell ( getRowCells ( rows [ targetRowIndex ] ) , cellIndex ) ;
391+ focusCell ( toFocus ) ;
392+ return ;
393+ }
317394
395+ if ( isPageDown ) {
396+ let pageJump = getPageJumpRowCount ( element , rows ) ;
397+ let targetRowIndex = Math . min ( rows . length - 1 , rowIndex + pageJump ) ;
398+ let toFocus = findClosestNavigableCell ( getRowCells ( rows [ targetRowIndex ] ) , cellIndex ) ;
399+ focusCell ( toFocus ) ;
400+ return ;
401+ }
402+
403+ if ( isHome ) {
404+ let toFocus = e . ctrlKey || e . metaKey
405+ ? navigableCells [ 0 ]
406+ : rowCells . find ( isFocusableCell ) ;
407+ focusCell ( toFocus ) ;
408+ return ;
409+ }
410+
411+ if ( isEnd ) {
412+ let toFocus = e . ctrlKey || e . metaKey
413+ ? navigableCells [ navigableCells . length - 1 ]
414+ : [ ...rowCells ] . reverse ( ) . find ( isFocusableCell ) ;
415+ focusCell ( toFocus ) ;
416+ return ;
417+ }
318418}
0 commit comments