@@ -35,7 +35,7 @@ import type {
35
35
ViewToken ,
36
36
ViewabilityConfigCallbackPair ,
37
37
} from './ViewabilityHelper' ;
38
- import type { ScrollEvent } from '../Types/CoreEventTypes' ; // TODO(macOS GH#774)
38
+ import type { KeyEvent } from '../Types/CoreEventTypes' ; // TODO(macOS GH#774)
39
39
import {
40
40
VirtualizedListCellContextProvider ,
41
41
VirtualizedListContext ,
@@ -109,12 +109,24 @@ type OptionalProps = {|
109
109
* this for debugging purposes. Defaults to false.
110
110
*/
111
111
disableVirtualization ?: ?boolean ,
112
+ // [TODO(macOS GH#774)
112
113
/**
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
114
125
*
115
126
* @platform macos
116
127
*/
117
- enableSelectionOnKeyPress ?: ?boolean , // TODO(macOS GH#774)
128
+ enableSelectionOnKeyPress ?: ?boolean ,
129
+ // ]TODO(macOS GH#774)
118
130
/**
119
131
* A marker property for telling the list to re-render (since it implements `PureComponent`). If
120
132
* any of your `renderItem`, Header, Footer, etc. functions depend on anything outside of the
@@ -145,6 +157,12 @@ type OptionalProps = {|
145
157
* `getItemLayout` to be implemented.
146
158
*/
147
159
initialScrollIndex ?: ?number ,
160
+ // [TODO(macOS GH#774)
161
+ /**
162
+ * The initially selected row, if `enableSelectionOnKeyPress` is set.
163
+ */
164
+ initialSelectedIndex ?: ?number ,
165
+ // ]TODO(macOS GH#774)
148
166
/**
149
167
* Reverses the direction of scroll. Uses scale transforms of -1.
150
168
*/
@@ -782,7 +800,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
782
800
( this . props . initialScrollIndex || 0 ) +
783
801
initialNumToRenderOrDefault ( this . props . initialNumToRender ) ,
784
802
) - 1 ,
785
- selectedRowIndex : 0 , // TODO(macOS GH#774)
803
+ selectedRowIndex : this . props . initialSelectedIndex || - 1 , // TODO(macOS GH#774)
786
804
} ;
787
805
788
806
if ( this . _isNestedWithSameOrientation ( ) ) {
@@ -845,7 +863,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
845
863
) ,
846
864
last : Math . max ( 0 , Math . min ( prevState . last , getItemCount ( data ) - 1 ) ) ,
847
865
selectedRowIndex : Math . max (
848
- 0 ,
866
+ - 1 , // Used to indicate no row is selected
849
867
Math . min ( prevState . selectedRowIndex , getItemCount ( data ) ) ,
850
868
) , // TODO(macOS GH#774)
851
869
} ;
@@ -1309,14 +1327,16 @@ class VirtualizedList extends React.PureComponent<Props, State> {
1309
1327
}
1310
1328
1311
1329
_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
- }
1330
+ // [TODO(macOS GH#774)
1318
1331
const preferredScrollerStyleDidChangeHandler =
1319
- this . props . onPreferredScrollerStyleDidChange ; // ]TODO(macOS GH#774)
1332
+ this . props . onPreferredScrollerStyleDidChange ;
1333
+
1334
+ const keyboardNavigationProps = {
1335
+ focusable : true ,
1336
+ validKeysDown : [ 'ArrowUp' , 'ArrowDown' , 'Home' , 'End' ] ,
1337
+ onKeyDown : this . _handleKeyDown ,
1338
+ } ;
1339
+ // ]TODO(macOS GH#774)
1320
1340
const onRefresh = props . onRefresh ;
1321
1341
if ( this . _isNestedWithSameOrientation ( ) ) {
1322
1342
// $FlowFixMe[prop-missing] - Typing ReactNativeComponent revealed errors
@@ -1333,8 +1353,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
1333
1353
< ScrollView
1334
1354
{ ...props }
1335
1355
// [TODO(macOS GH#774)
1336
- { ...( props . enableSelectionOnKeyPress && { focusable : true } ) }
1337
- onScrollKeyDown = { keyEventHandler }
1356
+ { ...( props . enableSelectionOnKeyPress && keyboardNavigationProps ) }
1338
1357
onPreferredScrollerStyleDidChange = {
1339
1358
preferredScrollerStyleDidChangeHandler
1340
1359
} // TODO(macOS GH#774)]
@@ -1356,8 +1375,8 @@ class VirtualizedList extends React.PureComponent<Props, State> {
1356
1375
// $FlowFixMe Invalid prop usage
1357
1376
< ScrollView
1358
1377
{ ...props }
1359
- { ... ( props . enableSelectionOnKeyPress && { focusable : true } ) } // [TODO(macOS GH#774)
1360
- onScrollKeyDown = { keyEventHandler }
1378
+ // [TODO(macOS GH#774)
1379
+ { ... ( props . enableSelectionOnKeyPress && keyboardNavigationProps ) }
1361
1380
onPreferredScrollerStyleDidChange = {
1362
1381
preferredScrollerStyleDidChangeHandler
1363
1382
} // TODO(macOS GH#774)]
@@ -1510,98 +1529,11 @@ class VirtualizedList extends React.PureComponent<Props, State> {
1510
1529
} ;
1511
1530
1512
1531
// [TODO(macOS GH#774)
1513
- _selectRowAboveIndex = rowIndex => {
1514
- const rowAbove = rowIndex > 0 ? rowIndex - 1 : rowIndex ;
1515
- this . setState ( state => {
1516
- return { selectedRowIndex : rowAbove } ;
1517
- } ) ;
1518
- return rowAbove ;
1519
- } ;
1520
-
1521
1532
_selectRowAtIndex = rowIndex => {
1522
- this . setState ( state => {
1523
- return { selectedRowIndex : rowIndex } ;
1524
- } ) ;
1525
- return rowIndex ;
1526
- } ;
1527
-
1528
- _selectRowBelowIndex = rowIndex => {
1529
- if ( this . props . getItemCount ) {
1530
- const { data} = this . props ;
1531
- const itemCount = this . props . getItemCount ( data ) ;
1532
- const rowBelow = rowIndex < itemCount - 1 ? rowIndex + 1 : rowIndex ;
1533
- this . setState ( state => {
1534
- return { selectedRowIndex : rowBelow } ;
1535
- } ) ;
1536
- return rowBelow ;
1537
- } else {
1538
- return rowIndex ;
1539
- }
1540
- } ;
1541
-
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
- }
1556
-
1557
- // const {data, getItem} = this.props;
1558
- if ( key === 'UP_ARROW' ) {
1559
- newIndex = this . _selectRowAboveIndex ( prevIndex ) ;
1560
- this . _handleSelectionChange ( prevIndex , newIndex ) ;
1561
- } else if ( key = = = 'DOWN_ARROW' ) {
1562
- newIndex = this . _selectRowBelowIndex ( prevIndex ) ;
1563
- this . _handleSelectionChange ( prevIndex , newIndex ) ;
1564
- } else if ( key = = = 'ENTER' ) {
1565
- 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
- }
1570
- }
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 } ) ;
1599
- }
1600
- }
1601
- }
1602
- } ;
1533
+ const prevIndex = this . state . selectedRowIndex ;
1534
+ const newIndex = rowIndex ;
1535
+ this . setState ( { selectedRowIndex : newIndex } ) ;
1603
1536
1604
- _handleSelectionChange = ( prevIndex , newIndex ) => {
1605
1537
this . ensureItemAtIndexIsVisible ( newIndex ) ;
1606
1538
if ( prevIndex !== newIndex ) {
1607
1539
const item = this . props . getItem ( this . props . data , newIndex ) ;
@@ -1613,6 +1545,62 @@ class VirtualizedList extends React.PureComponent<Props, State> {
1613
1545
} ) ;
1614
1546
}
1615
1547
}
1548
+
1549
+ return newIndex ;
1550
+ } ;
1551
+
1552
+ _selectRowAboveIndex = rowIndex => {
1553
+ const rowAbove = rowIndex > 0 ? rowIndex - 1 : rowIndex ;
1554
+ this . _selectRowAtIndex ( rowAbove ) ;
1555
+ } ;
1556
+
1557
+ _selectRowBelowIndex = rowIndex => {
1558
+ const rowBelow = rowIndex < this . state . last ? rowIndex + 1 : rowIndex ;
1559
+ this . _selectRowAtIndex ( rowBelow ) ;
1560
+ } ;
1561
+
1562
+ _handleKeyDown = ( event : KeyEvent ) => {
1563
+ if ( Platform . OS === 'macos' ) {
1564
+ this . props . onKeyDown ?. ( event ) ;
1565
+ if ( event . defaultPrevented ) {
1566
+ return ;
1567
+ }
1568
+
1569
+ const nativeEvent = event . nativeEvent ;
1570
+ const key = nativeEvent . key ;
1571
+
1572
+ let selectedIndex = - 1 ;
1573
+ if ( this . state . selectedRowIndex > = 0) {
1574
+ selectedIndex = this . state . selectedRowIndex ;
1575
+ }
1576
+
1577
+ if (key === 'ArrowUp') {
1578
+ if ( nativeEvent . altKey ) {
1579
+ // Option+Up selects the first element
1580
+ this . _selectRowAtIndex ( 0 ) ;
1581
+ } else {
1582
+ this . _selectRowAboveIndex ( selectedIndex ) ;
1583
+ }
1584
+ } else if ( key === 'ArrowDown ') {
1585
+ if ( nativeEvent . altKey ) {
1586
+ // Option+Down selects the last element
1587
+ this . _selectRowAtIndex ( this . state . last ) ;
1588
+ } else {
1589
+ this . _selectRowBelowIndex ( selectedIndex ) ;
1590
+ }
1591
+ } else if ( key === 'Enter ') {
1592
+ if ( this . props . onSelectionEntered ) {
1593
+ const item = this . props . getItem ( this . props . data , selectedIndex ) ;
1594
+ if ( this . props . onSelectionEntered ) {
1595
+ this . props . onSelectionEntered ( item ) ;
1596
+ }
1597
+ }
1598
+ } else if ( key === 'Home ') {
1599
+ this . scrollToOffset ( { animated : true , offset : 0 } ) ;
1600
+ } else if (key === 'End') {
1601
+ this . scrollToEnd ( { animated : true } ) ;
1602
+ }
1603
+ }
1616
1604
} ;
1617
1605
// ]TODO(macOS GH#774)
1618
1606
0 commit comments