mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-03-27 01:34:17 +08:00
Add snapshot of VirtualizedList from React Native
Exact source code of the module and its dependencies, for the given SHA.
This commit is contained in:
1
packages/react-native-web/src/vendor/Batchinator/SHA
vendored
Normal file
1
packages/react-native-web/src/vendor/Batchinator/SHA
vendored
Normal file
@@ -0,0 +1 @@
|
||||
facebook/react-native@19a4a7d3cb6d00780ccbbbd7b0062896f64ab24d
|
||||
75
packages/react-native-web/src/vendor/Batchinator/index.js
vendored
Normal file
75
packages/react-native-web/src/vendor/Batchinator/index.js
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @providesModule Batchinator
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const InteractionManager = require('InteractionManager');
|
||||
|
||||
/**
|
||||
* A simple class for batching up invocations of a low-pri callback. A timeout is set to run the
|
||||
* callback once after a delay, no matter how many times it's scheduled. Once the delay is reached,
|
||||
* InteractionManager.runAfterInteractions is used to invoke the callback after any hi-pri
|
||||
* interactions are done running.
|
||||
*
|
||||
* Make sure to cleanup with dispose(). Example:
|
||||
*
|
||||
* class Widget extends React.Component {
|
||||
* _batchedSave: new Batchinator(() => this._saveState, 1000);
|
||||
* _saveSate() {
|
||||
* // save this.state to disk
|
||||
* }
|
||||
* componentDidUpdate() {
|
||||
* this._batchedSave.schedule();
|
||||
* }
|
||||
* componentWillUnmount() {
|
||||
* this._batchedSave.dispose();
|
||||
* }
|
||||
* ...
|
||||
* }
|
||||
*/
|
||||
class Batchinator {
|
||||
_callback: () => void;
|
||||
_delay: number;
|
||||
_taskHandle: ?{ cancel: () => void };
|
||||
constructor(callback: () => void, delayMS: number) {
|
||||
this._delay = delayMS;
|
||||
this._callback = callback;
|
||||
}
|
||||
/*
|
||||
* Cleanup any pending tasks.
|
||||
*
|
||||
* 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 }) {
|
||||
if (this._taskHandle) {
|
||||
this._taskHandle.cancel();
|
||||
if (!options.abort) {
|
||||
this._callback();
|
||||
}
|
||||
this._taskHandle = null;
|
||||
}
|
||||
}
|
||||
schedule() {
|
||||
if (this._taskHandle) {
|
||||
return;
|
||||
}
|
||||
const timeoutHandle = setTimeout(() => {
|
||||
this._taskHandle = InteractionManager.runAfterInteractions(() => {
|
||||
// Note that we clear the handle before invoking the callback so that if the callback calls
|
||||
// schedule again, it will actually schedule another task.
|
||||
this._taskHandle = null;
|
||||
this._callback();
|
||||
});
|
||||
}, this._delay);
|
||||
this._taskHandle = { cancel: () => clearTimeout(timeoutHandle) };
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Batchinator;
|
||||
1
packages/react-native-web/src/vendor/FillRateHelper/SHA
vendored
Normal file
1
packages/react-native-web/src/vendor/FillRateHelper/SHA
vendored
Normal file
@@ -0,0 +1 @@
|
||||
facebook/react-native@19a4a7d3cb6d00780ccbbbd7b0062896f64ab24d
|
||||
225
packages/react-native-web/src/vendor/FillRateHelper/index.js
vendored
Normal file
225
packages/react-native-web/src/vendor/FillRateHelper/index.js
vendored
Normal file
@@ -0,0 +1,225 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @providesModule FillRateHelper
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses an error
|
||||
* found when Flow v0.54 was deployed. To see the error delete this comment and
|
||||
* run Flow. */
|
||||
const performanceNow = require('fbjs/lib/performanceNow');
|
||||
/* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses an error
|
||||
* found when Flow v0.54 was deployed. To see the error delete this comment and
|
||||
* run Flow. */
|
||||
const warning = require('fbjs/lib/warning');
|
||||
|
||||
export type FillRateInfo = Info;
|
||||
|
||||
class Info {
|
||||
any_blank_count = 0;
|
||||
any_blank_ms = 0;
|
||||
any_blank_speed_sum = 0;
|
||||
mostly_blank_count = 0;
|
||||
mostly_blank_ms = 0;
|
||||
pixels_blank = 0;
|
||||
pixels_sampled = 0;
|
||||
pixels_scrolled = 0;
|
||||
total_time_spent = 0;
|
||||
sample_count = 0;
|
||||
}
|
||||
|
||||
type FrameMetrics = { inLayout?: boolean, length: number, offset: number };
|
||||
|
||||
const DEBUG = false;
|
||||
|
||||
let _listeners: Array<(Info) => void> = [];
|
||||
let _minSampleCount = 10;
|
||||
let _sampleRate = DEBUG ? 1 : null;
|
||||
|
||||
/**
|
||||
* A helper class for detecting when the maximem fill rate of `VirtualizedList` is exceeded.
|
||||
* By default the sampling rate is set to zero and this will do nothing. If you want to collect
|
||||
* samples (e.g. to log them), make sure to call `FillRateHelper.setSampleRate(0.0-1.0)`.
|
||||
*
|
||||
* Listeners and sample rate are global for all `VirtualizedList`s - typical usage will combine with
|
||||
* `SceneTracker.getActiveScene` to determine the context of the events.
|
||||
*/
|
||||
class FillRateHelper {
|
||||
_anyBlankStartTime = (null: ?number);
|
||||
_enabled = false;
|
||||
_getFrameMetrics: (index: number) => ?FrameMetrics;
|
||||
_info = new Info();
|
||||
_mostlyBlankStartTime = (null: ?number);
|
||||
_samplesStartTime = (null: ?number);
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static setSampleRate(sampleRate: number) {
|
||||
_sampleRate = sampleRate;
|
||||
}
|
||||
|
||||
static setMinSampleCount(minSampleCount: number) {
|
||||
_minSampleCount = minSampleCount;
|
||||
}
|
||||
|
||||
constructor(getFrameMetrics: (index: number) => ?FrameMetrics) {
|
||||
this._getFrameMetrics = getFrameMetrics;
|
||||
this._enabled = (_sampleRate || 0) > Math.random();
|
||||
this._resetData();
|
||||
}
|
||||
|
||||
activate() {
|
||||
if (this._enabled && this._samplesStartTime == null) {
|
||||
DEBUG && console.debug('FillRateHelper: activate');
|
||||
this._samplesStartTime = performanceNow();
|
||||
}
|
||||
}
|
||||
|
||||
deactivateAndFlush() {
|
||||
if (!this._enabled) {
|
||||
return;
|
||||
}
|
||||
const start = this._samplesStartTime; // const for flow
|
||||
if (start == null) {
|
||||
DEBUG && console.debug('FillRateHelper: bail on deactivate with no start time');
|
||||
return;
|
||||
}
|
||||
if (this._info.sample_count < _minSampleCount) {
|
||||
// Don't bother with under-sampled events.
|
||||
this._resetData();
|
||||
return;
|
||||
}
|
||||
const total_time_spent = performanceNow() - start;
|
||||
const info: any = {
|
||||
...this._info,
|
||||
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),
|
||||
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
|
||||
};
|
||||
for (const key in derived) {
|
||||
derived[key] = Math.round(1000 * derived[key]) / 1000;
|
||||
}
|
||||
console.debug('FillRateHelper deactivateAndFlush: ', { derived, info });
|
||||
}
|
||||
_listeners.forEach(listener => listener(info));
|
||||
this._resetData();
|
||||
}
|
||||
|
||||
computeBlankness(
|
||||
props: {
|
||||
data: Array<any>,
|
||||
getItemCount: (data: Array<any>) => number,
|
||||
initialNumToRender: number
|
||||
},
|
||||
state: {
|
||||
first: number,
|
||||
last: number
|
||||
},
|
||||
scrollMetrics: {
|
||||
dOffset: number,
|
||||
offset: number,
|
||||
velocity: number,
|
||||
visibleLength: number
|
||||
}
|
||||
): number {
|
||||
if (!this._enabled || props.getItemCount(props.data) === 0 || this._samplesStartTime == null) {
|
||||
return 0;
|
||||
}
|
||||
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.
|
||||
this._info.sample_count++;
|
||||
this._info.pixels_sampled += Math.round(visibleLength);
|
||||
this._info.pixels_scrolled += Math.round(Math.abs(dOffset));
|
||||
const scrollSpeed = Math.round(Math.abs(velocity) * 1000); // px / sec
|
||||
|
||||
// Whether blank now or not, record the elapsed time blank if we were blank last time.
|
||||
const now = performanceNow();
|
||||
if (this._anyBlankStartTime != null) {
|
||||
this._info.any_blank_ms += now - this._anyBlankStartTime;
|
||||
}
|
||||
this._anyBlankStartTime = null;
|
||||
if (this._mostlyBlankStartTime != null) {
|
||||
this._info.mostly_blank_ms += now - this._mostlyBlankStartTime;
|
||||
}
|
||||
this._mostlyBlankStartTime = null;
|
||||
|
||||
let blankTop = 0;
|
||||
let first = state.first;
|
||||
let firstFrame = this._getFrameMetrics(first);
|
||||
while (first <= state.last && (!firstFrame || !firstFrame.inLayout)) {
|
||||
firstFrame = this._getFrameMetrics(first);
|
||||
first++;
|
||||
}
|
||||
// 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));
|
||||
}
|
||||
let blankBottom = 0;
|
||||
let last = state.last;
|
||||
let lastFrame = this._getFrameMetrics(last);
|
||||
while (last >= state.first && (!lastFrame || !lastFrame.inLayout)) {
|
||||
lastFrame = this._getFrameMetrics(last);
|
||||
last--;
|
||||
}
|
||||
// Only count blankBottom if we aren't rendering the last item, otherwise we will count the
|
||||
// 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));
|
||||
}
|
||||
const pixels_blank = Math.round(blankTop + blankBottom);
|
||||
const blankness = pixels_blank / visibleLength;
|
||||
if (blankness > 0) {
|
||||
this._anyBlankStartTime = now;
|
||||
this._info.any_blank_speed_sum += scrollSpeed;
|
||||
this._info.any_blank_count++;
|
||||
this._info.pixels_blank += pixels_blank;
|
||||
if (blankness > 0.5) {
|
||||
this._mostlyBlankStartTime = now;
|
||||
this._info.mostly_blank_count++;
|
||||
}
|
||||
} else if (scrollSpeed < 0.01 || Math.abs(dOffset) < 1) {
|
||||
this.deactivateAndFlush();
|
||||
}
|
||||
return blankness;
|
||||
}
|
||||
|
||||
enabled(): boolean {
|
||||
return this._enabled;
|
||||
}
|
||||
|
||||
_resetData() {
|
||||
this._anyBlankStartTime = null;
|
||||
this._info = new Info();
|
||||
this._mostlyBlankStartTime = null;
|
||||
this._samplesStartTime = null;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = FillRateHelper;
|
||||
1
packages/react-native-web/src/vendor/ViewabilityHelper/SHA
vendored
Normal file
1
packages/react-native-web/src/vendor/ViewabilityHelper/SHA
vendored
Normal file
@@ -0,0 +1 @@
|
||||
facebook/react-native@19a4a7d3cb6d00780ccbbbd7b0062896f64ab24d
|
||||
279
packages/react-native-web/src/vendor/ViewabilityHelper/index.js
vendored
Normal file
279
packages/react-native-web/src/vendor/ViewabilityHelper/index.js
vendored
Normal file
@@ -0,0 +1,279 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @providesModule ViewabilityHelper
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
export type ViewToken = {
|
||||
item: any,
|
||||
key: string,
|
||||
index: ?number,
|
||||
isViewable: boolean,
|
||||
section?: any
|
||||
};
|
||||
|
||||
export type ViewabilityConfigCallbackPair = {
|
||||
viewabilityConfig: ViewabilityConfig,
|
||||
onViewableItemsChanged: (info: {
|
||||
viewableItems: Array<ViewToken>,
|
||||
changed: Array<ViewToken>
|
||||
}) => void
|
||||
};
|
||||
|
||||
export type ViewabilityConfig = {|
|
||||
/**
|
||||
* Minimum amount of time (in milliseconds) that an item must be physically viewable before the
|
||||
* viewability callback will be fired. A high number means that scrolling through content without
|
||||
* stopping will not mark the content as viewable.
|
||||
*/
|
||||
minimumViewTime?: number,
|
||||
|
||||
/**
|
||||
* Percent of viewport that must be covered for a partially occluded item to count as
|
||||
* "viewable", 0-100. Fully visible items are always considered viewable. A value of 0 means
|
||||
* that a single pixel in the viewport makes the item viewable, and a value of 100 means that
|
||||
* an item must be either entirely visible or cover the entire viewport to count as viewable.
|
||||
*/
|
||||
viewAreaCoveragePercentThreshold?: number,
|
||||
|
||||
/**
|
||||
* Similar to `viewAreaPercentThreshold`, but considers the percent of the item that is visible,
|
||||
* rather than the fraction of the viewable area it covers.
|
||||
*/
|
||||
itemVisiblePercentThreshold?: number,
|
||||
|
||||
/**
|
||||
* Nothing is considered viewable until the user scrolls or `recordInteraction` is called after
|
||||
* render.
|
||||
*/
|
||||
waitForInteraction?: boolean
|
||||
|};
|
||||
|
||||
/**
|
||||
* A Utility class for calculating viewable items based on current metrics like scroll position and
|
||||
* layout.
|
||||
*
|
||||
* An item is said to be in a "viewable" state when any of the following
|
||||
* is true for longer than `minimumViewTime` milliseconds (after an interaction if `waitForInteraction`
|
||||
* is true):
|
||||
*
|
||||
* - Occupying >= `viewAreaCoveragePercentThreshold` of the view area XOR fraction of the item
|
||||
* visible in the view area >= `itemVisiblePercentThreshold`.
|
||||
* - Entirely visible on screen
|
||||
*/
|
||||
class ViewabilityHelper {
|
||||
_config: ViewabilityConfig;
|
||||
_hasInteracted: boolean = false;
|
||||
/* $FlowFixMe(>=0.63.0 site=react_native_fb) This comment suppresses an error
|
||||
* found when Flow v0.63 was deployed. To see the error delete this comment
|
||||
* and run Flow. */
|
||||
_timers: Set<number> = new Set();
|
||||
_viewableIndices: Array<number> = [];
|
||||
_viewableItems: Map<string, ViewToken> = new Map();
|
||||
|
||||
constructor(config: ViewabilityConfig = { viewAreaCoveragePercentThreshold: 0 }) {
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup, e.g. on unmount. Clears any pending timers.
|
||||
*/
|
||||
dispose() {
|
||||
this._timers.forEach(clearTimeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines which items are viewable based on the current metrics and config.
|
||||
*/
|
||||
computeViewableItems(
|
||||
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
|
||||
): Array<number> {
|
||||
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'
|
||||
);
|
||||
const viewableIndices = [];
|
||||
if (itemCount === 0) {
|
||||
return viewableIndices;
|
||||
}
|
||||
let firstVisible = -1;
|
||||
const { first, last } = renderRange || { first: 0, last: itemCount - 1 };
|
||||
invariant(
|
||||
last < itemCount,
|
||||
'Invalid render range ' + JSON.stringify({ renderRange, itemCount })
|
||||
);
|
||||
for (let idx = first; idx <= last; idx++) {
|
||||
const metrics = getFrameMetrics(idx);
|
||||
if (!metrics) {
|
||||
continue;
|
||||
}
|
||||
const top = metrics.offset - scrollOffset;
|
||||
const bottom = top + metrics.length;
|
||||
if (top < viewportHeight && bottom > 0) {
|
||||
firstVisible = idx;
|
||||
if (
|
||||
_isViewable(
|
||||
viewAreaMode,
|
||||
viewablePercentThreshold,
|
||||
top,
|
||||
bottom,
|
||||
viewportHeight,
|
||||
metrics.length
|
||||
)
|
||||
) {
|
||||
viewableIndices.push(idx);
|
||||
}
|
||||
} else if (firstVisible >= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return viewableIndices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Figures out which items are viewable and how that has changed from before and calls
|
||||
* `onViewableItemsChanged` as appropriate.
|
||||
*/
|
||||
onUpdate(
|
||||
itemCount: number,
|
||||
scrollOffset: number,
|
||||
viewportHeight: number,
|
||||
getFrameMetrics: (index: number) => ?{ length: number, offset: number },
|
||||
createViewToken: (index: number, isViewable: boolean) => ViewToken,
|
||||
onViewableItemsChanged: ({
|
||||
viewableItems: Array<ViewToken>,
|
||||
changed: Array<ViewToken>
|
||||
}) => void,
|
||||
renderRange?: { first: number, last: number } // Optional optimization to reduce the scan size
|
||||
): void {
|
||||
if (
|
||||
(this._config.waitForInteraction && !this._hasInteracted) ||
|
||||
itemCount === 0 ||
|
||||
!getFrameMetrics(0)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
let viewableIndices = [];
|
||||
if (itemCount) {
|
||||
viewableIndices = this.computeViewableItems(
|
||||
itemCount,
|
||||
scrollOffset,
|
||||
viewportHeight,
|
||||
getFrameMetrics,
|
||||
renderRange
|
||||
);
|
||||
}
|
||||
if (
|
||||
this._viewableIndices.length === viewableIndices.length &&
|
||||
this._viewableIndices.every((v, ii) => v === viewableIndices[ii])
|
||||
) {
|
||||
// We might get a lot of scroll events where visibility doesn't change and we don't want to do
|
||||
// extra work in those cases.
|
||||
return;
|
||||
}
|
||||
this._viewableIndices = viewableIndices;
|
||||
if (this._config.minimumViewTime) {
|
||||
const handle = setTimeout(() => {
|
||||
this._timers.delete(handle);
|
||||
this._onUpdateSync(viewableIndices, onViewableItemsChanged, createViewToken);
|
||||
}, this._config.minimumViewTime);
|
||||
this._timers.add(handle);
|
||||
} else {
|
||||
this._onUpdateSync(viewableIndices, onViewableItemsChanged, createViewToken);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* clean-up cached _viewableIndices to evaluate changed items on next update
|
||||
*/
|
||||
resetViewableIndices() {
|
||||
this._viewableIndices = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Records that an interaction has happened even if there has been no scroll.
|
||||
*/
|
||||
recordInteraction() {
|
||||
this._hasInteracted = true;
|
||||
}
|
||||
|
||||
_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)
|
||||
);
|
||||
const prevItems = this._viewableItems;
|
||||
const nextItems = new Map(
|
||||
viewableIndicesToCheck.map(ii => {
|
||||
const viewable = createViewToken(ii, true);
|
||||
return [viewable.key, viewable];
|
||||
})
|
||||
);
|
||||
|
||||
const changed = [];
|
||||
for (const [key, viewable] of nextItems) {
|
||||
if (!prevItems.has(key)) {
|
||||
changed.push(viewable);
|
||||
}
|
||||
}
|
||||
for (const [key, viewable] of prevItems) {
|
||||
if (!nextItems.has(key)) {
|
||||
changed.push({ ...viewable, isViewable: false });
|
||||
}
|
||||
}
|
||||
if (changed.length > 0) {
|
||||
this._viewableItems = nextItems;
|
||||
onViewableItemsChanged({
|
||||
viewableItems: Array.from(nextItems.values()),
|
||||
changed,
|
||||
viewabilityConfig: this._config
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _isViewable(
|
||||
viewAreaMode: boolean,
|
||||
viewablePercentThreshold: number,
|
||||
top: number,
|
||||
bottom: number,
|
||||
viewportHeight: 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);
|
||||
return percent >= viewablePercentThreshold;
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
return top >= 0 && bottom <= viewportHeight && bottom > top;
|
||||
}
|
||||
|
||||
module.exports = ViewabilityHelper;
|
||||
1
packages/react-native-web/src/vendor/VirtualizeUtils/SHA
vendored
Normal file
1
packages/react-native-web/src/vendor/VirtualizeUtils/SHA
vendored
Normal file
@@ -0,0 +1 @@
|
||||
facebook/react-native@19a4a7d3cb6d00780ccbbbd7b0062896f64ab24d
|
||||
205
packages/react-native-web/src/vendor/VirtualizeUtils/index.js
vendored
Normal file
205
packages/react-native-web/src/vendor/VirtualizeUtils/index.js
vendored
Normal file
@@ -0,0 +1,205 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @providesModule VirtualizeUtils
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
/**
|
||||
* Used to find the indices of the frames that overlap the given offsets. Useful for finding the
|
||||
* items that bound different windows of content, such as the visible area or the buffered overscan
|
||||
* area.
|
||||
*/
|
||||
function elementsThatOverlapOffsets(
|
||||
offsets: Array<number>,
|
||||
itemCount: number,
|
||||
getFrameMetrics: (index: number) => { length: number, offset: number }
|
||||
): Array<number> {
|
||||
const out = [];
|
||||
let outLength = 0;
|
||||
for (let ii = 0; ii < itemCount; ii++) {
|
||||
const frame = getFrameMetrics(ii);
|
||||
const trailingOffset = frame.offset + frame.length;
|
||||
for (let kk = 0; kk < offsets.length; kk++) {
|
||||
if (out[kk] == null && trailingOffset >= offsets[kk]) {
|
||||
out[kk] = ii;
|
||||
outLength++;
|
||||
if (kk === offsets.length - 1) {
|
||||
invariant(
|
||||
outLength === offsets.length,
|
||||
'bad offsets input, should be in increasing order: %s',
|
||||
JSON.stringify(offsets)
|
||||
);
|
||||
return out;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the number of elements in the `next` range that are new compared to the `prev` range.
|
||||
* Handy for calculating how many new items will be rendered when the render window changes so we
|
||||
* can restrict the number of new items render at once so that content can appear on the screen
|
||||
* faster.
|
||||
*/
|
||||
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))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom logic for determining which items should be rendered given the current frame and scroll
|
||||
* metrics, as well as the previous render state. The algorithm may evolve over time, but generally
|
||||
* prioritizes the visible area first, then expands that with overscan regions ahead and behind,
|
||||
* biased in the direction of scroll.
|
||||
*/
|
||||
function computeWindowedRenderLimits(
|
||||
props: {
|
||||
data: any,
|
||||
getItemCount: (data: any) => number,
|
||||
maxToRenderPerBatch: number,
|
||||
windowSize: 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;
|
||||
const itemCount = getItemCount(data);
|
||||
if (itemCount === 0) {
|
||||
return prev;
|
||||
}
|
||||
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
|
||||
// too.
|
||||
const visibleBegin = Math.max(0, offset);
|
||||
const visibleEnd = visibleBegin + visibleLength;
|
||||
const overscanLength = (windowSize - 1) * visibleLength;
|
||||
|
||||
// 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 overscanBegin = Math.max(0, visibleBegin - (1 - leadFactor) * overscanLength);
|
||||
const overscanEnd = Math.max(0, visibleEnd + leadFactor * overscanLength);
|
||||
|
||||
const lastItemOffset = getFrameMetricsApprox(itemCount - 1).offset;
|
||||
if (lastItemOffset < overscanBegin) {
|
||||
// Entire list is before our overscan window
|
||||
return {
|
||||
first: Math.max(0, itemCount - 1 - maxToRenderPerBatch),
|
||||
last: itemCount - 1
|
||||
};
|
||||
}
|
||||
|
||||
// Find the indices that correspond to the items at the render boundaries we're targeting.
|
||||
let [overscanFirst, first, last, overscanLast] = elementsThatOverlapOffsets(
|
||||
[overscanBegin, visibleBegin, visibleEnd, overscanEnd],
|
||||
props.getItemCount(props.data),
|
||||
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 };
|
||||
|
||||
// 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
|
||||
// could be staring at white space for a long time waiting for a bunch of offscreen content to
|
||||
// render.
|
||||
let newCellCount = newRangeCount(prev, visible);
|
||||
|
||||
while (true) {
|
||||
if (first <= overscanFirst && last >= overscanLast) {
|
||||
// If we fill the entire overscan range, we're done.
|
||||
break;
|
||||
}
|
||||
const maxNewCells = newCellCount >= maxToRenderPerBatch;
|
||||
const firstWillAddMore = first <= prev.first || first > prev.last;
|
||||
const firstShouldIncrement = first > overscanFirst && (!maxNewCells || !firstWillAddMore);
|
||||
const lastWillAddMore = last >= prev.last || last < prev.first;
|
||||
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
|
||||
// possible, reducing render churn and keeping the rendered overscan range as large as
|
||||
// possible.
|
||||
break;
|
||||
}
|
||||
if (
|
||||
firstShouldIncrement &&
|
||||
!(fillPreference === 'after' && lastShouldIncrement && lastWillAddMore)
|
||||
) {
|
||||
if (firstWillAddMore) {
|
||||
newCellCount++;
|
||||
}
|
||||
first--;
|
||||
}
|
||||
if (
|
||||
lastShouldIncrement &&
|
||||
!(fillPreference === 'before' && firstShouldIncrement && firstWillAddMore)
|
||||
) {
|
||||
if (lastWillAddMore) {
|
||||
newCellCount++;
|
||||
}
|
||||
last++;
|
||||
}
|
||||
}
|
||||
if (
|
||||
!(
|
||||
last >= first &&
|
||||
first >= 0 &&
|
||||
last < itemCount &&
|
||||
first >= overscanFirst &&
|
||||
last <= overscanLast &&
|
||||
first <= visible.first &&
|
||||
last >= visible.last
|
||||
)
|
||||
) {
|
||||
throw new Error(
|
||||
'Bad window calculation ' +
|
||||
JSON.stringify({
|
||||
first,
|
||||
last,
|
||||
itemCount,
|
||||
overscanFirst,
|
||||
overscanLast,
|
||||
visible
|
||||
})
|
||||
);
|
||||
}
|
||||
return { first, last };
|
||||
}
|
||||
|
||||
const VirtualizeUtils = {
|
||||
computeWindowedRenderLimits,
|
||||
elementsThatOverlapOffsets,
|
||||
newRangeCount
|
||||
};
|
||||
|
||||
module.exports = VirtualizeUtils;
|
||||
1
packages/react-native-web/src/vendor/VirtualizedList/SHA
vendored
Normal file
1
packages/react-native-web/src/vendor/VirtualizedList/SHA
vendored
Normal file
@@ -0,0 +1 @@
|
||||
facebook/react-native@19a4a7d3cb6d00780ccbbbd7b0062896f64ab24d
|
||||
1593
packages/react-native-web/src/vendor/VirtualizedList/index.js
vendored
Normal file
1593
packages/react-native-web/src/vendor/VirtualizedList/index.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
18
packages/react-native-web/src/vendor/infoLog/index.js
vendored
Normal file
18
packages/react-native-web/src/vendor/infoLog/index.js
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @providesModule infoLog
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Intentional info-level logging for clear separation from ad-hoc console debug logging.
|
||||
*/
|
||||
function infoLog(...args) {
|
||||
return console.log(...args);
|
||||
}
|
||||
|
||||
module.exports = infoLog;
|
||||
Reference in New Issue
Block a user