diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 69a2735bd44ed..74b338633c1b1 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -6,6 +6,7 @@
- `ConfirmDialog`: Add support for custom label text on the confirmation and cancelation buttons ([#38994](https://github.com/WordPress/gutenberg/pull/38994))
- `InputControl`: Allow `onBlur` for empty values to commit the change when `isPressEnterToChange` is true, and move reset behavior to the ESCAPE key. ([#39109](https://github.com/WordPress/gutenberg/pull/39109)).
+- `TreeGrid`: Add tests for Home/End keyboard navigation. Add `onFocusRow` callback for Home/End keyboard navigation, this was missed in the implementation PR. Modify test for expanding/collapsing a row as row 1 implements this now. Update README with latest changes. ([#39302](https://github.com/WordPress/gutenberg/pull/39302))
### Bug Fix
diff --git a/packages/components/src/tree-grid/README.md b/packages/components/src/tree-grid/README.md
index 66264a0104b7f..45e140306b58a 100644
--- a/packages/components/src/tree-grid/README.md
+++ b/packages/components/src/tree-grid/README.md
@@ -114,7 +114,7 @@ Aside from the documented callback functions, any props specified will be passed
###### onFocusRow( event: Event, startRow: HTMLElement, destinationRow: HTMLElement )
-Callback that fires when focus is shifted from one row to another via the UP and DOWN keys.
+Callback that fires when focus is shifted from one row to another via the Up and Down keys. Callback is also fired on Home and End keys which move focus from the beginning row to the end row.
The callback is passed the event, the start row element that the focus was on originally, and
the destination row element after the focus has moved.
diff --git a/packages/components/src/tree-grid/index.js b/packages/components/src/tree-grid/index.js
index cf84160d060d0..2e5731cb068af 100644
--- a/packages/components/src/tree-grid/index.js
+++ b/packages/components/src/tree-grid/index.js
@@ -275,6 +275,10 @@ function TreeGrid(
);
focusablesInNextRow[ nextIndex ].focus();
+ // Let consumers know the row that was originally focused,
+ // and the row that is now in focus.
+ onFocusRow( event, activeRow, rows[ nextRowIndex ] );
+
// Prevent key use for anything else. This ensures Voiceover
// doesn't try to handle key navigation.
event.preventDefault();
diff --git a/packages/components/src/tree-grid/test/index.js b/packages/components/src/tree-grid/test/index.js
index b565ebdb4a57e..7f2193bba7910 100644
--- a/packages/components/src/tree-grid/test/index.js
+++ b/packages/components/src/tree-grid/test/index.js
@@ -6,7 +6,7 @@ import { fireEvent, render, screen } from '@testing-library/react';
/**
* WordPress dependencies
*/
-import { LEFT, RIGHT, UP, DOWN } from '@wordpress/keycodes';
+import { LEFT, RIGHT, UP, DOWN, HOME, END } from '@wordpress/keycodes';
import { forwardRef } from '@wordpress/element';
/**
@@ -68,10 +68,10 @@ describe( 'TreeGrid', () => {
level={ 1 }
positionInSet={ 1 }
setSize={ 3 }
- isExpanded={ true }
+ isExpanded={ false }
>
- Row 1
+ Row 1
{
level={ 1 }
positionInSet={ 3 }
setSize={ 3 }
- isExpanded={ true }
+ isExpanded={ false }
>
Row 3
@@ -97,16 +97,16 @@ describe( 'TreeGrid', () => {
);
- screen.getByText( 'Row 2' ).focus();
- const row2Element = screen.getByText( 'Row 2' ).closest( 'tr' );
+ screen.getByText( 'Row 1' ).focus();
+ const row1Element = screen.getByText( 'Row 1' ).closest( 'tr' );
- fireEvent.keyDown( screen.getByText( 'Row 2' ), {
+ fireEvent.keyDown( screen.getByText( 'Row 1' ), {
key: 'ArrowRight',
keyCode: RIGHT,
- currentTarget: row2Element,
+ currentTarget: row1Element,
} );
- expect( onExpandRow ).toHaveBeenCalledWith( row2Element );
+ expect( onExpandRow ).toHaveBeenCalledWith( row1Element );
} );
} );
@@ -120,17 +120,17 @@ describe( 'TreeGrid', () => {
level={ 1 }
positionInSet={ 1 }
setSize={ 3 }
- isExpanded={ false }
+ isExpanded={ true }
>
- Row 1
+ Row 1
Row 2
@@ -149,16 +149,16 @@ describe( 'TreeGrid', () => {
);
- screen.getByText( 'Row 2' ).focus();
- const row2Element = screen.getByText( 'Row 2' ).closest( 'tr' );
+ screen.getByText( 'Row 1' ).focus();
+ const row1Element = screen.getByText( 'Row 1' ).closest( 'tr' );
- fireEvent.keyDown( screen.getByText( 'Row 2' ), {
+ fireEvent.keyDown( screen.getByText( 'Row 1' ), {
key: 'ArrowLeft',
keyCode: LEFT,
- currentTarget: row2Element,
+ currentTarget: row1Element,
} );
- expect( onCollapseRow ).toHaveBeenCalledWith( row2Element );
+ expect( onCollapseRow ).toHaveBeenCalledWith( row1Element );
} );
} );
@@ -205,6 +205,28 @@ describe( 'TreeGrid', () => {
);
} );
+ it( 'should call onFocusRow with event, start and end nodes when pressing End', () => {
+ const onFocusRow = jest.fn();
+ render( );
+
+ screen.getByText( 'Row 1' ).focus();
+
+ const row1Element = screen.getByText( 'Row 1' ).closest( 'tr' );
+ const row3Element = screen.getByText( 'Row 3' ).closest( 'tr' );
+
+ fireEvent.keyDown( screen.getByText( 'Row 1' ), {
+ key: 'End',
+ keyCode: END,
+ currentTarget: row1Element,
+ } );
+
+ expect( onFocusRow ).toHaveBeenCalledWith(
+ expect.objectContaining( { key: 'End', keyCode: END } ),
+ row1Element,
+ row3Element
+ );
+ } );
+
it( 'should call onFocusRow with event, start and end nodes when pressing Up Arrow', () => {
const onFocusRow = jest.fn();
render( );
@@ -227,6 +249,28 @@ describe( 'TreeGrid', () => {
);
} );
+ it( 'should call onFocusRow with event, start and end nodes when pressing Home', () => {
+ const onFocusRow = jest.fn();
+ render( );
+
+ screen.getByText( 'Row 3' ).focus();
+
+ const row3Element = screen.getByText( 'Row 3' ).closest( 'tr' );
+ const row1Element = screen.getByText( 'Row 1' ).closest( 'tr' );
+
+ fireEvent.keyDown( screen.getByText( 'Row 3' ), {
+ key: 'Home',
+ keyCode: HOME,
+ currentTarget: row3Element,
+ } );
+
+ expect( onFocusRow ).toHaveBeenCalledWith(
+ expect.objectContaining( { key: 'Home', keyCode: HOME } ),
+ row3Element,
+ row1Element
+ );
+ } );
+
it( 'should call onFocusRow when shift key is held', () => {
const onFocusRow = jest.fn();
render( );