Skip to content

Commit 53ac25d

Browse files
committed
Deprecate onScrollKeyDown
remove pressable diff Remove JS handling for PageUp/Down, fix flow errors Add back "autoscroll to focused view" behavior remove commented code remove change to pressable Update documentation fix flow error fix lint issue Fix 'selectRowAtIndex' More simplification lock
1 parent 6b42baf commit 53ac25d

File tree

16 files changed

+201
-352
lines changed

16 files changed

+201
-352
lines changed

Libraries/Components/ScrollView/ScrollView.js

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1188,50 +1188,6 @@ class ScrollView extends React.Component<Props, State> {
11881188
}
11891189

11901190
// [TODO(macOS GH#774)
1191-
_handleKeyDown = (event: ScrollEvent) => {
1192-
if (this.props.onScrollKeyDown) {
1193-
this.props.onScrollKeyDown(event);
1194-
} else {
1195-
if (Platform.OS === 'macos') {
1196-
const nativeEvent = event.nativeEvent;
1197-
const key = nativeEvent.key;
1198-
const kMinScrollOffset = 10;
1199-
if (key === 'PAGE_UP') {
1200-
this._handleScrollByKeyDown(event, {
1201-
x: nativeEvent.contentOffset.x,
1202-
y:
1203-
nativeEvent.contentOffset.y +
1204-
-nativeEvent.layoutMeasurement.height,
1205-
});
1206-
} else if (key === 'PAGE_DOWN') {
1207-
this._handleScrollByKeyDown(event, {
1208-
x: nativeEvent.contentOffset.x,
1209-
y:
1210-
nativeEvent.contentOffset.y +
1211-
nativeEvent.layoutMeasurement.height,
1212-
});
1213-
} else if (key === 'HOME') {
1214-
this.scrollTo({x: 0, y: 0});
1215-
} else if (key === 'END') {
1216-
this.scrollToEnd({animated: true});
1217-
}
1218-
}
1219-
}
1220-
};
1221-
1222-
_handleScrollByKeyDown = (event: ScrollEvent, newOffset) => {
1223-
const maxX =
1224-
event.nativeEvent.contentSize.width -
1225-
event.nativeEvent.layoutMeasurement.width;
1226-
const maxY =
1227-
event.nativeEvent.contentSize.height -
1228-
event.nativeEvent.layoutMeasurement.height;
1229-
this.scrollTo({
1230-
x: Math.max(0, Math.min(maxX, newOffset.x)),
1231-
y: Math.max(0, Math.min(maxY, newOffset.y)),
1232-
});
1233-
};
1234-
12351191
_handlePreferredScrollerStyleDidChange = (event: ScrollEvent) => {
12361192
this.setState({contentKey: this.state.contentKey + 1});
12371193
}; // ]TODO(macOS GH#774)
@@ -1787,7 +1743,6 @@ class ScrollView extends React.Component<Props, State> {
17871743
// Override the onContentSizeChange from props, since this event can
17881744
// bubble up from TextInputs
17891745
onContentSizeChange: null,
1790-
onScrollKeyDown: this._handleKeyDown, // TODO(macOS GH#774)
17911746
onPreferredScrollerStyleDidChange:
17921747
this._handlePreferredScrollerStyleDidChange, // TODO(macOS GH#774)
17931748
onLayout: this._handleLayout,

Libraries/Components/ScrollView/__tests__/__snapshots__/ScrollView-test.js.snap

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ exports[`<ScrollView /> should render as expected: should deep render when not m
2727
onScroll={[Function]}
2828
onScrollBeginDrag={[Function]}
2929
onScrollEndDrag={[Function]}
30-
onScrollKeyDown={[Function]}
3130
onScrollShouldSetResponder={[Function]}
3231
onStartShouldSetResponder={[Function]}
3332
onStartShouldSetResponderCapture={[Function]}

Libraries/Components/View/ViewPropTypes.js

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,6 @@ type DirectEventProps = $ReadOnly<{|
7676
*/
7777
onPreferredScrollerStyleDidChange?: ?(event: ScrollEvent) => mixed, // TODO(macOS GH#774)
7878

79-
/**
80-
* When `focusable` is true, the system will try to invoke this function
81-
* when the user performs accessibility key down gesture.
82-
*/
83-
onScrollKeyDown?: ?(event: ScrollEvent) => mixed, // TODO(macOS GH#774)
84-
8579
/**
8680
* Invoked on mount and layout changes with:
8781
*

Libraries/Lists/VirtualizedList.js

Lines changed: 104 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -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)
3939
import {
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
@@ -576,23 +588,6 @@ class VirtualizedList extends React.PureComponent<Props, State> {
576588
}
577589

578590
// [TODO(macOS GH#774)
579-
ensureItemAtIndexIsVisible(rowIndex: number) {
580-
const frame = this._getFrameMetricsApprox(rowIndex);
581-
const visTop = this._scrollMetrics.offset;
582-
const visLen = this._scrollMetrics.visibleLength;
583-
const visEnd = visTop + visLen;
584-
const contentLength = this._scrollMetrics.contentLength;
585-
const frameEnd = frame.offset + frame.length;
586-
587-
if (frameEnd > visEnd) {
588-
const newOffset = Math.min(contentLength, visTop + (frameEnd - visEnd));
589-
this.scrollToOffset({offset: newOffset});
590-
} else if (frame.offset < visTop) {
591-
const newOffset = Math.min(frame.offset, visTop - frame.length);
592-
this.scrollToOffset({offset: newOffset});
593-
}
594-
}
595-
596591
selectRowAtIndex(rowIndex: number) {
597592
this._selectRowAtIndex(rowIndex);
598593
}
@@ -1309,14 +1304,16 @@ class VirtualizedList extends React.PureComponent<Props, State> {
13091304
}
13101305

13111306
_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-
}
1307+
// [TODO(macOS GH#774)
13181308
const preferredScrollerStyleDidChangeHandler =
1319-
this.props.onPreferredScrollerStyleDidChange; // ]TODO(macOS GH#774)
1309+
this.props.onPreferredScrollerStyleDidChange;
1310+
1311+
const keyboardNavigationProps = {
1312+
focusable: true,
1313+
validKeysDown: ['ArrowUp', 'ArrowDown', 'Home', 'End'],
1314+
onKeyDown: this._handleKeyDown,
1315+
};
1316+
// ]TODO(macOS GH#774)
13201317
const onRefresh = props.onRefresh;
13211318
if (this._isNestedWithSameOrientation()) {
13221319
// $FlowFixMe[prop-missing] - Typing ReactNativeComponent revealed errors
@@ -1333,8 +1330,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
13331330
<ScrollView
13341331
{...props}
13351332
// [TODO(macOS GH#774)
1336-
{...(props.enableSelectionOnKeyPress && {focusable: true})}
1337-
onScrollKeyDown={keyEventHandler}
1333+
{...(props.enableSelectionOnKeyPress && keyboardNavigationProps)}
13381334
onPreferredScrollerStyleDidChange={
13391335
preferredScrollerStyleDidChangeHandler
13401336
} // TODO(macOS GH#774)]
@@ -1356,8 +1352,8 @@ class VirtualizedList extends React.PureComponent<Props, State> {
13561352
// $FlowFixMe Invalid prop usage
13571353
<ScrollView
13581354
{...props}
1359-
{...(props.enableSelectionOnKeyPress && {focusable: true})} // [TODO(macOS GH#774)
1360-
onScrollKeyDown={keyEventHandler}
1355+
// [TODO(macOS GH#774)
1356+
{...(props.enableSelectionOnKeyPress && keyboardNavigationProps)}
13611357
onPreferredScrollerStyleDidChange={
13621358
preferredScrollerStyleDidChangeHandler
13631359
} // TODO(macOS GH#774)]
@@ -1510,99 +1506,32 @@ class VirtualizedList extends React.PureComponent<Props, State> {
15101506
};
15111507

15121508
// [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-
_selectRowAtIndex = rowIndex => {
1522-
this.setState(state => {
1523-
return {selectedRowIndex: rowIndex};
1524-
});
1525-
return rowIndex;
1526-
};
1509+
_ensureItemAtIndexIsVisible(rowIndex: number) {
1510+
const frame = this._getFrameMetricsApprox(rowIndex);
1511+
const visTop = this._scrollMetrics.offset;
1512+
const visLen = this._scrollMetrics.visibleLength;
1513+
const visEnd = visTop + visLen;
1514+
const contentLength = this._scrollMetrics.contentLength;
1515+
const frameEnd = frame.offset + frame.length;
15271516

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;
1517+
if (frameEnd > visEnd) {
1518+
const newOffset = Math.min(contentLength, visTop + (frameEnd - visEnd));
1519+
this.scrollToOffset({offset: newOffset});
1520+
} else if (frame.offset < visTop) {
1521+
const newOffset = Math.min(frame.offset, visTop - frame.length);
1522+
this.scrollToOffset({offset: newOffset});
15391523
}
1540-
};
1524+
}
15411525

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-
}
1526+
_selectRowAtIndex = rowIndex => {
1527+
const prevIndex = this.state.selectedRowIndex;
1528+
const newIndex = rowIndex;
15561529

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-
};
1530+
this.setState(state => {
1531+
return {selectedRowIndex: newIndex};
1532+
});
16031533

1604-
_handleSelectionChange = (prevIndex, newIndex) => {
1605-
this.ensureItemAtIndexIsVisible(newIndex);
1534+
this._ensureItemAtIndexIsVisible(newIndex);
16061535
if (prevIndex !== newIndex) {
16071536
const item = this.props.getItem(this.props.data, newIndex);
16081537
if (this.props.onSelectionChanged) {
@@ -1613,6 +1542,62 @@ class VirtualizedList extends React.PureComponent<Props, State> {
16131542
});
16141543
}
16151544
}
1545+
1546+
return newIndex;
1547+
};
1548+
1549+
_selectRowAboveIndex = rowIndex => {
1550+
const rowAbove = rowIndex > 0 ? rowIndex - 1 : rowIndex;
1551+
this._selectRowAtIndex(rowAbove);
1552+
};
1553+
1554+
_selectRowBelowIndex = rowIndex => {
1555+
const rowBelow = rowIndex < this.state.last ? rowIndex + 1 : rowIndex;
1556+
this._selectRowAtIndex(rowBelow);
1557+
};
1558+
1559+
_handleKeyDown = (event: KeyEvent) => {
1560+
if (Platform.OS === 'macos') {
1561+
this.props.onKeyDown?.(event);
1562+
if (event.defaultPrevented) {
1563+
return;
1564+
}
1565+
1566+
const nativeEvent = event.nativeEvent;
1567+
const key = nativeEvent.key;
1568+
1569+
let selectedIndex = -1;
1570+
if ('selectedRowIndex' in this.state) {
1571+
selectedIndex = this.state.selectedRowIndex;
1572+
}
1573+
1574+
if (key === 'ArrowUp') {
1575+
if (nativeEvent.altKey) {
1576+
// Option+Up selects the first element
1577+
this._selectRowAtIndex(0);
1578+
} else {
1579+
this._selectRowAboveIndex(selectedIndex);
1580+
}
1581+
} else if (key === 'ArrowDown') {
1582+
if (nativeEvent.altKey) {
1583+
// Option+Down selects the last element
1584+
this._selectRowAtIndex(this.state.last);
1585+
} else {
1586+
this._selectRowBelowIndex(selectedIndex);
1587+
}
1588+
} else if (key === 'Enter') {
1589+
if (this.props.onSelectionEntered) {
1590+
const item = this.props.getItem(this.props.data, selectedIndex);
1591+
if (this.props.onSelectionEntered) {
1592+
this.props.onSelectionEntered(item);
1593+
}
1594+
}
1595+
} else if (key === 'Home') {
1596+
this.scrollToOffset({animated: true, offset: 0});
1597+
} else if (key === 'End') {
1598+
this.scrollToEnd({animated: true});
1599+
}
1600+
}
16161601
};
16171602
// ]TODO(macOS GH#774)
16181603

0 commit comments

Comments
 (0)