@@ -35,7 +35,7 @@ import type {
3535 ViewToken ,
3636 ViewabilityConfigCallbackPair ,
3737} from './ViewabilityHelper' ;
38- import type { ScrollEvent } from '../Types/CoreEventTypes' ; // TODO(macOS GH#774)
38+ import type { KeyEvent } from '../Types/CoreEventTypes' ; // TODO(macOS GH#774)
3939import {
4040 VirtualizedListCellContextProvider ,
4141 VirtualizedListContext ,
@@ -109,12 +109,24 @@ type OptionalProps = {|
109109 * this for debugging purposes. Defaults to false.
110110 */
111111 disableVirtualization ?: ?boolean ,
112+ // [TODO(macOS GH#774)
112113 /**
113- * Handles key down events and updates selection based on the key event
114+ * Allows you to 'select' a row using arrow keys. The selected row will have the prop `isSelected`
115+ * passed in as true to it's renderItem / ListItemComponent. You can also imperatively select a row
116+ * using the `selectRowAtIndex` method. You can set the initially selected row using the
117+ * `initialSelectedIndex` prop.
118+ * Keyboard Behavior:
119+ * - ArrowUp: Select row above current selected row
120+ * - ArrowDown: Select row below current selected row
121+ * - Option+ArrowUp: Select the first row
122+ * - Opton+ArrowDown: Select the last 'realized' row
123+ * - Home: Scroll to top of list
124+ * - End: Scroll to end of list
114125 *
115126 * @platform macos
116127 */
117- enableSelectionOnKeyPress ?: ?boolean , // TODO(macOS GH#774)
128+ enableSelectionOnKeyPress ?: ?boolean ,
129+ // ]TODO(macOS GH#774)
118130 /**
119131 * A marker property for telling the list to re-render (since it implements `PureComponent`). If
120132 * any of your `renderItem`, Header, Footer, etc. functions depend on anything outside of the
@@ -1309,14 +1321,16 @@ class VirtualizedList extends React.PureComponent<Props, State> {
13091321 }
13101322
13111323 _defaultRenderScrollComponent = props => {
1312- let keyEventHandler = this . props . onScrollKeyDown ; // [TODO(macOS GH#774)
1313- if ( ! keyEventHandler ) {
1314- keyEventHandler = this . props . enableSelectionOnKeyPress
1315- ? this . _handleKeyDown
1316- : null ;
1317- }
1324+ // [TODO(macOS GH#774)
13181325 const preferredScrollerStyleDidChangeHandler =
1319- this . props . onPreferredScrollerStyleDidChange ; // ]TODO(macOS GH#774)
1326+ this . props . onPreferredScrollerStyleDidChange ;
1327+
1328+ const keyboardNavigationProps = {
1329+ focusable : true ,
1330+ validKeysDown : [ 'ArrowUp' , 'ArrowDown' , 'Home' , 'End' ] ,
1331+ onKeyDown : this . _handleKeyDown ,
1332+ } ;
1333+ // ]TODO(macOS GH#774)
13201334 const onRefresh = props . onRefresh ;
13211335 if ( this . _isNestedWithSameOrientation ( ) ) {
13221336 // $FlowFixMe[prop-missing] - Typing ReactNativeComponent revealed errors
@@ -1333,8 +1347,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
13331347 < ScrollView
13341348 { ...props }
13351349 // [TODO(macOS GH#774)
1336- { ...( props . enableSelectionOnKeyPress && { focusable : true } ) }
1337- onScrollKeyDown = { keyEventHandler }
1350+ { ...( props . enableSelectionOnKeyPress && keyboardNavigationProps ) }
13381351 onPreferredScrollerStyleDidChange = {
13391352 preferredScrollerStyleDidChangeHandler
13401353 } // TODO(macOS GH#774)]
@@ -1356,8 +1369,8 @@ class VirtualizedList extends React.PureComponent<Props, State> {
13561369 // $FlowFixMe Invalid prop usage
13571370 < ScrollView
13581371 { ...props }
1359- { ... ( props . enableSelectionOnKeyPress && { focusable : true } ) } // [TODO(macOS GH#774)
1360- onScrollKeyDown = { keyEventHandler }
1372+ // [TODO(macOS GH#774)
1373+ { ... ( props . enableSelectionOnKeyPress && keyboardNavigationProps ) }
13611374 onPreferredScrollerStyleDidChange = {
13621375 preferredScrollerStyleDidChangeHandler
13631376 } // TODO(macOS GH#774)]
@@ -1539,64 +1552,50 @@ class VirtualizedList extends React.PureComponent<Props, State> {
15391552 }
15401553 } ;
15411554
1542- _handleKeyDown = ( event : ScrollEvent ) => {
1543- if ( this . props . onScrollKeyDown ) {
1544- this . props . onScrollKeyDown ( event ) ;
1545- } else {
1546- if ( Platform . OS === 'macos' ) {
1547- // $FlowFixMe Cannot get e.nativeEvent because property nativeEvent is missing in Event
1548- const nativeEvent = event . nativeEvent ;
1549- const key = nativeEvent . key ;
1550-
1551- let prevIndex = - 1 ;
1552- let newIndex = - 1 ;
1553- if ( 'selectedRowIndex' in this . state ) {
1554- prevIndex = this . state . selectedRowIndex ;
1555- }
1555+ _handleKeyDown = ( event : KeyEvent ) => {
1556+ if ( Platform . OS === 'macos' ) {
1557+ this . props . onKeyDown ?. ( event ) ;
1558+ if ( event . defaultPrevented ) {
1559+ return ;
1560+ }
1561+
1562+ const nativeEvent = event . nativeEvent ;
1563+ const key = nativeEvent . key ;
15561564
1557- // const {data, getItem} = this.props;
1558- if ( key === 'UP_ARROW' ) {
1565+ let prevIndex = - 1 ;
1566+ let newIndex = - 1 ;
1567+ if ( 'selectedRowIndex' in this . state ) {
1568+ prevIndex = this . state . selectedRowIndex ;
1569+ }
1570+
1571+ // const {data, getItem} = this.props;
1572+ if ( key = = = 'ArrowUp' ) {
1573+ if ( nativeEvent . altKey ) {
1574+ // Option+Up selects the first element
1575+ newIndex = this . _selectRowAtIndex ( 0 ) ;
1576+ } else {
15591577 newIndex = this . _selectRowAboveIndex ( prevIndex ) ;
1560- this . _handleSelectionChange ( prevIndex , newIndex ) ;
1561- } else if ( key = = = 'DOWN_ARROW' ) {
1578+ }
1579+ this . _handleSelectionChange ( prevIndex , newIndex ) ;
1580+ } else if ( key = = = 'ArrowDown' ) {
1581+ if ( nativeEvent . altKey ) {
1582+ // Option+Down selects the last element
1583+ newIndex = this . _selectRowAtIndex ( this . state . last ) ;
1584+ } else {
15621585 newIndex = this . _selectRowBelowIndex ( prevIndex ) ;
1563- this . _handleSelectionChange ( prevIndex , newIndex ) ;
1564- } else if ( key = = = 'ENTER' ) {
1586+ }
1587+ this . _handleSelectionChange ( prevIndex , newIndex ) ;
1588+ } else if ( key = = = 'Enter' ) {
1589+ if ( this . props . onSelectionEntered ) {
1590+ const item = this . props . getItem ( this . props . data , prevIndex ) ;
15651591 if ( this . props . onSelectionEntered ) {
1566- const item = this . props . getItem ( this . props . data , prevIndex ) ;
1567- if ( this . props . onSelectionEntered ) {
1568- this . props . onSelectionEntered ( item ) ;
1569- }
1592+ this . props . onSelectionEntered ( item ) ;
15701593 }
1571- } else if ( key = = = 'OPTION_UP' ) {
1572- newIndex = this . _selectRowAtIndex ( 0 ) ;
1573- this . _handleSelectionChange ( prevIndex , newIndex ) ;
1574- } else if ( key = = = 'OPTION_DOWN' ) {
1575- newIndex = this . _selectRowAtIndex ( this . state . last ) ;
1576- this . _handleSelectionChange ( prevIndex , newIndex ) ;
1577- } else if ( key = = = 'PAGE_UP' ) {
1578- const maxY =
1579- event . nativeEvent . contentSize . height -
1580- event . nativeEvent . layoutMeasurement . height ;
1581- const newOffset = Math . min (
1582- maxY ,
1583- nativeEvent . contentOffset . y + - nativeEvent . layoutMeasurement . height ,
1584- ) ;
1585- this . scrollToOffset ( { animated : true , offset : newOffset } ) ;
1586- } else if ( key = = = 'PAGE_DOWN' ) {
1587- const maxY =
1588- event . nativeEvent . contentSize . height -
1589- event . nativeEvent . layoutMeasurement . height ;
1590- const newOffset = Math . min (
1591- maxY ,
1592- nativeEvent . contentOffset . y + nativeEvent . layoutMeasurement . height ,
1593- ) ;
1594- this . scrollToOffset ( { animated : true , offset : newOffset } ) ;
1595- } else if ( key = = = 'HOME' ) {
1596- this . scrollToOffset ( { animated : true , offset : 0 } ) ;
1597- } else if ( key = = = 'END' ) {
1598- this . scrollToEnd ( { animated : true } ) ;
15991594 }
1595+ } else if ( key = = = 'Home' ) {
1596+ this . scrollToOffset ( { animated : true , offset : 0 } ) ;
1597+ } else if ( key = = = 'End' ) {
1598+ this . scrollToEnd ( { animated : true } ) ;
16001599 }
16011600 }
16021601 } ;
0 commit comments