diff --git a/packages/react-native-web/src/exports/ListView/ListViewPropTypes.js b/packages/react-native-web/src/exports/ListView/ListViewPropTypes.js deleted file mode 100644 index fc01905d..00000000 --- a/packages/react-native-web/src/exports/ListView/ListViewPropTypes.js +++ /dev/null @@ -1,22 +0,0 @@ -import ListViewDataSource from './ListViewDataSource'; -import ScrollView from '../ScrollView'; -import { arrayOf, bool, func, instanceOf, number } from 'prop-types'; - -export default { - ...ScrollView.propTypes, - dataSource: instanceOf(ListViewDataSource).isRequired, - renderSeparator: func, - renderRow: func.isRequired, - initialListSize: number, - onEndReached: func, - onEndReachedThreshold: number, - pageSize: number, - renderFooter: func, - renderHeader: func, - renderSectionHeader: func, - renderScrollComponent: func.isRequired, - scrollRenderAheadDistance: number, - onChangeVisibleRows: func, - removeClippedSubviews: bool, - stickyHeaderIndices: arrayOf(number) -}; diff --git a/packages/react-native-web/src/exports/ListView/index.js b/packages/react-native-web/src/exports/ListView/index.js index 3ab42eed..68f4827d 100644 --- a/packages/react-native-web/src/exports/ListView/index.js +++ b/packages/react-native-web/src/exports/ListView/index.js @@ -1,444 +1,11 @@ -import applyNativeMethods from '../../modules/applyNativeMethods'; -import ListViewDataSource from './ListViewDataSource'; -import ListViewPropTypes from './ListViewPropTypes'; -import ScrollView from '../ScrollView'; -import StaticRenderer from '../../modules/StaticRenderer'; -import React, { Component } from 'react'; -import isEmpty from 'fbjs/lib/isEmpty'; -import requestAnimationFrame from 'fbjs/lib/requestAnimationFrame'; +/** + * 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 + */ -const DEFAULT_PAGE_SIZE = 1; -const DEFAULT_INITIAL_ROWS = 10; -const DEFAULT_SCROLL_RENDER_AHEAD = 1000; -const DEFAULT_END_REACHED_THRESHOLD = 1000; -const DEFAULT_SCROLL_CALLBACK_THROTTLE = 50; - -class ListView extends Component { - static propTypes = ListViewPropTypes; - - static defaultProps = { - initialListSize: DEFAULT_INITIAL_ROWS, - pageSize: DEFAULT_PAGE_SIZE, - renderScrollComponent: props => , - scrollRenderAheadDistance: DEFAULT_SCROLL_RENDER_AHEAD, - onEndReachedThreshold: DEFAULT_END_REACHED_THRESHOLD, - scrollEventThrottle: DEFAULT_SCROLL_CALLBACK_THROTTLE, - removeClippedSubviews: true, - stickyHeaderIndices: [] - }; - - static DataSource = ListViewDataSource; - - constructor(props) { - super(props); - this.state = { - curRenderedRowsCount: this.props.initialListSize, - highlightedRow: {} - }; - this.onRowHighlighted = (sectionId, rowId) => this._onRowHighlighted(sectionId, rowId); - this.scrollProperties = {}; - } - - componentWillMount() { - // this data should never trigger a render pass, so don't put in state - this.scrollProperties = { - visibleLength: null, - contentLength: null, - offset: 0 - }; - this._childFrames = []; - this._visibleRows = {}; - this._prevRenderedRowsCount = 0; - this._sentEndForContentLength = null; - } - - componentDidMount() { - // do this in animation frame until componentDidMount actually runs after - // the component is laid out - requestAnimationFrame(() => { - this._measureAndUpdateScrollProps(); - }); - } - - componentWillReceiveProps(nextProps: Object) { - if ( - this.props.dataSource !== nextProps.dataSource || - this.props.initialListSize !== nextProps.initialListSize - ) { - this.setState( - (state, props) => { - this._prevRenderedRowsCount = 0; - return { - curRenderedRowsCount: Math.min( - Math.max(state.curRenderedRowsCount, props.initialListSize), - props.enableEmptySections - ? props.dataSource.getRowAndSectionCount() - : props.dataSource.getRowCount() - ) - }; - }, - () => this._renderMoreRowsIfNeeded() - ); - } - } - - componentDidUpdate() { - requestAnimationFrame(() => { - this._measureAndUpdateScrollProps(); - }); - } - - getScrollResponder() { - return this._scrollViewRef && this._scrollViewRef.getScrollResponder(); - } - - scrollTo(...args) { - return this._scrollViewRef && this._scrollViewRef.scrollTo(...args); - } - - setNativeProps(props) { - return this._scrollViewRef && this._scrollViewRef.setNativeProps(props); - } - - _onRowHighlighted = (sectionId, rowId) => { - this.setState({ highlightedRow: { sectionId, rowId } }); - }; - - renderSectionHeaderFn = (data, sectionID) => { - return () => this.props.renderSectionHeader(data, sectionID); - }; - - renderRowFn = (data, sectionID, rowID) => { - return () => this.props.renderRow(data, sectionID, rowID, this._onRowHighlighted); - }; - - render() { - const children = []; - - const { - dataSource, - enableEmptySections, - renderFooter, - renderHeader, - renderScrollComponent, - renderSectionHeader, - renderSeparator, - /* eslint-disable */ - initialListSize, - onChangeVisibleRows, - onEndReached, - onEndReachedThreshold, - onKeyboardDidHide, - onKeyboardDidShow, - onKeyboardWillHide, - onKeyboardWillShow, - pageSize, - renderRow, - scrollRenderAheadDistance, - stickyHeaderIndices, - /* eslint-enable */ - ...scrollProps - } = this.props; - - const allRowIDs = dataSource.rowIdentities; - let rowCount = 0; - const sectionHeaderIndices = []; - - const header = renderHeader && renderHeader(); - const footer = renderFooter && renderFooter(); - let totalIndex = header ? 1 : 0; - - for (let sectionIdx = 0; sectionIdx < allRowIDs.length; sectionIdx++) { - const sectionID = dataSource.sectionIdentities[sectionIdx]; - const rowIDs = allRowIDs[sectionIdx]; - if (rowIDs.length === 0) { - if (enableEmptySections === undefined) { - const warning = require('fbjs/lib/warning'); - warning( - false, - 'In next release empty section headers will be rendered.' + - " In this release you can use 'enableEmptySections' flag to render empty section headers." - ); - continue; - } else { - const invariant = require('fbjs/lib/invariant'); - invariant( - enableEmptySections, - "In next release 'enableEmptySections' flag will be deprecated," + - ' empty section headers will always be rendered. If empty section headers' + - ' are not desirable their indices should be excluded from sectionIDs object.' + - " In this release 'enableEmptySections' may only have value 'true'" + - ' to allow empty section headers rendering.' - ); - } - } - - if (renderSectionHeader) { - const shouldUpdateHeader = - rowCount >= this._prevRenderedRowsCount && - dataSource.sectionHeaderShouldUpdate(sectionIdx); - children.push( - - ); - sectionHeaderIndices.push(totalIndex++); - } - - for (let rowIdx = 0; rowIdx < rowIDs.length; rowIdx++) { - const rowID = rowIDs[rowIdx]; - const comboID = `${sectionID}_${rowID}`; - const shouldUpdateRow = - rowCount >= this._prevRenderedRowsCount && dataSource.rowShouldUpdate(sectionIdx, rowIdx); - const row = ( - - ); - children.push(row); - totalIndex++; - - if ( - renderSeparator && - (rowIdx !== rowIDs.length - 1 || sectionIdx === allRowIDs.length - 1) - ) { - const adjacentRowHighlighted = - this.state.highlightedRow.sectionID === sectionID && - (this.state.highlightedRow.rowID === rowID || - this.state.highlightedRow.rowID === rowIDs[rowIdx + 1]); - const separator = renderSeparator(sectionID, rowID, adjacentRowHighlighted); - if (separator) { - children.push(separator); - totalIndex++; - } - } - if (++rowCount === this.state.curRenderedRowsCount) { - break; - } - } - if (rowCount >= this.state.curRenderedRowsCount) { - break; - } - } - scrollProps.onScroll = this._onScroll; - - return React.cloneElement( - renderScrollComponent(scrollProps), - { - ref: this._setScrollViewRef, - onContentSizeChange: this._onContentSizeChange, - onLayout: this._onLayout - }, - header, - children, - footer - ); - } - - _measureAndUpdateScrollProps() { - const scrollComponent = this.getScrollResponder(); - if (!scrollComponent || !scrollComponent.getInnerViewNode) { - return; - } - - this._updateVisibleRows(); - } - - _onLayout = (event: Object) => { - const { width, height } = event.nativeEvent.layout; - const visibleLength = !this.props.horizontal ? height : width; - if (visibleLength !== this.scrollProperties.visibleLength) { - this.scrollProperties.visibleLength = visibleLength; - this._updateVisibleRows(); - this._renderMoreRowsIfNeeded(); - } - this.props.onLayout && this.props.onLayout(event); - }; - - _updateVisibleRows(updatedFrames?: Array) { - if (!this.props.onChangeVisibleRows) { - return; // No need to compute visible rows if there is no callback - } - if (updatedFrames) { - updatedFrames.forEach(newFrame => { - this._childFrames[newFrame.index] = Object.assign({}, newFrame); - }); - } - const isVertical = !this.props.horizontal; - const dataSource = this.props.dataSource; - const visibleMin = this.scrollProperties.offset; - const visibleMax = visibleMin + this.scrollProperties.visibleLength; - const allRowIDs = dataSource.rowIdentities; - - const header = this.props.renderHeader && this.props.renderHeader(); - let totalIndex = header ? 1 : 0; - let visibilityChanged = false; - const changedRows = {}; - for (let sectionIdx = 0; sectionIdx < allRowIDs.length; sectionIdx++) { - const rowIDs = allRowIDs[sectionIdx]; - if (rowIDs.length === 0) { - continue; - } - const sectionID = dataSource.sectionIdentities[sectionIdx]; - if (this.props.renderSectionHeader) { - totalIndex++; - } - let visibleSection = this._visibleRows[sectionID]; - if (!visibleSection) { - visibleSection = {}; - } - for (let rowIdx = 0; rowIdx < rowIDs.length; rowIdx++) { - const rowID = rowIDs[rowIdx]; - const frame = this._childFrames[totalIndex]; - totalIndex++; - if ( - this.props.renderSeparator && - (rowIdx !== rowIDs.length - 1 || sectionIdx === allRowIDs.length - 1) - ) { - totalIndex++; - } - if (!frame) { - break; - } - const rowVisible = visibleSection[rowID]; - const min = isVertical ? frame.y : frame.x; - const max = min + (isVertical ? frame.height : frame.width); - if ((!min && !max) || min === max) { - break; - } - if (min > visibleMax || max < visibleMin) { - if (rowVisible) { - visibilityChanged = true; - delete visibleSection[rowID]; - if (!changedRows[sectionID]) { - changedRows[sectionID] = {}; - } - changedRows[sectionID][rowID] = false; - } - } else if (!rowVisible) { - visibilityChanged = true; - visibleSection[rowID] = true; - if (!changedRows[sectionID]) { - changedRows[sectionID] = {}; - } - changedRows[sectionID][rowID] = true; - } - } - if (!isEmpty(visibleSection)) { - this._visibleRows[sectionID] = visibleSection; - } else if (this._visibleRows[sectionID]) { - delete this._visibleRows[sectionID]; - } - } - visibilityChanged && this.props.onChangeVisibleRows(this._visibleRows, changedRows); - } - - _onContentSizeChange = (width: number, height: number) => { - const contentLength = !this.props.horizontal ? height : width; - if (contentLength !== this.scrollProperties.contentLength) { - this.scrollProperties.contentLength = contentLength; - this._updateVisibleRows(); - this._renderMoreRowsIfNeeded(); - } - this.props.onContentSizeChange && this.props.onContentSizeChange(width, height); - }; - - _getDistanceFromEnd(scrollProperties: Object) { - return ( - scrollProperties.contentLength - scrollProperties.visibleLength - scrollProperties.offset - ); - } - - _maybeCallOnEndReached(event?: Object) { - if ( - this.props.onEndReached && - this.scrollProperties.contentLength !== this._sentEndForContentLength && - this._getDistanceFromEnd(this.scrollProperties) < this.props.onEndReachedThreshold && - this.state.curRenderedRowsCount === - (this.props.enableEmptySections - ? this.props.dataSource.getRowAndSectionCount() - : this.props.dataSource.getRowCount()) - ) { - this._sentEndForContentLength = this.scrollProperties.contentLength; - this.props.onEndReached(event); - return true; - } - return false; - } - - _renderMoreRowsIfNeeded() { - if ( - this.scrollProperties.contentLength === null || - this.scrollProperties.visibleLength === null || - this.state.curRenderedRowsCount === - (this.props.enableEmptySections - ? this.props.dataSource.getRowAndSectionCount() - : this.props.dataSource.getRowCount()) - ) { - this._maybeCallOnEndReached(); - return; - } - - const distanceFromEnd = this._getDistanceFromEnd(this.scrollProperties); - if (distanceFromEnd < this.props.scrollRenderAheadDistance) { - this._pageInNewRows(); - } - } - - _pageInNewRows() { - this.setState( - (state, props) => { - const rowsToRender = Math.min( - state.curRenderedRowsCount + props.pageSize, - props.enableEmptySections - ? props.dataSource.getRowAndSectionCount() - : props.dataSource.getRowCount() - ); - this._prevRenderedRowsCount = state.curRenderedRowsCount; - return { - curRenderedRowsCount: rowsToRender - }; - }, - () => { - this._measureAndUpdateScrollProps(); - this._prevRenderedRowsCount = this.state.curRenderedRowsCount; - } - ); - } - - _onScroll = (e: Object) => { - const isVertical = !this.props.horizontal; - this.scrollProperties.visibleLength = - e.nativeEvent.layoutMeasurement[isVertical ? 'height' : 'width']; - this.scrollProperties.contentLength = - e.nativeEvent.contentSize[isVertical ? 'height' : 'width']; - this.scrollProperties.offset = e.nativeEvent.contentOffset[isVertical ? 'y' : 'x']; - this._updateVisibleRows(e.nativeEvent.updatedChildFrames); - if (!this._maybeCallOnEndReached(e)) { - this._renderMoreRowsIfNeeded(); - } - - if ( - this.props.onEndReached && - this._getDistanceFromEnd(this.scrollProperties) > this.props.onEndReachedThreshold - ) { - // Scrolled out of the end zone, so it should be able to trigger again. - this._sentEndForContentLength = null; - } - - this.props.onScroll && this.props.onScroll(e); - }; - - _setScrollViewRef = component => { - this._scrollViewRef = component; - }; -} - -export default applyNativeMethods(ListView); +import ListView from '../../vendor/react-native/ListView'; +export default ListView; diff --git a/packages/react-native-web/src/exports/ListView/ListViewDataSource.js b/packages/react-native-web/src/vendor/react-native/ListView/ListViewDataSource.js similarity index 76% rename from packages/react-native-web/src/exports/ListView/ListViewDataSource.js rename to packages/react-native-web/src/vendor/react-native/ListView/ListViewDataSource.js index de1587cf..5b7e84c6 100644 --- a/packages/react-native-web/src/exports/ListView/ListViewDataSource.js +++ b/packages/react-native-web/src/vendor/react-native/ListView/ListViewDataSource.js @@ -1,41 +1,31 @@ -/* eslint-disable */ /** - * Copyright (c) 2015, Facebook, Inc. All rights reserved. + * Copyright (c) 2015-present, Facebook, Inc. * - * Facebook, Inc. ("Facebook") owns all right, title and interest, including - * all intellectual property and other proprietary rights, in and to the React - * Native CustomComponents software (the "Software"). Subject to your - * compliance with these terms, you are hereby granted a non-exclusive, - * worldwide, royalty-free copyright license to (1) use and copy the Software; - * and (2) reproduce and distribute the Software as part of your own software - * ("Your Software"). Facebook reserves all rights not expressly granted to - * you in this license agreement. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. * - * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. - * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR - * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * @typechecks + * @providesModule ListViewDataSource * @flow + * @format */ +'use strict'; import invariant from 'fbjs/lib/invariant'; -import isEmpty from 'fbjs/lib/isEmpty'; +import isEmpty from '../isEmpty'; import warning from 'fbjs/lib/warning'; -function defaultGetRowData(dataBlob: any, sectionID: number | string, rowID: number | string): any { +function defaultGetRowData( + dataBlob: any, + sectionID: number | string, + rowID: number | string, +): any { return dataBlob[sectionID][rowID]; } -function defaultGetSectionHeaderData(dataBlob: any, sectionID: number | string): any { +function defaultGetSectionHeaderData( + dataBlob: any, + sectionID: number | string, +): any { return dataBlob[sectionID]; } @@ -43,9 +33,9 @@ type differType = (data1: any, data2: any) => boolean; type ParamType = { rowHasChanged: differType, - getRowData: ?typeof defaultGetRowData, - sectionHeaderHasChanged: ?differType, - getSectionHeaderData: ?typeof defaultGetSectionHeaderData + getRowData?: ?typeof defaultGetRowData, + sectionHeaderHasChanged?: ?differType, + getSectionHeaderData?: ?typeof defaultGetSectionHeaderData, }; /** @@ -70,7 +60,7 @@ type ParamType = { * * ``` * getInitialState: function() { - * var ds = new ListViewDataSource({rowHasChanged: this._rowHasChanged}); + * var ds = new ListView.DataSource({rowHasChanged: this._rowHasChanged}); * return {ds}; * }, * _onDataArrived(newData) { @@ -111,12 +101,13 @@ class ListViewDataSource { constructor(params: ParamType) { invariant( params && typeof params.rowHasChanged === 'function', - 'Must provide a rowHasChanged function.' + 'Must provide a rowHasChanged function.', ); this._rowHasChanged = params.rowHasChanged; this._getRowData = params.getRowData || defaultGetRowData; this._sectionHeaderHasChanged = params.sectionHeaderHasChanged; - this._getSectionHeaderData = params.getSectionHeaderData || defaultGetSectionHeaderData; + this._getSectionHeaderData = + params.getSectionHeaderData || defaultGetSectionHeaderData; this._dataBlob = null; this._dirtyRows = []; @@ -135,7 +126,7 @@ class ListViewDataSource { * construction an extractor to get the interesting information was defined * (or the default was used). * - * The `rowIdentities` is is a 2D array of identifiers for rows. + * The `rowIdentities` is a 2D array of identifiers for rows. * ie. [['a1', 'a2'], ['b1', 'b2', 'b3'], ...]. If not provided, it's * assumed that the keys of the section data are the row identities. * @@ -146,14 +137,14 @@ class ListViewDataSource { * this function as the `dataBlob`. */ cloneWithRows( - dataBlob: Array | { [key: string]: any }, - rowIdentities: ?Array + dataBlob: $ReadOnlyArray | {+[key: string]: any}, + rowIdentities: ?$ReadOnlyArray, ): ListViewDataSource { - var rowIds = rowIdentities ? [rowIdentities] : null; + var rowIds = rowIdentities ? [[...rowIdentities]] : null; if (!this._sectionHeaderHasChanged) { this._sectionHeaderHasChanged = () => false; } - return this.cloneWithRowsAndSections({ s1: dataBlob }, ['s1'], rowIds); + return this.cloneWithRowsAndSections({s1: dataBlob}, ['s1'], rowIds); } /** @@ -161,26 +152,41 @@ class ListViewDataSource { * you also specify what your `sectionIdentities` are. If you don't care * about sections you should safely be able to use `cloneWithRows`. * - * `sectionIdentities` is an array of identifiers for sections. - * ie. ['s1', 's2', ...]. If not provided, it's assumed that the + * `sectionIdentities` is an array of identifiers for sections. + * ie. ['s1', 's2', ...]. The identifiers should correspond to the keys or array indexes + * of the data you wish to include. If not provided, it's assumed that the * keys of dataBlob are the section identities. * * Note: this returns a new object! + * + * ``` + * const dataSource = ds.cloneWithRowsAndSections({ + * addresses: ['row 1', 'row 2'], + * phone_numbers: ['data 1', 'data 2'], + * }, ['phone_numbers']); + * ``` */ cloneWithRowsAndSections( dataBlob: any, sectionIdentities: ?Array, - rowIdentities: ?Array> + rowIdentities: ?Array>, ): ListViewDataSource { invariant( typeof this._sectionHeaderHasChanged === 'function', - 'Must provide a sectionHeaderHasChanged function with section data.' + 'Must provide a sectionHeaderHasChanged function with section data.', ); + invariant( + !sectionIdentities || + !rowIdentities || + sectionIdentities.length === rowIdentities.length, + 'row and section ids lengths must be the same', + ); + var newSource = new ListViewDataSource({ getRowData: this._getRowData, getSectionHeaderData: this._getSectionHeaderData, rowHasChanged: this._rowHasChanged, - sectionHeaderHasChanged: this._sectionHeaderHasChanged + sectionHeaderHasChanged: this._sectionHeaderHasChanged, }); newSource._dataBlob = dataBlob; if (sectionIdentities) { @@ -198,15 +204,29 @@ class ListViewDataSource { } newSource._cachedRowCount = countRows(newSource.rowIdentities); - newSource._calculateDirtyArrays(this._dataBlob, this.sectionIdentities, this.rowIdentities); + newSource._calculateDirtyArrays( + this._dataBlob, + this.sectionIdentities, + this.rowIdentities, + ); return newSource; } + /** + * Returns the total number of rows in the data source. + * + * If you are specifying the rowIdentities or sectionIdentities, then `getRowCount` will return the number of rows in the filtered data source. + */ getRowCount(): number { return this._cachedRowCount; } + /** + * Returns the total number of rows in the data source (see `getRowCount` for how this is calculated) plus the number of sections in the data. + * + * If you are specifying the rowIdentities or sectionIdentities, then `getRowAndSectionCount` will return the number of rows & sections in the filtered data source. + */ getRowAndSectionCount(): number { return this._cachedRowCount + this.sectionIdentities.length; } @@ -218,7 +238,7 @@ class ListViewDataSource { var needsUpdate = this._dirtyRows[sectionIndex][rowIndex]; warning( needsUpdate !== undefined, - 'missing dirtyBit for section, row: ' + sectionIndex + ', ' + rowIndex + 'missing dirtyBit for section, row: ' + sectionIndex + ', ' + rowIndex, ); return needsUpdate; } @@ -231,7 +251,7 @@ class ListViewDataSource { var rowID = this.rowIdentities[sectionIndex][rowIndex]; warning( sectionID !== undefined && rowID !== undefined, - 'rendering invalid section, row: ' + sectionIndex + ', ' + rowIndex + 'rendering invalid section, row: ' + sectionIndex + ', ' + rowIndex, ); return this._getRowData(this._dataBlob, sectionID, rowID); } @@ -284,7 +304,10 @@ class ListViewDataSource { */ sectionHeaderShouldUpdate(sectionIndex: number): boolean { var needsUpdate = this._dirtySections[sectionIndex]; - warning(needsUpdate !== undefined, 'missing dirtyBit for section: ' + sectionIndex); + warning( + needsUpdate !== undefined, + 'missing dirtyBit for section: ' + sectionIndex, + ); return needsUpdate; } @@ -296,7 +319,10 @@ class ListViewDataSource { return null; } var sectionID = this.sectionIdentities[sectionIndex]; - warning(sectionID !== undefined, 'renderSection called on invalid section: ' + sectionIndex); + warning( + sectionID !== undefined, + 'renderSection called on invalid section: ' + sectionIndex, + ); return this._getSectionHeaderData(this._dataBlob, sectionID); } @@ -322,14 +348,17 @@ class ListViewDataSource { _calculateDirtyArrays( prevDataBlob: any, prevSectionIDs: Array, - prevRowIDs: Array> + prevRowIDs: Array>, ): void { // construct a hashmap of the existing (old) id arrays var prevSectionsHash = keyedDictionaryFromArray(prevSectionIDs); var prevRowsHash = {}; for (var ii = 0; ii < prevRowIDs.length; ii++) { var sectionID = prevSectionIDs[ii]; - warning(!prevRowsHash[sectionID], 'SectionID appears more than once: ' + sectionID); + warning( + !prevRowsHash[sectionID], + 'SectionID appears more than once: ' + sectionID, + ); prevRowsHash[sectionID] = keyedDictionaryFromArray(prevRowIDs[ii]); } @@ -346,13 +375,17 @@ class ListViewDataSource { if (!dirty && sectionHeaderHasChanged) { dirty = sectionHeaderHasChanged( this._getSectionHeaderData(prevDataBlob, sectionID), - this._getSectionHeaderData(this._dataBlob, sectionID) + this._getSectionHeaderData(this._dataBlob, sectionID), ); } this._dirtySections.push(!!dirty); this._dirtyRows[sIndex] = []; - for (var rIndex = 0; rIndex < this.rowIdentities[sIndex].length; rIndex++) { + for ( + var rIndex = 0; + rIndex < this.rowIdentities[sIndex].length; + rIndex++ + ) { var rowID = this.rowIdentities[sIndex][rIndex]; // dirty if the section is new, row is new or _rowHasChanged is true dirty = @@ -360,7 +393,7 @@ class ListViewDataSource { !prevRowsHash[sectionID][rowID] || this._rowHasChanged( this._getRowData(prevDataBlob, sectionID, rowID), - this._getRowData(this._dataBlob, sectionID, rowID) + this._getRowData(this._dataBlob, sectionID, rowID), ); this._dirtyRows[sIndex].push(!!dirty); } diff --git a/packages/react-native-web/src/vendor/react-native/ListView/cloneReferencedElement.js b/packages/react-native-web/src/vendor/react-native/ListView/cloneReferencedElement.js new file mode 100644 index 00000000..93267ff2 --- /dev/null +++ b/packages/react-native-web/src/vendor/react-native/ListView/cloneReferencedElement.js @@ -0,0 +1,34 @@ +'use strict'; + +import React from 'react'; + +const __DEV__ = process.env.NODE_ENV !== 'production'; + +function cloneReferencedElement(element, config, ...children) { + let cloneRef = config.ref; + let originalRef = element.ref; + if (originalRef == null || cloneRef == null) { + return React.cloneElement(element, config, ...children); + } + + if (typeof originalRef !== 'function') { + if (__DEV__) { + console.warn( + 'Cloning an element with a ref that will be overwritten because it ' + + 'is not a function. Use a composable callback-style ref instead. ' + + 'Ignoring ref: ' + originalRef, + ); + } + return React.cloneElement(element, config, ...children); + } + + return React.cloneElement(element, { + ...config, + ref(component) { + cloneRef(component); + originalRef(component); + }, + }, ...children); +} + +export default cloneReferencedElement; diff --git a/packages/react-native-web/src/vendor/react-native/ListView/index.js b/packages/react-native-web/src/vendor/react-native/ListView/index.js new file mode 100644 index 00000000..c339af50 --- /dev/null +++ b/packages/react-native-web/src/vendor/react-native/ListView/index.js @@ -0,0 +1,762 @@ +/** + * 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 ListView + * @flow + * @format + */ +'use strict'; + +import ListViewDataSource from './ListViewDataSource'; +import Platform from '../../../exports/Platform'; +import React from 'react'; +import PropTypes from 'prop-types'; +import findNodeHandle from '../../../exports/findNodeHandle'; +import NativeModules from '../../../exports/NativeModules'; +import ScrollView from '../../../exports/ScrollView'; +import ScrollResponder from '../../../modules/ScrollResponder'; +import StaticRenderer from '../StaticRenderer'; +import TimerMixin from 'react-timer-mixin'; +import View from '../../../exports/View'; + +import cloneReferencedElement from './cloneReferencedElement'; +import createReactClass from 'create-react-class'; +import isEmpty from '../isEmpty'; + +var merge = (...args) => Object.assign({}, ...args); +var RCTScrollViewManager = NativeModules.ScrollViewManager; + +var DEFAULT_PAGE_SIZE = 1; +var DEFAULT_INITIAL_ROWS = 10; +var DEFAULT_SCROLL_RENDER_AHEAD = 1000; +var DEFAULT_END_REACHED_THRESHOLD = 1000; +var DEFAULT_SCROLL_CALLBACK_THROTTLE = 50; + +/** + * DEPRECATED - use one of the new list components, such as [`FlatList`](docs/flatlist.html) + * or [`SectionList`](docs/sectionlist.html) for bounded memory use, fewer bugs, + * better performance, an easier to use API, and more features. Check out this + * [blog post](https://facebook.github.io/react-native/blog/2017/03/13/better-list-views.html) + * for more details. + * + * ListView - A core component designed for efficient display of vertically + * scrolling lists of changing data. The minimal API is to create a + * [`ListView.DataSource`](docs/listviewdatasource.html), populate it with a simple + * array of data blobs, and instantiate a `ListView` component with that data + * source and a `renderRow` callback which takes a blob from the data array and + * returns a renderable component. + * + * Minimal example: + * + * ``` + * class MyComponent extends Component { + * constructor() { + * super(); + * const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2}); + * this.state = { + * dataSource: ds.cloneWithRows(['row 1', 'row 2']), + * }; + * } + * + * render() { + * return ( + * {rowData}} + * /> + * ); + * } + * } + * ``` + * + * ListView also supports more advanced features, including sections with sticky + * section headers, header and footer support, callbacks on reaching the end of + * the available data (`onEndReached`) and on the set of rows that are visible + * in the device viewport change (`onChangeVisibleRows`), and several + * performance optimizations. + * + * There are a few performance operations designed to make ListView scroll + * smoothly while dynamically loading potentially very large (or conceptually + * infinite) data sets: + * + * * Only re-render changed rows - the rowHasChanged function provided to the + * data source tells the ListView if it needs to re-render a row because the + * source data has changed - see ListViewDataSource for more details. + * + * * Rate-limited row rendering - By default, only one row is rendered per + * event-loop (customizable with the `pageSize` prop). This breaks up the + * work into smaller chunks to reduce the chance of dropping frames while + * rendering rows. + */ + +var ListView = createReactClass({ + displayName: 'ListView', + _childFrames: ([]: Array), + _sentEndForContentLength: (null: ?number), + _scrollComponent: (null: any), + _prevRenderedRowsCount: 0, + _visibleRows: ({}: Object), + scrollProperties: ({}: Object), + + mixins: [ScrollResponder.Mixin, TimerMixin], + + statics: { + DataSource: ListViewDataSource, + }, + + /** + * You must provide a renderRow function. If you omit any of the other render + * functions, ListView will simply skip rendering them. + * + * - renderRow(rowData, sectionID, rowID, highlightRow); + * - renderSectionHeader(sectionData, sectionID); + */ + propTypes: { + ...ScrollView.propTypes, + /** + * An instance of [ListView.DataSource](docs/listviewdatasource.html) to use + */ + dataSource: PropTypes.instanceOf(ListViewDataSource).isRequired, + /** + * (sectionID, rowID, adjacentRowHighlighted) => renderable + * + * If provided, a renderable component to be rendered as the separator + * below each row but not the last row if there is a section header below. + * Take a sectionID and rowID of the row above and whether its adjacent row + * is highlighted. + */ + renderSeparator: PropTypes.func, + /** + * (rowData, sectionID, rowID, highlightRow) => renderable + * + * Takes a data entry from the data source and its ids and should return + * a renderable component to be rendered as the row. By default the data + * is exactly what was put into the data source, but it's also possible to + * provide custom extractors. ListView can be notified when a row is + * being highlighted by calling `highlightRow(sectionID, rowID)`. This + * sets a boolean value of adjacentRowHighlighted in renderSeparator, allowing you + * to control the separators above and below the highlighted row. The highlighted + * state of a row can be reset by calling highlightRow(null). + */ + renderRow: PropTypes.func.isRequired, + /** + * How many rows to render on initial component mount. Use this to make + * it so that the first screen worth of data appears at one time instead of + * over the course of multiple frames. + */ + initialListSize: PropTypes.number.isRequired, + /** + * Called when all rows have been rendered and the list has been scrolled + * to within onEndReachedThreshold of the bottom. The native scroll + * event is provided. + */ + onEndReached: PropTypes.func, + /** + * Threshold in pixels (virtual, not physical) for calling onEndReached. + */ + onEndReachedThreshold: PropTypes.number.isRequired, + /** + * Number of rows to render per event loop. Note: if your 'rows' are actually + * cells, i.e. they don't span the full width of your view (as in the + * ListViewGridLayoutExample), you should set the pageSize to be a multiple + * of the number of cells per row, otherwise you're likely to see gaps at + * the edge of the ListView as new pages are loaded. + */ + pageSize: PropTypes.number.isRequired, + /** + * () => renderable + * + * The header and footer are always rendered (if these props are provided) + * on every render pass. If they are expensive to re-render, wrap them + * in StaticContainer or other mechanism as appropriate. Footer is always + * at the bottom of the list, and header at the top, on every render pass. + * In a horizontal ListView, the header is rendered on the left and the + * footer on the right. + */ + renderFooter: PropTypes.func, + renderHeader: PropTypes.func, + /** + * (sectionData, sectionID) => renderable + * + * If provided, a header is rendered for this section. + */ + renderSectionHeader: PropTypes.func, + /** + * (props) => renderable + * + * A function that returns the scrollable component in which the list rows + * are rendered. Defaults to returning a ScrollView with the given props. + */ + renderScrollComponent: PropTypes.func.isRequired, + /** + * How early to start rendering rows before they come on screen, in + * pixels. + */ + scrollRenderAheadDistance: PropTypes.number.isRequired, + /** + * (visibleRows, changedRows) => void + * + * Called when the set of visible rows changes. `visibleRows` maps + * { sectionID: { rowID: true }} for all the visible rows, and + * `changedRows` maps { sectionID: { rowID: true | false }} for the rows + * that have changed their visibility, with true indicating visible, and + * false indicating the view has moved out of view. + */ + onChangeVisibleRows: PropTypes.func, + /** + * A performance optimization for improving scroll perf of + * large lists, used in conjunction with overflow: 'hidden' on the row + * containers. This is enabled by default. + */ + removeClippedSubviews: PropTypes.bool, + /** + * Makes the sections headers sticky. The sticky behavior means that it + * will scroll with the content at the top of the section until it reaches + * the top of the screen, at which point it will stick to the top until it + * is pushed off the screen by the next section header. This property is + * not supported in conjunction with `horizontal={true}`. Only enabled by + * default on iOS because of typical platform standards. + */ + stickySectionHeadersEnabled: PropTypes.bool, + /** + * An array of child indices determining which children get docked to the + * top of the screen when scrolling. For example, passing + * `stickyHeaderIndices={[0]}` will cause the first child to be fixed to the + * top of the scroll view. This property is not supported in conjunction + * with `horizontal={true}`. + */ + stickyHeaderIndices: PropTypes.arrayOf(PropTypes.number).isRequired, + /** + * Flag indicating whether empty section headers should be rendered. In the future release + * empty section headers will be rendered by default, and the flag will be deprecated. + * If empty sections are not desired to be rendered their indices should be excluded from sectionID object. + */ + enableEmptySections: PropTypes.bool, + }, + + /** + * Exports some data, e.g. for perf investigations or analytics. + */ + getMetrics: function() { + return { + contentLength: this.scrollProperties.contentLength, + totalRows: this.props.enableEmptySections + ? this.props.dataSource.getRowAndSectionCount() + : this.props.dataSource.getRowCount(), + renderedRows: this.state.curRenderedRowsCount, + visibleRows: Object.keys(this._visibleRows).length, + }; + }, + + /** + * Provides a handle to the underlying scroll responder. + * Note that `this._scrollComponent` might not be a `ScrollView`, so we + * need to check that it responds to `getScrollResponder` before calling it. + */ + getScrollResponder: function() { + if (this._scrollComponent && this._scrollComponent.getScrollResponder) { + return this._scrollComponent.getScrollResponder(); + } + }, + + getScrollableNode: function() { + if (this._scrollComponent && this._scrollComponent.getScrollableNode) { + return this._scrollComponent.getScrollableNode(); + } else { + return findNodeHandle(this._scrollComponent); + } + }, + + /** + * Scrolls to a given x, y offset, either immediately or with a smooth animation. + * + * See `ScrollView#scrollTo`. + */ + scrollTo: function(...args: Array) { + if (this._scrollComponent && this._scrollComponent.scrollTo) { + this._scrollComponent.scrollTo(...args); + } + }, + + /** + * If this is a vertical ListView scrolls to the bottom. + * If this is a horizontal ListView scrolls to the right. + * + * Use `scrollToEnd({animated: true})` for smooth animated scrolling, + * `scrollToEnd({animated: false})` for immediate scrolling. + * If no options are passed, `animated` defaults to true. + * + * See `ScrollView#scrollToEnd`. + */ + scrollToEnd: function(options?: ?{animated?: ?boolean}) { + if (this._scrollComponent) { + if (this._scrollComponent.scrollToEnd) { + this._scrollComponent.scrollToEnd(options); + } else { + console.warn( + 'The scroll component used by the ListView does not support ' + + 'scrollToEnd. Check the renderScrollComponent prop of your ListView.', + ); + } + } + }, + + /** + * Displays the scroll indicators momentarily. + * + * @platform ios + */ + flashScrollIndicators: function() { + if (this._scrollComponent && this._scrollComponent.flashScrollIndicators) { + this._scrollComponent.flashScrollIndicators(); + } + }, + + setNativeProps: function(props: Object) { + if (this._scrollComponent) { + this._scrollComponent.setNativeProps(props); + } + }, + + /** + * React life cycle hooks. + */ + + getDefaultProps: function() { + return { + initialListSize: DEFAULT_INITIAL_ROWS, + pageSize: DEFAULT_PAGE_SIZE, + renderScrollComponent: props => , + scrollRenderAheadDistance: DEFAULT_SCROLL_RENDER_AHEAD, + onEndReachedThreshold: DEFAULT_END_REACHED_THRESHOLD, + stickySectionHeadersEnabled: Platform.OS === 'ios' || Platform.OS === 'web', + stickyHeaderIndices: [], + }; + }, + + getInitialState: function() { + return { + curRenderedRowsCount: this.props.initialListSize, + highlightedRow: ({}: Object), + }; + }, + + getInnerViewNode: function() { + return this._scrollComponent.getInnerViewNode(); + }, + + UNSAFE_componentWillMount: function() { + // this data should never trigger a render pass, so don't put in state + this.scrollProperties = { + visibleLength: null, + contentLength: null, + offset: 0, + }; + this._childFrames = []; + this._visibleRows = {}; + this._prevRenderedRowsCount = 0; + this._sentEndForContentLength = null; + }, + + componentDidMount: function() { + // do this in animation frame until componentDidMount actually runs after + // the component is laid out + this.requestAnimationFrame(() => { + this._measureAndUpdateScrollProps(); + }); + }, + + UNSAFE_componentWillReceiveProps: function(nextProps: Object) { + if ( + this.props.dataSource !== nextProps.dataSource || + this.props.initialListSize !== nextProps.initialListSize + ) { + this.setState( + (state, props) => { + this._prevRenderedRowsCount = 0; + return { + curRenderedRowsCount: Math.min( + Math.max(state.curRenderedRowsCount, props.initialListSize), + props.enableEmptySections + ? props.dataSource.getRowAndSectionCount() + : props.dataSource.getRowCount(), + ), + }; + }, + () => this._renderMoreRowsIfNeeded(), + ); + } + }, + + componentDidUpdate: function() { + this.requestAnimationFrame(() => { + this._measureAndUpdateScrollProps(); + }); + }, + + _onRowHighlighted: function(sectionID: string, rowID: string) { + this.setState({highlightedRow: {sectionID, rowID}}); + }, + + render: function() { + var bodyComponents = []; + + var dataSource = this.props.dataSource; + var allRowIDs = dataSource.rowIdentities; + var rowCount = 0; + var stickySectionHeaderIndices = []; + + const {renderSectionHeader} = this.props; + + var header = this.props.renderHeader && this.props.renderHeader(); + var footer = this.props.renderFooter && this.props.renderFooter(); + var totalIndex = header ? 1 : 0; + + for (var sectionIdx = 0; sectionIdx < allRowIDs.length; sectionIdx++) { + var sectionID = dataSource.sectionIdentities[sectionIdx]; + var rowIDs = allRowIDs[sectionIdx]; + if (rowIDs.length === 0) { + if (this.props.enableEmptySections === undefined) { + /* $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. */ + var warning = require('fbjs/lib/warning'); + warning( + false, + 'In next release empty section headers will be rendered.' + + " In this release you can use 'enableEmptySections' flag to render empty section headers.", + ); + continue; + } else { + var invariant = require('fbjs/lib/invariant'); + invariant( + this.props.enableEmptySections, + "In next release 'enableEmptySections' flag will be deprecated, empty section headers will always be rendered." + + ' If empty section headers are not desirable their indices should be excluded from sectionIDs object.' + + " In this release 'enableEmptySections' may only have value 'true' to allow empty section headers rendering.", + ); + } + } + + if (renderSectionHeader) { + const element = renderSectionHeader( + dataSource.getSectionHeaderData(sectionIdx), + sectionID, + ); + if (element) { + bodyComponents.push( + React.cloneElement(element, {key: 's_' + sectionID}), + ); + if (this.props.stickySectionHeadersEnabled) { + stickySectionHeaderIndices.push(totalIndex); + } + totalIndex++; + } + } + + for (var rowIdx = 0; rowIdx < rowIDs.length; rowIdx++) { + var rowID = rowIDs[rowIdx]; + var comboID = sectionID + '_' + rowID; + var shouldUpdateRow = + rowCount >= this._prevRenderedRowsCount && + dataSource.rowShouldUpdate(sectionIdx, rowIdx); + var row = ( + + ); + bodyComponents.push(row); + totalIndex++; + + if ( + this.props.renderSeparator && + (rowIdx !== rowIDs.length - 1 || sectionIdx === allRowIDs.length - 1) + ) { + var adjacentRowHighlighted = + this.state.highlightedRow.sectionID === sectionID && + (this.state.highlightedRow.rowID === rowID || + this.state.highlightedRow.rowID === rowIDs[rowIdx + 1]); + var separator = this.props.renderSeparator( + sectionID, + rowID, + adjacentRowHighlighted, + ); + if (separator) { + bodyComponents.push({separator}); + totalIndex++; + } + } + if (++rowCount === this.state.curRenderedRowsCount) { + break; + } + } + if (rowCount >= this.state.curRenderedRowsCount) { + break; + } + } + + var {renderScrollComponent, ...props} = this.props; + if (!props.scrollEventThrottle) { + props.scrollEventThrottle = DEFAULT_SCROLL_CALLBACK_THROTTLE; + } + if (props.removeClippedSubviews === undefined) { + props.removeClippedSubviews = true; + } + Object.assign(props, { + onScroll: this._onScroll, + stickyHeaderIndices: this.props.stickyHeaderIndices.concat( + stickySectionHeaderIndices, + ), + + // Do not pass these events downstream to ScrollView since they will be + // registered in ListView's own ScrollResponder.Mixin + onKeyboardWillShow: undefined, + onKeyboardWillHide: undefined, + onKeyboardDidShow: undefined, + onKeyboardDidHide: undefined, + }); + + return cloneReferencedElement( + renderScrollComponent(props), + { + ref: this._setScrollComponentRef, + onContentSizeChange: this._onContentSizeChange, + onLayout: this._onLayout, + DEPRECATED_sendUpdatedChildFrames: + typeof props.onChangeVisibleRows !== undefined, + }, + header, + bodyComponents, + footer, + ); + }, + + /** + * Private methods + */ + + _measureAndUpdateScrollProps: function() { + var scrollComponent = this.getScrollResponder(); + if (!scrollComponent || !scrollComponent.getInnerViewNode) { + return; + } + + // RCTScrollViewManager.calculateChildFrames is not available on + // every platform + RCTScrollViewManager && + RCTScrollViewManager.calculateChildFrames && + RCTScrollViewManager.calculateChildFrames( + findNodeHandle(scrollComponent), + this._updateVisibleRows, + ); + }, + + _setScrollComponentRef: function(scrollComponent: Object) { + this._scrollComponent = scrollComponent; + }, + + _onContentSizeChange: function(width: number, height: number) { + var contentLength = !this.props.horizontal ? height : width; + if (contentLength !== this.scrollProperties.contentLength) { + this.scrollProperties.contentLength = contentLength; + this._updateVisibleRows(); + this._renderMoreRowsIfNeeded(); + } + this.props.onContentSizeChange && + this.props.onContentSizeChange(width, height); + }, + + _onLayout: function(event: Object) { + var {width, height} = event.nativeEvent.layout; + var visibleLength = !this.props.horizontal ? height : width; + if (visibleLength !== this.scrollProperties.visibleLength) { + this.scrollProperties.visibleLength = visibleLength; + this._updateVisibleRows(); + this._renderMoreRowsIfNeeded(); + } + this.props.onLayout && this.props.onLayout(event); + }, + + _maybeCallOnEndReached: function(event?: Object) { + if ( + this.props.onEndReached && + this.scrollProperties.contentLength !== this._sentEndForContentLength && + this._getDistanceFromEnd(this.scrollProperties) < + this.props.onEndReachedThreshold && + this.state.curRenderedRowsCount === + (this.props.enableEmptySections + ? this.props.dataSource.getRowAndSectionCount() + : this.props.dataSource.getRowCount()) + ) { + this._sentEndForContentLength = this.scrollProperties.contentLength; + this.props.onEndReached(event); + return true; + } + return false; + }, + + _renderMoreRowsIfNeeded: function() { + if ( + this.scrollProperties.contentLength === null || + this.scrollProperties.visibleLength === null || + this.state.curRenderedRowsCount === + (this.props.enableEmptySections + ? this.props.dataSource.getRowAndSectionCount() + : this.props.dataSource.getRowCount()) + ) { + this._maybeCallOnEndReached(); + return; + } + + var distanceFromEnd = this._getDistanceFromEnd(this.scrollProperties); + if (distanceFromEnd < this.props.scrollRenderAheadDistance) { + this._pageInNewRows(); + } + }, + + _pageInNewRows: function() { + this.setState( + (state, props) => { + var rowsToRender = Math.min( + state.curRenderedRowsCount + props.pageSize, + props.enableEmptySections + ? props.dataSource.getRowAndSectionCount() + : props.dataSource.getRowCount(), + ); + this._prevRenderedRowsCount = state.curRenderedRowsCount; + return { + curRenderedRowsCount: rowsToRender, + }; + }, + () => { + this._measureAndUpdateScrollProps(); + this._prevRenderedRowsCount = this.state.curRenderedRowsCount; + }, + ); + }, + + _getDistanceFromEnd: function(scrollProperties: Object) { + return ( + scrollProperties.contentLength - + scrollProperties.visibleLength - + scrollProperties.offset + ); + }, + + _updateVisibleRows: function(updatedFrames?: Array) { + if (!this.props.onChangeVisibleRows) { + return; // No need to compute visible rows if there is no callback + } + if (updatedFrames) { + updatedFrames.forEach(newFrame => { + this._childFrames[newFrame.index] = merge(newFrame); + }); + } + var isVertical = !this.props.horizontal; + var dataSource = this.props.dataSource; + var visibleMin = this.scrollProperties.offset; + var visibleMax = visibleMin + this.scrollProperties.visibleLength; + var allRowIDs = dataSource.rowIdentities; + + var header = this.props.renderHeader && this.props.renderHeader(); + var totalIndex = header ? 1 : 0; + var visibilityChanged = false; + var changedRows = {}; + for (var sectionIdx = 0; sectionIdx < allRowIDs.length; sectionIdx++) { + var rowIDs = allRowIDs[sectionIdx]; + if (rowIDs.length === 0) { + continue; + } + var sectionID = dataSource.sectionIdentities[sectionIdx]; + if (this.props.renderSectionHeader) { + totalIndex++; + } + var visibleSection = this._visibleRows[sectionID]; + if (!visibleSection) { + visibleSection = {}; + } + for (var rowIdx = 0; rowIdx < rowIDs.length; rowIdx++) { + var rowID = rowIDs[rowIdx]; + var frame = this._childFrames[totalIndex]; + totalIndex++; + if ( + this.props.renderSeparator && + (rowIdx !== rowIDs.length - 1 || sectionIdx === allRowIDs.length - 1) + ) { + totalIndex++; + } + if (!frame) { + break; + } + var rowVisible = visibleSection[rowID]; + var min = isVertical ? frame.y : frame.x; + var max = min + (isVertical ? frame.height : frame.width); + if ((!min && !max) || min === max) { + break; + } + if (min > visibleMax || max < visibleMin) { + if (rowVisible) { + visibilityChanged = true; + delete visibleSection[rowID]; + if (!changedRows[sectionID]) { + changedRows[sectionID] = {}; + } + changedRows[sectionID][rowID] = false; + } + } else if (!rowVisible) { + visibilityChanged = true; + visibleSection[rowID] = true; + if (!changedRows[sectionID]) { + changedRows[sectionID] = {}; + } + changedRows[sectionID][rowID] = true; + } + } + if (!isEmpty(visibleSection)) { + this._visibleRows[sectionID] = visibleSection; + } else if (this._visibleRows[sectionID]) { + delete this._visibleRows[sectionID]; + } + } + visibilityChanged && + this.props.onChangeVisibleRows(this._visibleRows, changedRows); + }, + + _onScroll: function(e: Object) { + var isVertical = !this.props.horizontal; + this.scrollProperties.visibleLength = + e.nativeEvent.layoutMeasurement[isVertical ? 'height' : 'width']; + this.scrollProperties.contentLength = + e.nativeEvent.contentSize[isVertical ? 'height' : 'width']; + this.scrollProperties.offset = + e.nativeEvent.contentOffset[isVertical ? 'y' : 'x']; + this._updateVisibleRows(e.nativeEvent.updatedChildFrames); + if (!this._maybeCallOnEndReached(e)) { + this._renderMoreRowsIfNeeded(); + } + + if ( + this.props.onEndReached && + this._getDistanceFromEnd(this.scrollProperties) > + this.props.onEndReachedThreshold + ) { + // Scrolled out of the end zone, so it should be able to trigger again. + this._sentEndForContentLength = null; + } + + this.props.onScroll && this.props.onScroll(e); + }, +}); + +export default ListView; diff --git a/packages/react-native-web/src/modules/StaticContainer/index.js b/packages/react-native-web/src/vendor/react-native/StaticContainer/index.js similarity index 100% rename from packages/react-native-web/src/modules/StaticContainer/index.js rename to packages/react-native-web/src/vendor/react-native/StaticContainer/index.js diff --git a/packages/react-native-web/src/modules/StaticRenderer/index.js b/packages/react-native-web/src/vendor/react-native/StaticRenderer/index.js similarity index 100% rename from packages/react-native-web/src/modules/StaticRenderer/index.js rename to packages/react-native-web/src/vendor/react-native/StaticRenderer/index.js diff --git a/packages/react-native-web/src/vendor/react-native/isEmpty/index.js b/packages/react-native-web/src/vendor/react-native/isEmpty/index.js new file mode 100644 index 00000000..31750758 --- /dev/null +++ b/packages/react-native-web/src/vendor/react-native/isEmpty/index.js @@ -0,0 +1,27 @@ +/** + * 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 isEmpty + */ +'use strict'; + +/** + * Mimics empty from PHP. + */ +function isEmpty(obj) { + if (Array.isArray(obj)) { + return obj.length === 0; + } else if (typeof obj === 'object') { + for (var i in obj) { + return false; + } + return true; + } else { + return !obj; + } +} + +export default isEmpty;