Skip to content

Commit

Permalink
Allow reverse-order lists
Browse files Browse the repository at this point in the history
Summary:
We have a special case for treating horizontal RTL lists differently. This change adds similar functionality for capturing the correct virtualized list cell metrics when a list items children are rendered in reverse order, e.g., with `flexDirection: 'column-reverse'`.

## Changelog

[General][Fixed] Fixed FlatList to support reverse ordered items

Differential Revision: D64575365
  • Loading branch information
rozele authored and facebook-github-bot committed Oct 22, 2024
1 parent 3dfe22b commit 3be01bd
Show file tree
Hide file tree
Showing 3 changed files with 325 additions and 37 deletions.
18 changes: 14 additions & 4 deletions packages/virtualized-lists/Lists/ListMetricsAggregator.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export type CellMetrics = {
// based implementation instead of transform.
export type ListOrientation = {
horizontal: boolean,
reversed: boolean,
rtl: boolean,
};

Expand Down Expand Up @@ -66,6 +67,7 @@ export default class ListMetricsAggregator {
_orientation: ListOrientation = {
horizontal: false,
rtl: false,
reversed: false,
};

/**
Expand Down Expand Up @@ -265,9 +267,9 @@ export default class ListMetricsAggregator {
* right in RTL) from a layout box.
*/
flowRelativeOffset(layout: Layout, referenceContentLength?: ?number): number {
const {horizontal, rtl} = this._orientation;
const {horizontal, reversed, rtl} = this._orientation;

if (horizontal && rtl) {
if ((horizontal && rtl) || reversed) {
const contentLength = referenceContentLength ?? this._contentLength;
invariant(
contentLength != null,
Expand All @@ -286,9 +288,9 @@ export default class ListMetricsAggregator {
* Converts a flow-relative offset to a cartesian offset
*/
cartesianOffset(flowRelativeOffset: number): number {
const {horizontal, rtl} = this._orientation;
const {horizontal, reversed, rtl} = this._orientation;

if (horizontal && rtl) {
if ((horizontal && rtl) || reversed) {
invariant(
this._contentLength != null,
'ListMetricsAggregator must be notified of list content layout before resolving offsets',
Expand All @@ -311,6 +313,14 @@ export default class ListMetricsAggregator {
this._measuredCellsCount = 0;
}

if (orientation.reversed !== this._orientation.reversed) {
this._cellMetrics.clear();
this._averageCellLength = 0;
this._highestMeasuredCellIndex = 0;
this._measuredCellsLength = 0;
this._measuredCellsCount = 0;
}

this._orientation = orientation;
}

Expand Down
29 changes: 21 additions & 8 deletions packages/virtualized-lists/Lists/VirtualizedList.js
Original file line number Diff line number Diff line change
Expand Up @@ -252,10 +252,14 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
return;
}

const {horizontal, rtl} = this._orientation();
if (horizontal && rtl && !this._listMetrics.hasContentLength()) {
const {horizontal, reversed, rtl} = this._orientation();
if (
((horizontal && rtl) || reversed) &&
!this._listMetrics.hasContentLength()
) {
const mode = horizontal && rtl ? 'RTL' : 'reversed lists';
console.warn(
'scrollToOffset may not be called in RTL before content is laid out',
`scrollToOffset may not be called in ${mode} before content is laid out`,
);
return;
}
Expand All @@ -267,8 +271,8 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
}

_scrollToParamsFromOffset(offset: number): {x?: number, y?: number} {
const {horizontal, rtl} = this._orientation();
if (horizontal && rtl) {
const {horizontal, reversed, rtl} = this._orientation();
if ((horizontal && rtl) || reversed) {
// Add the visible length of the scrollview so that the offset is right-aligned
const cartOffset = this._listMetrics.cartesianOffset(
offset + this._scrollMetrics.visibleLength,
Expand Down Expand Up @@ -1489,8 +1493,17 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
}

_orientation(): ListOrientation {
const horizontal = horizontalOrDefault(this.props.horizontal);
const contentFlexDirection = StyleSheet.flatten(
this.props.contentContainerStyle,
)?.flexDirection;
const reversed =
(horizontal && contentFlexDirection === 'row-reverse') ||
contentFlexDirection === 'column-reverse';

return {
horizontal: horizontalOrDefault(this.props.horizontal),
horizontal,
reversed,
rtl: I18nManager.isRTL,
};
}
Expand Down Expand Up @@ -1731,8 +1744,8 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {

_offsetFromScrollEvent(e: ScrollEvent): number {
const {contentOffset, contentSize, layoutMeasurement} = e.nativeEvent;
const {horizontal, rtl} = this._orientation();
if (horizontal && rtl) {
const {horizontal, reversed, rtl} = this._orientation();
if ((horizontal && rtl) || reversed) {
return (
this._selectLength(contentSize) -
(this._selectOffset(contentOffset) +
Expand Down
Loading

0 comments on commit 3be01bd

Please sign in to comment.