Update and reorganize React Native vendor code

This commit is contained in:
Nicolas Gallagher
2018-05-21 22:21:21 -07:00
parent 2d83ffbd6b
commit 45975d3a1e
54 changed files with 585 additions and 380 deletions

View File

@@ -7,7 +7,7 @@
* @flow
*/
import AnimatedImplementation from '../../vendor/Animated/AnimatedImplementation';
import AnimatedImplementation from '../../vendor/react-native/Animated/AnimatedImplementation';
import Image from '../Image';
import ScrollView from '../ScrollView';
import Text from '../Text';

View File

@@ -7,5 +7,5 @@
* @flow
*/
import Easing from '../../vendor/Animated/Easing';
import Easing from '../../vendor/react-native/Animated/Easing';
export default Easing;

View File

@@ -1,2 +1,11 @@
/**
* Copyright (c) 2016-present, Nicolas Gallagher.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import UnimplementedView from '../../modules/UnimplementedView';
export default UnimplementedView;

View File

@@ -1,2 +1,2 @@
import PanResponder from '../../vendor/PanResponder';
import PanResponder from '../../vendor/react-native/PanResponder';
export default PanResponder;

View File

@@ -1,2 +1,11 @@
/**
* Copyright (c) 2016-present, Nicolas Gallagher.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import UnimplementedView from '../../modules/UnimplementedView';
export default UnimplementedView;

View File

@@ -7,7 +7,7 @@
* @flow
*/
import PooledClass from '../../vendor/PooledClass';
import PooledClass from '../../vendor/react-native/PooledClass';
const twoArgumentPooler = PooledClass.twoArgumentPooler;

View File

@@ -7,7 +7,7 @@
* @noflow
*/
import PooledClass from '../../vendor/PooledClass';
import PooledClass from '../../vendor/react-native/PooledClass';
const twoArgumentPooler = PooledClass.twoArgumentPooler;

View File

@@ -7,7 +7,7 @@
* @noflow
*/
import setValueForStyles from '../../vendor/setValueForStyles';
import setValueForStyles from '../../vendor/react-dom/setValueForStyles';
const getRect = node => {
const height = node.offsetHeight;

View File

@@ -1,2 +1,11 @@
import VirtualizedList from '../../vendor/VirtualizedList';
/**
* Copyright (c) 2016-present, Nicolas Gallagher.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import VirtualizedList from '../../vendor/react-native/VirtualizedList';
export default VirtualizedList;

View File

@@ -1 +0,0 @@
facebook/react-native@19a4a7d3cb6d00780ccbbbd7b0062896f64ab24d

View File

@@ -1 +0,0 @@
facebook/react-native@19a4a7d3cb6d00780ccbbbd7b0062896f64ab24d

View File

@@ -1 +0,0 @@
facebook/react-native@71006f74cdafdae7212c8a10603fb972c6ee338c

View File

@@ -1 +0,0 @@
facebook/react-native@19a4a7d3cb6d00780ccbbbd7b0062896f64ab24d

View File

@@ -1 +0,0 @@
facebook/react-native@19a4a7d3cb6d00780ccbbbd7b0062896f64ab24d

View File

@@ -1 +0,0 @@
facebook/react-native@19a4a7d3cb6d00780ccbbbd7b0062896f64ab24d

View File

@@ -9,7 +9,7 @@
* From React 16.0.0
*/
import isUnitlessNumber from '../../modules/unitlessNumbers';
import isUnitlessNumber from '../../../modules/unitlessNumbers';
/**
* Convert a value into the proper css writable value. The style name `name`

View File

@@ -11,7 +11,7 @@
import AnimatedValue from './nodes/AnimatedValue';
import NativeAnimatedHelper from './NativeAnimatedHelper';
import findNodeHandle from '../../exports/findNodeHandle';
import findNodeHandle from '../../../exports/findNodeHandle';
import invariant from 'fbjs/lib/invariant';
const { shouldUseNativeDriver } = NativeAnimatedHelper;

View File

@@ -10,8 +10,8 @@
'use strict';
import invariant from 'fbjs/lib/invariant';
import NativeModules from '../../exports/NativeModules';
import NativeEventEmitter from '../../modules/NativeEventEmitter';
import NativeModules from '../../../exports/NativeModules';
import NativeEventEmitter from '../../../modules/NativeEventEmitter';
import type { AnimationConfig } from './animations/Animation';
import type { EventConfig } from './AnimatedEvent';

View File

@@ -12,7 +12,7 @@
import { AnimatedEvent } from './AnimatedEvent';
import AnimatedProps from './nodes/AnimatedProps';
import React from 'react';
import ViewStylePropTypes from '../../exports/View/ViewStylePropTypes';
import ViewStylePropTypes from '../../../exports/View/ViewStylePropTypes';
function createAnimatedComponent(Component: any): any {
class AnimatedComponent extends React.Component<Object> {

View File

@@ -13,7 +13,7 @@ import { AnimatedEvent } from '../AnimatedEvent';
import AnimatedNode from './AnimatedNode';
import AnimatedStyle from './AnimatedStyle';
import NativeAnimatedHelper from '../NativeAnimatedHelper';
import findNodeHandle from '../../../exports/findNodeHandle';
import findNodeHandle from '../../../../exports/findNodeHandle';
import invariant from 'fbjs/lib/invariant';

View File

@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @noflow
* @flow
* @format
*/
'use strict';
@@ -13,7 +13,7 @@ import AnimatedNode from './AnimatedNode';
import AnimatedTransform from './AnimatedTransform';
import AnimatedWithChildren from './AnimatedWithChildren';
import NativeAnimatedHelper from '../NativeAnimatedHelper';
import StyleSheet from '../../../exports/StyleSheet';
import StyleSheet from '../../../../exports/StyleSheet';
const flattenStyle = StyleSheet.flatten;
@@ -33,7 +33,7 @@ class AnimatedStyle extends AnimatedWithChildren {
}
// Recursively get values for nested styles (like iOS's shadowOffset)
_walkStyleAndGetValues(style) {
_walkStyleAndGetValues(style: Object) {
const updatedStyle = {};
for (const key in style) {
const value = style[key];
@@ -58,7 +58,7 @@ class AnimatedStyle extends AnimatedWithChildren {
}
// Recursively get animated values for nested styles (like iOS's shadowOffset)
_walkStyleAndGetAnimatedValues(style) {
_walkStyleAndGetAnimatedValues(style: Object) {
const updatedStyle = {};
for (const key in style) {
const value = style[key];

View File

@@ -12,7 +12,7 @@
import AnimatedInterpolation from './AnimatedInterpolation';
import AnimatedNode from './AnimatedNode';
import AnimatedWithChildren from './AnimatedWithChildren';
import InteractionManager from '../../../exports/InteractionManager';
import InteractionManager from '../../../../exports/InteractionManager';
import NativeAnimatedHelper from '../NativeAnimatedHelper';
import type Animation, { EndCallback } from '../animations/Animation';

View File

@@ -7,7 +7,7 @@
* @flow
*/
import InteractionManager from '../../exports/InteractionManager';
import InteractionManager from '../../../exports/InteractionManager';
/**
* A simple class for batching up invocations of a low-pri callback. A timeout is set to run the
@@ -34,7 +34,7 @@ import InteractionManager from '../../exports/InteractionManager';
class Batchinator {
_callback: () => void;
_delay: number;
_taskHandle: ?{ cancel: () => void };
_taskHandle: ?{cancel: () => void};
constructor(callback: () => void, delayMS: number) {
this._delay = delayMS;
this._callback = callback;
@@ -45,7 +45,7 @@ class Batchinator {
* By default, if there is a pending task the callback is run immediately. Set the option abort to
* true to not call the callback if it was pending.
*/
dispose(options: { abort: boolean } = { abort: false }) {
dispose(options: {abort: boolean} = {abort: false}) {
if (this._taskHandle) {
this._taskHandle.cancel();
if (!options.abort) {
@@ -66,7 +66,7 @@ class Batchinator {
this._callback();
});
}, this._delay);
this._taskHandle = { cancel: () => clearTimeout(timeoutHandle) };
this._taskHandle = {cancel: () => clearTimeout(timeoutHandle)};
}
}

View File

@@ -26,7 +26,7 @@ class Info {
sample_count = 0;
}
type FrameMetrics = { inLayout?: boolean, length: number, offset: number };
type FrameMetrics = {inLayout?: boolean, length: number, offset: number};
const DEBUG = false;
@@ -50,13 +50,16 @@ class FillRateHelper {
_mostlyBlankStartTime = (null: ?number);
_samplesStartTime = (null: ?number);
static addListener(callback: FillRateInfo => void): { remove: () => void } {
warning(_sampleRate !== null, 'Call `FillRateHelper.setSampleRate` before `addListener`.');
static addListener(callback: FillRateInfo => void): {remove: () => void} {
warning(
_sampleRate !== null,
'Call `FillRateHelper.setSampleRate` before `addListener`.',
);
_listeners.push(callback);
return {
remove: () => {
_listeners = _listeners.filter(listener => callback !== listener);
}
},
};
}
@@ -87,7 +90,8 @@ class FillRateHelper {
}
const start = this._samplesStartTime; // const for flow
if (start == null) {
DEBUG && console.debug('FillRateHelper: bail on deactivate with no start time');
DEBUG &&
console.debug('FillRateHelper: bail on deactivate with no start time');
return;
}
if (this._info.sample_count < _minSampleCount) {
@@ -98,22 +102,25 @@ class FillRateHelper {
const total_time_spent = performanceNow() - start;
const info: any = {
...this._info,
total_time_spent
total_time_spent,
};
if (DEBUG) {
const derived = {
avg_blankness: this._info.pixels_blank / this._info.pixels_sampled,
avg_speed: this._info.pixels_scrolled / (total_time_spent / 1000),
avg_speed_when_any_blank: this._info.any_blank_speed_sum / this._info.any_blank_count,
any_blank_per_min: this._info.any_blank_count / (total_time_spent / 1000 / 60),
avg_speed_when_any_blank:
this._info.any_blank_speed_sum / this._info.any_blank_count,
any_blank_per_min:
this._info.any_blank_count / (total_time_spent / 1000 / 60),
any_blank_time_frac: this._info.any_blank_ms / total_time_spent,
mostly_blank_per_min: this._info.mostly_blank_count / (total_time_spent / 1000 / 60),
mostly_blank_time_frac: this._info.mostly_blank_ms / total_time_spent
mostly_blank_per_min:
this._info.mostly_blank_count / (total_time_spent / 1000 / 60),
mostly_blank_time_frac: this._info.mostly_blank_ms / total_time_spent,
};
for (const key in derived) {
derived[key] = Math.round(1000 * derived[key]) / 1000;
}
console.debug('FillRateHelper deactivateAndFlush: ', { derived, info });
console.debug('FillRateHelper deactivateAndFlush: ', {derived, info});
}
_listeners.forEach(listener => listener(info));
this._resetData();
@@ -123,23 +130,27 @@ class FillRateHelper {
props: {
data: Array<any>,
getItemCount: (data: Array<any>) => number,
initialNumToRender: number
initialNumToRender: number,
},
state: {
first: number,
last: number
last: number,
},
scrollMetrics: {
dOffset: number,
offset: number,
velocity: number,
visibleLength: number
}
visibleLength: number,
},
): number {
if (!this._enabled || props.getItemCount(props.data) === 0 || this._samplesStartTime == null) {
if (
!this._enabled ||
props.getItemCount(props.data) === 0 ||
this._samplesStartTime == null
) {
return 0;
}
const { dOffset, offset, velocity, visibleLength } = scrollMetrics;
const {dOffset, offset, velocity, visibleLength} = scrollMetrics;
// Denominator metrics that we track for all events - most of the time there is no blankness and
// we want to capture that.
@@ -169,7 +180,10 @@ class FillRateHelper {
// Only count blankTop if we aren't rendering the first item, otherwise we will count the header
// as blank.
if (firstFrame && first > 0) {
blankTop = Math.min(visibleLength, Math.max(0, firstFrame.offset - offset));
blankTop = Math.min(
visibleLength,
Math.max(0, firstFrame.offset - offset),
);
}
let blankBottom = 0;
let last = state.last;
@@ -182,7 +196,10 @@ class FillRateHelper {
// footer as blank.
if (lastFrame && last < props.getItemCount(props.data) - 1) {
const bottomEdge = lastFrame.offset + lastFrame.length;
blankBottom = Math.min(visibleLength, Math.max(0, offset + visibleLength - bottomEdge));
blankBottom = Math.min(
visibleLength,
Math.max(0, offset + visibleLength - bottomEdge),
);
}
const pixels_blank = Math.round(blankTop + blankBottom);
const blankness = pixels_blank / visibleLength;

View File

@@ -5,17 +5,13 @@
* LICENSE file in the root directory of this source tree.
*/
import InteractionManager from '../../exports/InteractionManager';
import InteractionManager from '../../../exports/InteractionManager';
import TouchHistoryMath from '../TouchHistoryMath';
const currentCentroidXOfTouchesChangedAfter =
TouchHistoryMath.currentCentroidXOfTouchesChangedAfter;
const currentCentroidYOfTouchesChangedAfter =
TouchHistoryMath.currentCentroidYOfTouchesChangedAfter;
const previousCentroidXOfTouchesChangedAfter =
TouchHistoryMath.previousCentroidXOfTouchesChangedAfter;
const previousCentroidYOfTouchesChangedAfter =
TouchHistoryMath.previousCentroidYOfTouchesChangedAfter;
const currentCentroidXOfTouchesChangedAfter = TouchHistoryMath.currentCentroidXOfTouchesChangedAfter;
const currentCentroidYOfTouchesChangedAfter = TouchHistoryMath.currentCentroidYOfTouchesChangedAfter;
const previousCentroidXOfTouchesChangedAfter = TouchHistoryMath.previousCentroidXOfTouchesChangedAfter;
const previousCentroidYOfTouchesChangedAfter = TouchHistoryMath.previousCentroidYOfTouchesChangedAfter;
const currentCentroidX = TouchHistoryMath.currentCentroidX;
const currentCentroidY = TouchHistoryMath.currentCentroidY;
@@ -118,6 +114,7 @@ const currentCentroidY = TouchHistoryMath.currentCentroidY;
*/
const PanResponder = {
/**
*
* A graphical explanation of the touch data flow:
@@ -181,7 +178,7 @@ const PanResponder = {
* - vx/vy: Velocity.
*/
_initializeGestureState: function(gestureState) {
_initializeGestureState: function (gestureState) {
gestureState.moveX = 0;
gestureState.moveY = 0;
gestureState.x0 = 0;
@@ -219,16 +216,10 @@ const PanResponder = {
* typical responder callback pattern (without using `PanResponder`), but
* avoids more dispatches than necessary.
*/
_updateGestureStateOnMove: function(gestureState, touchHistory) {
_updateGestureStateOnMove: function (gestureState, touchHistory) {
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
gestureState.moveX = currentCentroidXOfTouchesChangedAfter(
touchHistory,
gestureState._accountsForMovesUpTo
);
gestureState.moveY = currentCentroidYOfTouchesChangedAfter(
touchHistory,
gestureState._accountsForMovesUpTo
);
gestureState.moveX = currentCentroidXOfTouchesChangedAfter(touchHistory, gestureState._accountsForMovesUpTo);
gestureState.moveY = currentCentroidYOfTouchesChangedAfter(touchHistory, gestureState._accountsForMovesUpTo);
const movedAfter = gestureState._accountsForMovesUpTo;
const prevX = previousCentroidXOfTouchesChangedAfter(touchHistory, movedAfter);
const x = currentCentroidXOfTouchesChangedAfter(touchHistory, movedAfter);
@@ -279,39 +270,39 @@ const PanResponder = {
* accordingly. (numberActiveTouches) may not be totally accurate unless you
* are the responder.
*/
create: function(config) {
create: function (config) {
const interactionState = {
handle: (null: ?number)
handle: (null: ?number),
};
const gestureState = {
// Useful for debugging
stateID: Math.random()
stateID: Math.random(),
};
PanResponder._initializeGestureState(gestureState);
const panHandlers = {
onStartShouldSetResponder: function(e) {
return config.onStartShouldSetPanResponder === undefined
? false
: config.onStartShouldSetPanResponder(e, gestureState);
onStartShouldSetResponder: function (e) {
return config.onStartShouldSetPanResponder === undefined ?
false :
config.onStartShouldSetPanResponder(e, gestureState);
},
onMoveShouldSetResponder: function(e) {
return config.onMoveShouldSetPanResponder === undefined
? false
: config.onMoveShouldSetPanResponder(e, gestureState);
onMoveShouldSetResponder: function (e) {
return config.onMoveShouldSetPanResponder === undefined ?
false :
config.onMoveShouldSetPanResponder(e, gestureState);
},
onStartShouldSetResponderCapture: function(e) {
onStartShouldSetResponderCapture: function (e) {
// TODO: Actually, we should reinitialize the state any time
// touches.length increases from 0 active to > 0 active.
if (e.nativeEvent.touches.length === 1) {
PanResponder._initializeGestureState(gestureState);
}
gestureState.numberActiveTouches = e.touchHistory.numberActiveTouches;
return config.onStartShouldSetPanResponderCapture !== undefined
? config.onStartShouldSetPanResponderCapture(e, gestureState)
: false;
return config.onStartShouldSetPanResponderCapture !== undefined ?
config.onStartShouldSetPanResponderCapture(e, gestureState) :
false;
},
onMoveShouldSetResponderCapture: function(e) {
onMoveShouldSetResponderCapture: function (e) {
const touchHistory = e.touchHistory;
// Responder system incorrectly dispatches should* to current responder
// Filter out any touch moves past the first one - we would have
@@ -320,12 +311,12 @@ const PanResponder = {
return false;
}
PanResponder._updateGestureStateOnMove(gestureState, touchHistory);
return config.onMoveShouldSetPanResponderCapture
? config.onMoveShouldSetPanResponderCapture(e, gestureState)
: false;
return config.onMoveShouldSetPanResponderCapture ?
config.onMoveShouldSetPanResponderCapture(e, gestureState) :
false;
},
onResponderGrant: function(e) {
onResponderGrant: function (e) {
if (!interactionState.handle) {
interactionState.handle = InteractionManager.createInteractionHandle();
}
@@ -337,21 +328,21 @@ const PanResponder = {
config.onPanResponderGrant(e, gestureState);
}
// TODO: t7467124 investigate if this can be removed
return config.onShouldBlockNativeResponder === undefined
? true
: config.onShouldBlockNativeResponder();
return config.onShouldBlockNativeResponder === undefined ?
true :
config.onShouldBlockNativeResponder();
},
onResponderReject: function(e) {
onResponderReject: function (e) {
clearInteractionHandle(interactionState, config.onPanResponderReject, e, gestureState);
},
onResponderRelease: function(e) {
onResponderRelease: function (e) {
clearInteractionHandle(interactionState, config.onPanResponderRelease, e, gestureState);
PanResponder._initializeGestureState(gestureState);
},
onResponderStart: function(e) {
onResponderStart: function (e) {
const touchHistory = e.touchHistory;
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
if (config.onPanResponderStart) {
@@ -359,7 +350,7 @@ const PanResponder = {
}
},
onResponderMove: function(e) {
onResponderMove: function (e) {
const touchHistory = e.touchHistory;
// Guard against the dispatch of two touch moves when there are two
// simultaneously changed touches.
@@ -374,34 +365,34 @@ const PanResponder = {
}
},
onResponderEnd: function(e) {
onResponderEnd: function (e) {
const touchHistory = e.touchHistory;
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
clearInteractionHandle(interactionState, config.onPanResponderEnd, e, gestureState);
},
onResponderTerminate: function(e) {
onResponderTerminate: function (e) {
clearInteractionHandle(interactionState, config.onPanResponderTerminate, e, gestureState);
PanResponder._initializeGestureState(gestureState);
},
onResponderTerminationRequest: function(e) {
return config.onPanResponderTerminationRequest === undefined
? true
: config.onPanResponderTerminationRequest(e, gestureState);
onResponderTerminationRequest: function (e) {
return config.onPanResponderTerminationRequest === undefined ?
true :
config.onPanResponderTerminationRequest(e, gestureState);
}
};
return {
panHandlers,
getInteractionHandle(): ?number {
return interactionState.handle;
}
},
};
}
};
function clearInteractionHandle(
interactionState: { handle: ?number },
interactionState: {handle: ?number},
callback: Function,
event: Object,
gestureState: Object

View File

@@ -0,0 +1,2 @@
facebook/react-native@0.55.4
facebook/react-native@370bcffba748e895ad8afa825bfef40bff859c95

View File

@@ -1,5 +1,3 @@
/* eslint-disable */
var TouchHistoryMath = {
/**
* This code is optimized and not intended to look beautiful. This allows
@@ -17,7 +15,12 @@ var TouchHistoryMath = {
* touches vs. previous centroid of now actively moving touches.
* @return {number} value of centroid in specified dimension.
*/
centroidDimension: function(touchHistory, touchesChangedAfter, isXAxis, ofCurrent) {
centroidDimension: function(
touchHistory,
touchesChangedAfter,
isXAxis,
ofCurrent
) {
var touchBank = touchHistory.touchBank;
var total = 0;
var count = 0;
@@ -28,7 +31,10 @@ var TouchHistoryMath = {
: null;
if (oneTouchData !== null) {
if (oneTouchData.touchActive && oneTouchData.currentTimeStamp > touchesChangedAfter) {
if (
oneTouchData.touchActive &&
oneTouchData.currentTimeStamp > touchesChangedAfter
) {
total +=
ofCurrent && isXAxis
? oneTouchData.currentPageX
@@ -48,7 +54,7 @@ var TouchHistoryMath = {
touchTrack.touchActive &&
touchTrack.currentTimeStamp >= touchesChangedAfter
) {
var toAdd; // Yuck, program temporarily in invalid state.
var toAdd = void 0; // Yuck, program temporarily in invalid state.
if (ofCurrent && isXAxis) {
toAdd = touchTrack.currentPageX;
} else if (ofCurrent && !isXAxis) {
@@ -66,39 +72,51 @@ var TouchHistoryMath = {
return count > 0 ? total / count : TouchHistoryMath.noCentroid;
},
currentCentroidXOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) {
currentCentroidXOfTouchesChangedAfter: function(
touchHistory,
touchesChangedAfter
) {
return TouchHistoryMath.centroidDimension(
touchHistory,
touchesChangedAfter,
true, // isXAxis
true // ofCurrent
true
);
},
currentCentroidYOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) {
currentCentroidYOfTouchesChangedAfter: function(
touchHistory,
touchesChangedAfter
) {
return TouchHistoryMath.centroidDimension(
touchHistory,
touchesChangedAfter,
false, // isXAxis
true // ofCurrent
true
);
},
previousCentroidXOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) {
previousCentroidXOfTouchesChangedAfter: function(
touchHistory,
touchesChangedAfter
) {
return TouchHistoryMath.centroidDimension(
touchHistory,
touchesChangedAfter,
true, // isXAxis
false // ofCurrent
false
);
},
previousCentroidYOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) {
previousCentroidYOfTouchesChangedAfter: function(
touchHistory,
touchesChangedAfter
) {
return TouchHistoryMath.centroidDimension(
touchHistory,
touchesChangedAfter,
false, // isXAxis
false // ofCurrent
false
);
},
@@ -107,7 +125,7 @@ var TouchHistoryMath = {
touchHistory,
0, // touchesChangedAfter
true, // isXAxis
true // ofCurrent
true
);
},
@@ -116,7 +134,7 @@ var TouchHistoryMath = {
touchHistory,
0, // touchesChangedAfter
false, // isXAxis
true // ofCurrent
true
);
},

View File

@@ -15,15 +15,15 @@ export type ViewToken = {
key: string,
index: ?number,
isViewable: boolean,
section?: any
section?: any,
};
export type ViewabilityConfigCallbackPair = {
viewabilityConfig: ViewabilityConfig,
onViewableItemsChanged: (info: {
viewableItems: Array<ViewToken>,
changed: Array<ViewToken>
}) => void
changed: Array<ViewToken>,
}) => void,
};
export type ViewabilityConfig = {|
@@ -52,7 +52,7 @@ export type ViewabilityConfig = {|
* Nothing is considered viewable until the user scrolls or `recordInteraction` is called after
* render.
*/
waitForInteraction?: boolean
waitForInteraction?: boolean,
|};
/**
@@ -77,7 +77,9 @@ class ViewabilityHelper {
_viewableIndices: Array<number> = [];
_viewableItems: Map<string, ViewToken> = new Map();
constructor(config: ViewabilityConfig = { viewAreaCoveragePercentThreshold: 0 }) {
constructor(
config: ViewabilityConfig = {viewAreaCoveragePercentThreshold: 0},
) {
this._config = config;
}
@@ -95,28 +97,32 @@ class ViewabilityHelper {
itemCount: number,
scrollOffset: number,
viewportHeight: number,
getFrameMetrics: (index: number) => ?{ length: number, offset: number },
renderRange?: { first: number, last: number } // Optional optimization to reduce the scan size
getFrameMetrics: (index: number) => ?{length: number, offset: number},
renderRange?: {first: number, last: number}, // Optional optimization to reduce the scan size
): Array<number> {
const { itemVisiblePercentThreshold, viewAreaCoveragePercentThreshold } = this._config;
const {
itemVisiblePercentThreshold,
viewAreaCoveragePercentThreshold,
} = this._config;
const viewAreaMode = viewAreaCoveragePercentThreshold != null;
const viewablePercentThreshold = viewAreaMode
? viewAreaCoveragePercentThreshold
: itemVisiblePercentThreshold;
invariant(
viewablePercentThreshold != null &&
(itemVisiblePercentThreshold != null) !== (viewAreaCoveragePercentThreshold != null),
'Must set exactly one of itemVisiblePercentThreshold or viewAreaCoveragePercentThreshold'
(itemVisiblePercentThreshold != null) !==
(viewAreaCoveragePercentThreshold != null),
'Must set exactly one of itemVisiblePercentThreshold or viewAreaCoveragePercentThreshold',
);
const viewableIndices = [];
if (itemCount === 0) {
return viewableIndices;
}
let firstVisible = -1;
const { first, last } = renderRange || { first: 0, last: itemCount - 1 };
const {first, last} = renderRange || {first: 0, last: itemCount - 1};
invariant(
last < itemCount,
'Invalid render range ' + JSON.stringify({ renderRange, itemCount })
'Invalid render range ' + JSON.stringify({renderRange, itemCount}),
);
for (let idx = first; idx <= last; idx++) {
const metrics = getFrameMetrics(idx);
@@ -134,7 +140,7 @@ class ViewabilityHelper {
top,
bottom,
viewportHeight,
metrics.length
metrics.length,
)
) {
viewableIndices.push(idx);
@@ -154,13 +160,13 @@ class ViewabilityHelper {
itemCount: number,
scrollOffset: number,
viewportHeight: number,
getFrameMetrics: (index: number) => ?{ length: number, offset: number },
getFrameMetrics: (index: number) => ?{length: number, offset: number},
createViewToken: (index: number, isViewable: boolean) => ViewToken,
onViewableItemsChanged: ({
viewableItems: Array<ViewToken>,
changed: Array<ViewToken>
changed: Array<ViewToken>,
}) => void,
renderRange?: { first: number, last: number } // Optional optimization to reduce the scan size
renderRange?: {first: number, last: number}, // Optional optimization to reduce the scan size
): void {
if (
(this._config.waitForInteraction && !this._hasInteracted) ||
@@ -176,7 +182,7 @@ class ViewabilityHelper {
scrollOffset,
viewportHeight,
getFrameMetrics,
renderRange
renderRange,
);
}
if (
@@ -191,11 +197,19 @@ class ViewabilityHelper {
if (this._config.minimumViewTime) {
const handle = setTimeout(() => {
this._timers.delete(handle);
this._onUpdateSync(viewableIndices, onViewableItemsChanged, createViewToken);
this._onUpdateSync(
viewableIndices,
onViewableItemsChanged,
createViewToken,
);
}, this._config.minimumViewTime);
this._timers.add(handle);
} else {
this._onUpdateSync(viewableIndices, onViewableItemsChanged, createViewToken);
this._onUpdateSync(
viewableIndices,
onViewableItemsChanged,
createViewToken,
);
}
}
@@ -213,17 +227,21 @@ class ViewabilityHelper {
this._hasInteracted = true;
}
_onUpdateSync(viewableIndicesToCheck, onViewableItemsChanged, createViewToken) {
_onUpdateSync(
viewableIndicesToCheck,
onViewableItemsChanged,
createViewToken,
) {
// Filter out indices that have gone out of view since this call was scheduled.
viewableIndicesToCheck = viewableIndicesToCheck.filter(ii =>
this._viewableIndices.includes(ii)
this._viewableIndices.includes(ii),
);
const prevItems = this._viewableItems;
const nextItems = new Map(
viewableIndicesToCheck.map(ii => {
const viewable = createViewToken(ii, true);
return [viewable.key, viewable];
})
}),
);
const changed = [];
@@ -234,7 +252,7 @@ class ViewabilityHelper {
}
for (const [key, viewable] of prevItems) {
if (!nextItems.has(key)) {
changed.push({ ...viewable, isViewable: false });
changed.push({...viewable, isViewable: false});
}
}
if (changed.length > 0) {
@@ -242,7 +260,7 @@ class ViewabilityHelper {
onViewableItemsChanged({
viewableItems: Array.from(nextItems.values()),
changed,
viewabilityConfig: this._config
viewabilityConfig: this._config,
});
}
}
@@ -254,23 +272,32 @@ function _isViewable(
top: number,
bottom: number,
viewportHeight: number,
itemLength: number
itemLength: number,
): boolean {
if (_isEntirelyVisible(top, bottom, viewportHeight)) {
return true;
} else {
const pixels = _getPixelsVisible(top, bottom, viewportHeight);
const percent = 100 * (viewAreaMode ? pixels / viewportHeight : pixels / itemLength);
const percent =
100 * (viewAreaMode ? pixels / viewportHeight : pixels / itemLength);
return percent >= viewablePercentThreshold;
}
}
function _getPixelsVisible(top: number, bottom: number, viewportHeight: number): number {
function _getPixelsVisible(
top: number,
bottom: number,
viewportHeight: number,
): number {
const visibleHeight = Math.min(bottom, viewportHeight) - Math.max(top, 0);
return Math.max(0, visibleHeight);
}
function _isEntirelyVisible(top: number, bottom: number, viewportHeight: number): boolean {
function _isEntirelyVisible(
top: number,
bottom: number,
viewportHeight: number,
): boolean {
return top >= 0 && bottom <= viewportHeight && bottom > top;
}

View File

@@ -15,10 +15,10 @@ import invariant from 'fbjs/lib/invariant';
* items that bound different windows of content, such as the visible area or the buffered overscan
* area.
*/
export function elementsThatOverlapOffsets(
function elementsThatOverlapOffsets(
offsets: Array<number>,
itemCount: number,
getFrameMetrics: (index: number) => { length: number, offset: number }
getFrameMetrics: (index: number) => {length: number, offset: number},
): Array<number> {
const out = [];
let outLength = 0;
@@ -33,7 +33,7 @@ export function elementsThatOverlapOffsets(
invariant(
outLength === offsets.length,
'bad offsets input, should be in increasing order: %s',
JSON.stringify(offsets)
JSON.stringify(offsets),
);
return out;
}
@@ -49,15 +49,18 @@ export function elementsThatOverlapOffsets(
* can restrict the number of new items render at once so that content can appear on the screen
* faster.
*/
export function newRangeCount(
prev: { first: number, last: number },
next: { first: number, last: number }
function newRangeCount(
prev: {first: number, last: number},
next: {first: number, last: number},
): number {
return (
next.last -
next.first +
1 -
Math.max(0, 1 + Math.min(next.last, prev.last) - Math.max(next.first, prev.first))
Math.max(
0,
1 + Math.min(next.last, prev.last) - Math.max(next.first, prev.first),
)
);
}
@@ -67,28 +70,28 @@ export function newRangeCount(
* prioritizes the visible area first, then expands that with overscan regions ahead and behind,
* biased in the direction of scroll.
*/
export function computeWindowedRenderLimits(
function computeWindowedRenderLimits(
props: {
data: any,
getItemCount: (data: any) => number,
maxToRenderPerBatch: number,
windowSize: number
windowSize: number,
},
prev: { first: number, last: number },
getFrameMetricsApprox: (index: number) => { length: number, offset: number },
prev: {first: number, last: number},
getFrameMetricsApprox: (index: number) => {length: number, offset: number},
scrollMetrics: {
dt: number,
offset: number,
velocity: number,
visibleLength: number
}
): { first: number, last: number } {
const { data, getItemCount, maxToRenderPerBatch, windowSize } = props;
visibleLength: number,
},
): {first: number, last: number} {
const {data, getItemCount, maxToRenderPerBatch, windowSize} = props;
const itemCount = getItemCount(data);
if (itemCount === 0) {
return prev;
}
const { offset, velocity, visibleLength } = scrollMetrics;
const {offset, velocity, visibleLength} = scrollMetrics;
// Start with visible area, then compute maximum overscan region by expanding from there, biased
// in the direction of scroll. Total overscan area is capped, which should cap memory consumption
@@ -100,9 +103,13 @@ export function computeWindowedRenderLimits(
// Considering velocity seems to introduce more churn than it's worth.
const leadFactor = 0.5; // Math.max(0, Math.min(1, velocity / 25 + 0.5));
const fillPreference = velocity > 1 ? 'after' : velocity < -1 ? 'before' : 'none';
const fillPreference =
velocity > 1 ? 'after' : velocity < -1 ? 'before' : 'none';
const overscanBegin = Math.max(0, visibleBegin - (1 - leadFactor) * overscanLength);
const overscanBegin = Math.max(
0,
visibleBegin - (1 - leadFactor) * overscanLength,
);
const overscanEnd = Math.max(0, visibleEnd + leadFactor * overscanLength);
const lastItemOffset = getFrameMetricsApprox(itemCount - 1).offset;
@@ -110,7 +117,7 @@ export function computeWindowedRenderLimits(
// Entire list is before our overscan window
return {
first: Math.max(0, itemCount - 1 - maxToRenderPerBatch),
last: itemCount - 1
last: itemCount - 1,
};
}
@@ -118,13 +125,16 @@ export function computeWindowedRenderLimits(
let [overscanFirst, first, last, overscanLast] = elementsThatOverlapOffsets(
[overscanBegin, visibleBegin, visibleEnd, overscanEnd],
props.getItemCount(props.data),
getFrameMetricsApprox
getFrameMetricsApprox,
);
overscanFirst = overscanFirst == null ? 0 : overscanFirst;
first = first == null ? Math.max(0, overscanFirst) : first;
overscanLast = overscanLast == null ? itemCount - 1 : overscanLast;
last = last == null ? Math.min(overscanLast, first + maxToRenderPerBatch - 1) : last;
const visible = { first, last };
last =
last == null
? Math.min(overscanLast, first + maxToRenderPerBatch - 1)
: last;
const visible = {first, last};
// We want to limit the number of new cells we're rendering per batch so that we can fill the
// content on the screen quickly. If we rendered the entire overscan window at once, the user
@@ -139,9 +149,11 @@ export function computeWindowedRenderLimits(
}
const maxNewCells = newCellCount >= maxToRenderPerBatch;
const firstWillAddMore = first <= prev.first || first > prev.last;
const firstShouldIncrement = first > overscanFirst && (!maxNewCells || !firstWillAddMore);
const firstShouldIncrement =
first > overscanFirst && (!maxNewCells || !firstWillAddMore);
const lastWillAddMore = last >= prev.last || last < prev.first;
const lastShouldIncrement = last < overscanLast && (!maxNewCells || !lastWillAddMore);
const lastShouldIncrement =
last < overscanLast && (!maxNewCells || !lastWillAddMore);
if (maxNewCells && !firstShouldIncrement && !lastShouldIncrement) {
// We only want to stop if we've hit maxNewCells AND we cannot increment first or last
// without rendering new items. This let's us preserve as many already rendered items as
@@ -187,11 +199,17 @@ export function computeWindowedRenderLimits(
itemCount,
overscanFirst,
overscanLast,
visible
})
visible,
}),
);
}
return { first, last };
return {first, last};
}
export {
computeWindowedRenderLimits,
elementsThatOverlapOffsets,
newRangeCount
}
const VirtualizeUtils = {