diff --git a/package.json b/package.json index 8dcd8bd6feab4..b19d5a8368c98 100644 --- a/package.json +++ b/package.json @@ -137,7 +137,6 @@ "react-is": "^0.0.0-50b50c26f", "react-test-renderer": "^0.0.0-50b50c26f", "react-virtualized-auto-sizer": "^1.0.2", - "react-window": "^1.8.0", "request-promise": "^4.2.4", "rimraf": "^2.6.3", "scheduler": "^0.0.0-50b50c26f", diff --git a/shells/browser/shared/webpack.backend.js b/shells/browser/shared/webpack.backend.js index a091b269acdbf..c5283522d2d8c 100644 --- a/shells/browser/shared/webpack.backend.js +++ b/shells/browser/shared/webpack.backend.js @@ -26,6 +26,7 @@ module.exports = { resolve: { alias: { src: resolve(__dirname, '../../../src'), + 'react-window': resolve(__dirname, '../../../vendor/react-window'), }, }, plugins: [ diff --git a/shells/browser/shared/webpack.config.js b/shells/browser/shared/webpack.config.js index dd2acc1f3c917..061ca43b43c6e 100644 --- a/shells/browser/shared/webpack.config.js +++ b/shells/browser/shared/webpack.config.js @@ -31,6 +31,7 @@ module.exports = { resolve: { alias: { src: resolve(__dirname, '../../../src'), + 'react-window': resolve(__dirname, '../../../vendor/react-window'), }, }, plugins: [ diff --git a/shells/dev/webpack.config.js b/shells/dev/webpack.config.js index 6738e13117462..f1106a689a166 100644 --- a/shells/dev/webpack.config.js +++ b/shells/dev/webpack.config.js @@ -30,6 +30,7 @@ const config = { resolve: { alias: { src: resolve(__dirname, '../../src'), + 'react-window': resolve(__dirname, '../../vendor/react-window'), }, }, plugins: [ diff --git a/vendor/react-window/CHANGELOG.md b/vendor/react-window/CHANGELOG.md new file mode 100644 index 0000000000000..7030d4ee8e91c --- /dev/null +++ b/vendor/react-window/CHANGELOG.md @@ -0,0 +1,96 @@ +Changelog +------------ + +### 1.8.0 +* 🎉 Added new "smart" align option for grid and list scroll-to-item methods ([gaearon](https://github.com/gaearon) - [#209](https://github.com/bvaughn/react-window/pull/209)) + +### 1.7.2 +* 🐛 Add guards to avoid invalid scroll offsets when `scrollTo()` is called with a negative offset or when `scrollToItem` is called with invalid indices (negative or too large). + +### 1.7.1 +* 🐛 Fix SSR regression introduced in 1.7.0 - ([Betree](https://github.com/Betree) - [#185](https://github.com/bvaughn/react-window/pull/185)) + +### 1.7.0 +* 🎉 Grid `scrollToItem` supports optional `rowIndex` and `columnIndex` params ([jgoz](https://github.com/jgoz) - [#174](https://github.com/bvaughn/react-window/pull/174)) +* DEV mode checks for `WeakSet` support before using it to avoid requiring a polyfill for IE11 - ([jgoz](https://github.com/jgoz) - [#167](https://github.com/bvaughn/react-window/pull/167)) + +### 1.6.2 +* 🐛 Bugfix for RTL when scrolling back towards the beginning (right) of the list. + +### 1.6.1 +* 🐛 Bugfix to account for differences between Chrome and non-Chrome browsers with regard to RTL and "scroll" events. + +### 1.6.0 +* 🎉 RTL support added for lists and grids. Special thanks to [davidgarsan](https://github.com/davidgarsan) for his support. - [#156](https://github.com/bvaughn/react-window/pull/156) +* 🐛 Grid `scrollToItem` methods take scrollbar size into account when aligning items - [#153](https://github.com/bvaughn/react-window/issues/153) + +### 1.5.2 +* 🐛 Edge case bug fix for `VariableSizeList` and `VariableSizeGrid` when the number of items decreases while a scroll is in progress. - ([iamsolankiamit](https://github.com/iamsolankiamit) - [#138](https://github.com/bvaughn/react-window/pull/138)) + +### 1.5.1 +* 🐛 Updated `getDerivedState` Flow annotations to address a warning in a newer version of Flow. + +### 1.5.0 +* 🎉 Added advanced memoization helpers methods `areEqual` and `shouldComponentUpdate` for item renderers. - [#114](https://github.com/bvaughn/react-window/issues/114) + +### 1.4.0 +* 🎉 List and Grid components now "overscan" (pre-render) in both directions when scrolling is not active. When scrolling is in progress, cells are only pre-rendered in the direction being scrolled. This change has been made in an effort to reduce visible flicker when scrolling starts without adding additional overhead during scroll (which is the most performance sensitive time). +* 🎉 Grid components now support separate `overscanColumnsCount` and `overscanRowsCount` props. Legacy `overscanCount` prop will continue to work, but with a deprecation warning in DEV mode. +* 🐛 Replaced `setTimeout` with `requestAnimationFrame` based timer, to avoid starvation issue for `isScrolling` reset. - [#106](https://github.com/bvaughn/react-window/issues/106) +* 🎉 Renamed List and Grid `innerTagName` and `outerTagName` props to `innerElementType` and `outerElementType` to formalize support for attaching arbitrary props (e.g. test ids) to List and Grid inner and outer DOM elements. Legacy `innerTagName` and `outerTagName` props will continue to work, but with a deprecation warning in DEV mode. +* 🐛 List re-renders items if `direction` prop changes. - [#104](https://github.com/bvaughn/react-window/issues/104) + +### 1.3.1 +* 🎉 Pass `itemData` value to custom `itemKey` callbacks when present - [#90](https://github.com/bvaughn/react-window/issues/90)) + +### 1.3.0 +* (Skipped) + +### 1.2.4 +* 🐛 Added Flow annotations to memoized methods to avoid a Flow warning for newer versions of Flow + +### 1.2.3 +* 🐛 Relaxed `children` validation checks. They were too strict and didn't support new React APIs like `memo`. + +### 1.2.2 +* 🐛 Improved Flow types for class component item renderers - ([nicholas-l](https://github.com/nicholas-l) - [#77](https://github.com/bvaughn/react-window/pull/77)) + +### 1.2.1 +* 🎉 Improved Flow types to include optional `itemData` parameter. ([TrySound](https://github.com/TrySound) - [#66](https://github.com/bvaughn/react-window/pull/66)) +* 🐛 `VariableSizeList` and `VariableSizeGrid` no longer call size getter functions with invalid index when item count is zero. + +### 1.2.0 +* 🎉 Flow types added to NPM package. ([TrySound](https://github.com/TrySound) - [#40](https://github.com/bvaughn/react-window/pull/40)) +* 🎉 Relaxed grid `scrollTo` method to make `scrollLeft` and `scrollTop` params _optional_ (so you can only update one axis if desired). - [#63](https://github.com/bvaughn/react-window/pull/63)) +* 🐛 Fixed invalid `this` pointer in `VariableSizeGrid` that broke the `resetAfter*` methods - [#58](https://github.com/bvaughn/react-window/pull/58)) +* Upgraded to babel 7 and used shared runtime helpers to reduce package size slightly. ([TrySound](https://github.com/TrySound) - [#48](https://github.com/bvaughn/react-window/pull/48)) +* Remove `overflow:hidden` from inner container ([souporserious](https://github.com/souporserious) - [#56](https://github.com/bvaughn/react-window/pull/56)) + +### 1.1.2 +* 🐛 Fixed edge case `scrollToItem` bug that caused lists/grids with very few items to have negative scroll offsets. + +### 1.1.1 +* 🐛 `FixedSizeGrid` and `FixedSizeList` automatically clear style cache when item size props change. + +### 1.1.0 +* 🎉 Use explicit `constructor` and `super` to generate cleaner component code. ([Andarist](https://github.com/Andarist) - [#26](https://github.com/bvaughn/react-window/pull/26)) +* 🎉 Add optional `shouldForceUpdate` param reset-index methods to specify `forceUpdate` behavior. ([nihgwu](https://github.com/nihgwu) - [#32](https://github.com/bvaughn/react-window/pull/32)) + +### 1.0.3 +* 🐛 Avoid unnecessary scrollbars for lists (e.g. no horizontal scrollbar for a vertical list) unless content requires them. + +### 1.0.2 + +* 🎉 Enable Babel `annotate-pure-calls` option so that classes compiled by "transform-es2015-classes" are annotated with `#__PURE__`. This enables [UglifyJS to remove them if they are not referenced](https://github.com/mishoo/UglifyJS2/pull/1448), improving dead code elimination in application code. ([Andarist](https://github.com/Andarist) - [#20](https://github.com/bvaughn/react-window/pull/20)) +* 🎉 Update "rollup-plugin-peer-deps-external" and use new `includeDependencies` flag so that the "memoize-one" dependency does not get inlined into the Rollup bundle. ([Andarist](https://github.com/Andarist) - [#19](https://github.com/bvaughn/react-window/pull/19)) +* 🎉 Enable [Babel "loose" mode](https://babeljs.io/docs/en/babel-preset-env#loose) to reduce package size (-8%). ([Andarist](https://github.com/Andarist) - [#18](https://github.com/bvaughn/react-window/pull/18)) + +### 1.0.1 +Updated `README.md` file to remove `@alpha` tag from NPM installation instructions. + +# 1.0.0 +Initial release of library. Includes the following components: +* `FixedSizeGrid` +* `FixedSizeList` +* `VariableSizeGrid` +* `VariableSizeList` diff --git a/vendor/react-window/LICENSE.md b/vendor/react-window/LICENSE.md new file mode 100644 index 0000000000000..0569d0a7b8b21 --- /dev/null +++ b/vendor/react-window/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2018 Brian Vaughn + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/react-window/README.md b/vendor/react-window/README.md new file mode 100644 index 0000000000000..8603c9c5bcc53 --- /dev/null +++ b/vendor/react-window/README.md @@ -0,0 +1,88 @@ +# react-window + +> React components for efficiently rendering large lists and tabular data + +[![NPM registry](https://img.shields.io/npm/v/react-window.svg?style=for-the-badge)](https://yarnpkg.com/en/package/react-window) [![Travis](https://img.shields.io/badge/ci-travis-green.svg?style=for-the-badge)](https://travis-ci.org/bvaughn/react-window) [![NPM license](https://img.shields.io/badge/license-mit-red.svg?style=for-the-badge)](LICENSE.md) + +## Install + +```bash +# Yarn +yarn add react-window + +# NPM +npm install --save react-window +``` + +## Usage + +Learn more at [react-window.now.sh](https://react-window.now.sh/): + +## Related libraries + +* [`react-virtualized-auto-sizer`](https://npmjs.com/package/react-virtualized-auto-sizer): HOC that grows to fit all of the available space and passes the width and height values to its child. +* [`react-window-infinite-loader`](https://npmjs.com/package/react-window-infinite-loader): Helps break large data sets down into chunks that can be just-in-time loaded as they are scrolled into view. It can also be used to create infinite loading lists (e.g. Facebook or Twitter). + +## Frequently asked questions + +### How is `react-window` different from `react-virtualized`? +I wrote `react-virtualized` several years ago. At the time, I was new to both React and the concept of windowing. Because of this, I made a few API decisions that I later came to regret. One of these was adding too many non-essential features and components. Once you add something to an open source project, removing it is pretty painful for users. + +`react-window` is a complete rewrite of `react-virtualized`. I didn't try to solve as many problems or support as many use cases. Instead I focused on making the package **smaller**1 and **faster**. I also put a lot of thought into making the API (and documentation) as beginner-friendly as possible (with the caveat that windowing is still kind of an advanced use case). + +If `react-window` provides the functionality your project needs, I would strongly recommend using it instead of `react-virtualized`. However if you need features that only `react-virtualized` provides, you have two options: + +1. Use `react-virtualized`. (It's still widely used by a lot of successful projects!) +2. Create a component that decorates one of the `react-window` primitives and adds the functionality you need. You may even want to release this component to NPM (as its own, standalone package)! 🙂 + +1 - Adding a `react-virtualized` list to a CRA project increases the (gzipped) build size by ~33.5 KB. Adding a `react-window` list to a CRA project increases the (gzipped) build size by <2 KB. + +### Can a list or a grid fill 100% the width or height of a page? + +Yes. I recommend using the [`react-virtualized-auto-sizer` package](https://npmjs.com/package/react-virtualized-auto-sizer): + +screen shot 2019-03-07 at 7 29 08 pm + +Here's a [Code Sandbox demo](https://codesandbox.io/s/3vnx878jk5). + +### Why is my list blank when I scroll? + +If your list looks something like this... + + + +...then you probably forgot to use the `style` parameter! Libraries like react-window work by absolutely positioning the list items (via an inline style), so don't forget to attach it to the DOM element you render! + +screen shot 2019-03-07 at 7 21 48 pm + +### Can I lazy load data for my list? + +Yes. I recommend using the [`react-window-infinite-loader` package](https://npmjs.com/package/react-window-infinite-loader): + +screen shot 2019-03-07 at 7 32 32 pm + +Here's a [Code Sandbox demo](https://codesandbox.io/s/5wqo7z2np4). + +### Can I attach custom properties or event handlers? + +Yes, using the `outerElementType` prop. + +Screen Shot 2019-03-12 at 8 58 09 AM + +Here's a [Code Sandbox demo](https://codesandbox.io/s/4zqx79nww0). + +### Can I add gutter or padding between items? + +Yes, although it requires a bit of inline styling. + +Screen Shot 2019-03-26 at 6 33 56 PM + +Here's a [Code Sandbox demo](https://codesandbox.io/s/2w8wmlm89p). + +### Does this library support "sticky" items? + +Yes, although it requires a small amount of user code. Here's a [Code Sandbox demo](https://codesandbox.io/s/0mk3qwpl4l). + +## License + +MIT © [bvaughn](https://github.com/bvaughn) diff --git a/vendor/react-window/dist/index.cjs.js b/vendor/react-window/dist/index.cjs.js new file mode 100644 index 0000000000000..08582cdb6fc5d --- /dev/null +++ b/vendor/react-window/dist/index.cjs.js @@ -0,0 +1,1894 @@ +'use strict'; + +Object.defineProperty(exports, '__esModule', { value: true }); + +function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } + +var _extends = _interopDefault(require('@babel/runtime/helpers/extends')); +var _inheritsLoose = _interopDefault(require('@babel/runtime/helpers/inheritsLoose')); +var _assertThisInitialized = _interopDefault(require('@babel/runtime/helpers/assertThisInitialized')); +var memoizeOne = _interopDefault(require('memoize-one')); +var react = require('react'); +var _objectWithoutPropertiesLoose = _interopDefault(require('@babel/runtime/helpers/objectWithoutPropertiesLoose')); + +// Animation frame based implementation of setTimeout. +// Inspired by Joe Lambert, https://gist.github.com/joelambert/1002116#file-requesttimeout-js +var hasNativePerformanceNow = typeof performance === 'object' && typeof performance.now === 'function'; +var now = hasNativePerformanceNow ? function () { + return performance.now(); +} : function () { + return Date.now(); +}; +function cancelTimeout(timeoutID) { + cancelAnimationFrame(timeoutID.id); +} +function requestTimeout(callback, delay) { + var start = now(); + + function tick() { + if (now() - start >= delay) { + callback.call(null); + } else { + timeoutID.id = requestAnimationFrame(tick); + } + } + + var timeoutID = { + id: requestAnimationFrame(tick) + }; + return timeoutID; +} + +var size = -1; // This utility copied from "dom-helpers" package. + +function getScrollbarSize(recalculate) { + if (recalculate === void 0) { + recalculate = false; + } + + if (size === -1 || recalculate) { + var div = document.createElement('div'); + var style = div.style; + style.width = '50px'; + style.height = '50px'; + style.overflow = 'scroll'; + document.body.appendChild(div); + size = div.offsetWidth - div.clientWidth; + document.body.removeChild(div); + } + + return size; +} + +var IS_SCROLLING_DEBOUNCE_INTERVAL = 150; + +var defaultItemKey = function defaultItemKey(_ref) { + var columnIndex = _ref.columnIndex, + data = _ref.data, + rowIndex = _ref.rowIndex; + return rowIndex + ":" + columnIndex; +}; // In DEV mode, this Set helps us only log a warning once per component instance. +// This avoids spamming the console every time a render happens. + + +var devWarningsOverscanCount = null; +var devWarningsTagName = null; + +if (process.env.NODE_ENV !== 'production') { + if (typeof window !== 'undefined' && typeof window.WeakSet !== 'undefined') { + devWarningsOverscanCount = + /*#__PURE__*/ + new WeakSet(); + devWarningsTagName = + /*#__PURE__*/ + new WeakSet(); + } +} + +function createGridComponent(_ref2) { + var _class, _temp; + + var getColumnOffset = _ref2.getColumnOffset, + getColumnStartIndexForOffset = _ref2.getColumnStartIndexForOffset, + getColumnStopIndexForStartIndex = _ref2.getColumnStopIndexForStartIndex, + getColumnWidth = _ref2.getColumnWidth, + getEstimatedTotalHeight = _ref2.getEstimatedTotalHeight, + getEstimatedTotalWidth = _ref2.getEstimatedTotalWidth, + getOffsetForColumnAndAlignment = _ref2.getOffsetForColumnAndAlignment, + getOffsetForRowAndAlignment = _ref2.getOffsetForRowAndAlignment, + getRowHeight = _ref2.getRowHeight, + getRowOffset = _ref2.getRowOffset, + getRowStartIndexForOffset = _ref2.getRowStartIndexForOffset, + getRowStopIndexForStartIndex = _ref2.getRowStopIndexForStartIndex, + initInstanceProps = _ref2.initInstanceProps, + shouldResetStyleCacheOnItemSizeChange = _ref2.shouldResetStyleCacheOnItemSizeChange, + validateProps = _ref2.validateProps; + return _temp = _class = + /*#__PURE__*/ + function (_PureComponent) { + _inheritsLoose(Grid, _PureComponent); + + // Always use explicit constructor for React components. + // It produces less code after transpilation. (#26) + // eslint-disable-next-line no-useless-constructor + function Grid(props) { + var _this; + + _this = _PureComponent.call(this, props) || this; + _this._instanceProps = initInstanceProps(_this.props, _assertThisInitialized(_assertThisInitialized(_this))); + _this._resetIsScrollingTimeoutId = null; + _this._outerRef = void 0; + _this.state = { + instance: _assertThisInitialized(_assertThisInitialized(_this)), + isScrolling: false, + horizontalScrollDirection: 'forward', + scrollLeft: typeof _this.props.initialScrollLeft === 'number' ? _this.props.initialScrollLeft : 0, + scrollTop: typeof _this.props.initialScrollTop === 'number' ? _this.props.initialScrollTop : 0, + scrollUpdateWasRequested: false, + verticalScrollDirection: 'forward' + }; + _this._callOnItemsRendered = void 0; + _this._callOnItemsRendered = memoizeOne(function (overscanColumnStartIndex, overscanColumnStopIndex, overscanRowStartIndex, overscanRowStopIndex, visibleColumnStartIndex, visibleColumnStopIndex, visibleRowStartIndex, visibleRowStopIndex) { + return _this.props.onItemsRendered({ + overscanColumnStartIndex: overscanColumnStartIndex, + overscanColumnStopIndex: overscanColumnStopIndex, + overscanRowStartIndex: overscanRowStartIndex, + overscanRowStopIndex: overscanRowStopIndex, + visibleColumnStartIndex: visibleColumnStartIndex, + visibleColumnStopIndex: visibleColumnStopIndex, + visibleRowStartIndex: visibleRowStartIndex, + visibleRowStopIndex: visibleRowStopIndex + }); + }); + _this._callOnScroll = void 0; + _this._callOnScroll = memoizeOne(function (scrollLeft, scrollTop, horizontalScrollDirection, verticalScrollDirection, scrollUpdateWasRequested) { + return _this.props.onScroll({ + horizontalScrollDirection: horizontalScrollDirection, + scrollLeft: scrollLeft, + scrollTop: scrollTop, + verticalScrollDirection: verticalScrollDirection, + scrollUpdateWasRequested: scrollUpdateWasRequested + }); + }); + _this._getItemStyle = void 0; + + _this._getItemStyle = function (rowIndex, columnIndex) { + var _this$props = _this.props, + columnWidth = _this$props.columnWidth, + direction = _this$props.direction, + rowHeight = _this$props.rowHeight; + + var itemStyleCache = _this._getItemStyleCache(shouldResetStyleCacheOnItemSizeChange && columnWidth, shouldResetStyleCacheOnItemSizeChange && direction, shouldResetStyleCacheOnItemSizeChange && rowHeight); + + var key = rowIndex + ":" + columnIndex; + var style; + + if (itemStyleCache.hasOwnProperty(key)) { + style = itemStyleCache[key]; + } else { + var _style; + + itemStyleCache[key] = style = (_style = { + position: 'absolute' + }, _style[direction === 'rtl' ? 'right' : 'left'] = getColumnOffset(_this.props, columnIndex, _this._instanceProps), _style.top = getRowOffset(_this.props, rowIndex, _this._instanceProps), _style.height = getRowHeight(_this.props, rowIndex, _this._instanceProps), _style.width = getColumnWidth(_this.props, columnIndex, _this._instanceProps), _style); + } + + return style; + }; + + _this._getItemStyleCache = void 0; + _this._getItemStyleCache = memoizeOne(function (_, __, ___) { + return {}; + }); + + _this._onScroll = function (event) { + var _event$currentTarget = event.currentTarget, + clientWidth = _event$currentTarget.clientWidth, + scrollLeft = _event$currentTarget.scrollLeft, + scrollTop = _event$currentTarget.scrollTop, + scrollWidth = _event$currentTarget.scrollWidth; + + _this.setState(function (prevState) { + if (prevState.scrollLeft === scrollLeft && prevState.scrollTop === scrollTop) { + // Scroll position may have been updated by cDM/cDU, + // In which case we don't need to trigger another render, + // And we don't want to update state.isScrolling. + return null; + } + + var direction = _this.props.direction; // HACK According to the spec, scrollLeft should be negative for RTL aligned elements. + // Chrome does not seem to adhere; its scrollLeft values are positive (measured relative to the left). + // See https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollLeft + + var calculatedScrollLeft = scrollLeft; + + if (direction === 'rtl') { + if (scrollLeft <= 0) { + calculatedScrollLeft = -scrollLeft; + } else { + calculatedScrollLeft = scrollWidth - clientWidth - scrollLeft; + } + } + + return { + isScrolling: true, + horizontalScrollDirection: prevState.scrollLeft < scrollLeft ? 'forward' : 'backward', + scrollLeft: calculatedScrollLeft, + scrollTop: scrollTop, + verticalScrollDirection: prevState.scrollTop < scrollTop ? 'forward' : 'backward', + scrollUpdateWasRequested: false + }; + }, _this._resetIsScrollingDebounced); + }; + + _this._outerRefSetter = function (ref) { + var outerRef = _this.props.outerRef; + _this._outerRef = ref; + + if (typeof outerRef === 'function') { + outerRef(ref); + } else if (outerRef != null && typeof outerRef === 'object' && outerRef.hasOwnProperty('current')) { + outerRef.current = ref; + } + }; + + _this._resetIsScrollingDebounced = function () { + if (_this._resetIsScrollingTimeoutId !== null) { + cancelTimeout(_this._resetIsScrollingTimeoutId); + } + + _this._resetIsScrollingTimeoutId = requestTimeout(_this._resetIsScrolling, IS_SCROLLING_DEBOUNCE_INTERVAL); + }; + + _this._resetIsScrolling = function () { + _this._resetIsScrollingTimeoutId = null; + + _this.setState({ + isScrolling: false + }, function () { + // Clear style cache after state update has been committed. + // This way we don't break pure sCU for items that don't use isScrolling param. + _this._getItemStyleCache(-1); + }); + }; + + return _this; + } + + Grid.getDerivedStateFromProps = function getDerivedStateFromProps(nextProps, prevState) { + validateSharedProps(nextProps, prevState); + validateProps(nextProps); + return null; + }; + + var _proto = Grid.prototype; + + _proto.scrollTo = function scrollTo(_ref3) { + var scrollLeft = _ref3.scrollLeft, + scrollTop = _ref3.scrollTop; + + if (scrollLeft !== undefined) { + scrollLeft = Math.max(0, scrollLeft); + } + + if (scrollTop !== undefined) { + scrollTop = Math.max(0, scrollTop); + } + + this.setState(function (prevState) { + if (scrollLeft === undefined) { + scrollLeft = prevState.scrollLeft; + } + + if (scrollTop === undefined) { + scrollTop = prevState.scrollTop; + } + + if (prevState.scrollLeft === scrollLeft && prevState.scrollTop === scrollTop) { + return null; + } + + return { + horizontalScrollDirection: prevState.scrollLeft < scrollLeft ? 'forward' : 'backward', + scrollLeft: scrollLeft, + scrollTop: scrollTop, + scrollUpdateWasRequested: true, + verticalScrollDirection: prevState.scrollTop < scrollTop ? 'forward' : 'backward' + }; + }, this._resetIsScrollingDebounced); + }; + + _proto.scrollToItem = function scrollToItem(_ref4) { + var _ref4$align = _ref4.align, + align = _ref4$align === void 0 ? 'auto' : _ref4$align, + columnIndex = _ref4.columnIndex, + rowIndex = _ref4.rowIndex; + var _this$props2 = this.props, + columnCount = _this$props2.columnCount, + height = _this$props2.height, + rowCount = _this$props2.rowCount, + width = _this$props2.width; + var _this$state = this.state, + scrollLeft = _this$state.scrollLeft, + scrollTop = _this$state.scrollTop; + var scrollbarSize = getScrollbarSize(); + + if (columnIndex !== undefined) { + columnIndex = Math.max(0, Math.min(columnIndex, columnCount - 1)); + } + + if (rowIndex !== undefined) { + rowIndex = Math.max(0, Math.min(rowIndex, rowCount - 1)); + } + + var estimatedTotalHeight = getEstimatedTotalHeight(this.props, this._instanceProps); + var estimatedTotalWidth = getEstimatedTotalWidth(this.props, this._instanceProps); // The scrollbar size should be considered when scrolling an item into view, + // to ensure it's fully visible. + // But we only need to account for its size when it's actually visible. + + var horizontalScrollbarSize = estimatedTotalWidth > width ? scrollbarSize : 0; + var verticalScrollbarSize = estimatedTotalHeight > height ? scrollbarSize : 0; + this.scrollTo({ + scrollLeft: columnIndex !== undefined ? getOffsetForColumnAndAlignment(this.props, columnIndex, align, scrollLeft, this._instanceProps, verticalScrollbarSize) : scrollLeft, + scrollTop: rowIndex !== undefined ? getOffsetForRowAndAlignment(this.props, rowIndex, align, scrollTop, this._instanceProps, horizontalScrollbarSize) : scrollTop + }); + }; + + _proto.componentDidMount = function componentDidMount() { + var _this$props3 = this.props, + initialScrollLeft = _this$props3.initialScrollLeft, + initialScrollTop = _this$props3.initialScrollTop; + + if (typeof initialScrollLeft === 'number' && this._outerRef != null) { + this._outerRef.scrollLeft = initialScrollLeft; + } + + if (typeof initialScrollTop === 'number' && this._outerRef != null) { + this._outerRef.scrollTop = initialScrollTop; + } + + this._callPropsCallbacks(); + }; + + _proto.componentDidUpdate = function componentDidUpdate() { + var _this$state2 = this.state, + scrollLeft = _this$state2.scrollLeft, + scrollTop = _this$state2.scrollTop, + scrollUpdateWasRequested = _this$state2.scrollUpdateWasRequested; + + if (scrollUpdateWasRequested && this._outerRef !== null) { + this._outerRef.scrollLeft = scrollLeft; + this._outerRef.scrollTop = scrollTop; + } + + this._callPropsCallbacks(); + }; + + _proto.componentWillUnmount = function componentWillUnmount() { + if (this._resetIsScrollingTimeoutId !== null) { + cancelTimeout(this._resetIsScrollingTimeoutId); + } + }; + + _proto.render = function render() { + var _this$props4 = this.props, + children = _this$props4.children, + className = _this$props4.className, + columnCount = _this$props4.columnCount, + direction = _this$props4.direction, + height = _this$props4.height, + innerRef = _this$props4.innerRef, + innerElementType = _this$props4.innerElementType, + innerTagName = _this$props4.innerTagName, + itemData = _this$props4.itemData, + _this$props4$itemKey = _this$props4.itemKey, + itemKey = _this$props4$itemKey === void 0 ? defaultItemKey : _this$props4$itemKey, + outerElementType = _this$props4.outerElementType, + outerTagName = _this$props4.outerTagName, + rowCount = _this$props4.rowCount, + style = _this$props4.style, + useIsScrolling = _this$props4.useIsScrolling, + width = _this$props4.width; + var isScrolling = this.state.isScrolling; + + var _this$_getHorizontalR = this._getHorizontalRangeToRender(), + columnStartIndex = _this$_getHorizontalR[0], + columnStopIndex = _this$_getHorizontalR[1]; + + var _this$_getVerticalRan = this._getVerticalRangeToRender(), + rowStartIndex = _this$_getVerticalRan[0], + rowStopIndex = _this$_getVerticalRan[1]; + + var items = []; + + if (columnCount > 0 && rowCount) { + for (var _rowIndex = rowStartIndex; _rowIndex <= rowStopIndex; _rowIndex++) { + for (var _columnIndex = columnStartIndex; _columnIndex <= columnStopIndex; _columnIndex++) { + items.push(react.createElement(children, { + columnIndex: _columnIndex, + data: itemData, + isScrolling: useIsScrolling ? isScrolling : undefined, + key: itemKey({ + columnIndex: _columnIndex, + data: itemData, + rowIndex: _rowIndex + }), + rowIndex: _rowIndex, + style: this._getItemStyle(_rowIndex, _columnIndex) + })); + } + } + } // Read this value AFTER items have been created, + // So their actual sizes (if variable) are taken into consideration. + + + var estimatedTotalHeight = getEstimatedTotalHeight(this.props, this._instanceProps); + var estimatedTotalWidth = getEstimatedTotalWidth(this.props, this._instanceProps); + return react.createElement(outerElementType || outerTagName || 'div', { + className: className, + onScroll: this._onScroll, + ref: this._outerRefSetter, + style: _extends({ + position: 'relative', + height: height, + width: width, + overflow: 'auto', + WebkitOverflowScrolling: 'touch', + willChange: 'transform', + direction: direction + }, style) + }, react.createElement(innerElementType || innerTagName || 'div', { + children: items, + ref: innerRef, + style: { + height: estimatedTotalHeight, + pointerEvents: isScrolling ? 'none' : '', + width: estimatedTotalWidth + } + })); + }; + + _proto._callPropsCallbacks = function _callPropsCallbacks() { + var _this$props5 = this.props, + columnCount = _this$props5.columnCount, + onItemsRendered = _this$props5.onItemsRendered, + onScroll = _this$props5.onScroll, + rowCount = _this$props5.rowCount; + + if (typeof onItemsRendered === 'function') { + if (columnCount > 0 && rowCount > 0) { + var _this$_getHorizontalR2 = this._getHorizontalRangeToRender(), + _overscanColumnStartIndex = _this$_getHorizontalR2[0], + _overscanColumnStopIndex = _this$_getHorizontalR2[1], + _visibleColumnStartIndex = _this$_getHorizontalR2[2], + _visibleColumnStopIndex = _this$_getHorizontalR2[3]; + + var _this$_getVerticalRan2 = this._getVerticalRangeToRender(), + _overscanRowStartIndex = _this$_getVerticalRan2[0], + _overscanRowStopIndex = _this$_getVerticalRan2[1], + _visibleRowStartIndex = _this$_getVerticalRan2[2], + _visibleRowStopIndex = _this$_getVerticalRan2[3]; + + this._callOnItemsRendered(_overscanColumnStartIndex, _overscanColumnStopIndex, _overscanRowStartIndex, _overscanRowStopIndex, _visibleColumnStartIndex, _visibleColumnStopIndex, _visibleRowStartIndex, _visibleRowStopIndex); + } + } + + if (typeof onScroll === 'function') { + var _this$state3 = this.state, + _horizontalScrollDirection = _this$state3.horizontalScrollDirection, + _scrollLeft = _this$state3.scrollLeft, + _scrollTop = _this$state3.scrollTop, + _scrollUpdateWasRequested = _this$state3.scrollUpdateWasRequested, + _verticalScrollDirection = _this$state3.verticalScrollDirection; + + this._callOnScroll(_scrollLeft, _scrollTop, _horizontalScrollDirection, _verticalScrollDirection, _scrollUpdateWasRequested); + } + }; // Lazily create and cache item styles while scrolling, + // So that pure component sCU will prevent re-renders. + // We maintain this cache, and pass a style prop rather than index, + // So that List can clear cached styles and force item re-render if necessary. + + + _proto._getHorizontalRangeToRender = function _getHorizontalRangeToRender() { + var _this$props6 = this.props, + columnCount = _this$props6.columnCount, + overscanColumnsCount = _this$props6.overscanColumnsCount, + overscanCount = _this$props6.overscanCount, + rowCount = _this$props6.rowCount; + var _this$state4 = this.state, + horizontalScrollDirection = _this$state4.horizontalScrollDirection, + isScrolling = _this$state4.isScrolling, + scrollLeft = _this$state4.scrollLeft; + var overscanCountResolved = overscanColumnsCount || overscanCount || 1; + + if (columnCount === 0 || rowCount === 0) { + return [0, 0, 0, 0]; + } + + var startIndex = getColumnStartIndexForOffset(this.props, scrollLeft, this._instanceProps); + var stopIndex = getColumnStopIndexForStartIndex(this.props, startIndex, scrollLeft, this._instanceProps); // Overscan by one item in each direction so that tab/focus works. + // If there isn't at least one extra item, tab loops back around. + + var overscanBackward = !isScrolling || horizontalScrollDirection === 'backward' ? Math.max(1, overscanCountResolved) : 1; + var overscanForward = !isScrolling || horizontalScrollDirection === 'forward' ? Math.max(1, overscanCountResolved) : 1; + return [Math.max(0, startIndex - overscanBackward), Math.max(0, Math.min(columnCount - 1, stopIndex + overscanForward)), startIndex, stopIndex]; + }; + + _proto._getVerticalRangeToRender = function _getVerticalRangeToRender() { + var _this$props7 = this.props, + columnCount = _this$props7.columnCount, + overscanCount = _this$props7.overscanCount, + overscanRowsCount = _this$props7.overscanRowsCount, + rowCount = _this$props7.rowCount; + var _this$state5 = this.state, + isScrolling = _this$state5.isScrolling, + verticalScrollDirection = _this$state5.verticalScrollDirection, + scrollTop = _this$state5.scrollTop; + var overscanCountResolved = overscanRowsCount || overscanCount || 1; + + if (columnCount === 0 || rowCount === 0) { + return [0, 0, 0, 0]; + } + + var startIndex = getRowStartIndexForOffset(this.props, scrollTop, this._instanceProps); + var stopIndex = getRowStopIndexForStartIndex(this.props, startIndex, scrollTop, this._instanceProps); // Overscan by one item in each direction so that tab/focus works. + // If there isn't at least one extra item, tab loops back around. + + var overscanBackward = !isScrolling || verticalScrollDirection === 'backward' ? Math.max(1, overscanCountResolved) : 1; + var overscanForward = !isScrolling || verticalScrollDirection === 'forward' ? Math.max(1, overscanCountResolved) : 1; + return [Math.max(0, startIndex - overscanBackward), Math.max(0, Math.min(rowCount - 1, stopIndex + overscanForward)), startIndex, stopIndex]; + }; + + return Grid; + }(react.PureComponent), _class.defaultProps = { + direction: 'ltr', + itemData: undefined, + useIsScrolling: false + }, _temp; +} + +var validateSharedProps = function validateSharedProps(_ref5, _ref6) { + var children = _ref5.children, + direction = _ref5.direction, + height = _ref5.height, + innerTagName = _ref5.innerTagName, + outerTagName = _ref5.outerTagName, + overscanCount = _ref5.overscanCount, + width = _ref5.width; + var instance = _ref6.instance; + + if (process.env.NODE_ENV !== 'production') { + if (typeof overscanCount === 'number') { + if (devWarningsOverscanCount && !devWarningsOverscanCount.has(instance)) { + devWarningsOverscanCount.add(instance); + console.warn('The overscanCount prop has been deprecated. ' + 'Please use the overscanColumnsCount and overscanRowsCount props instead.'); + } + } + + if (innerTagName != null || outerTagName != null) { + if (devWarningsTagName && !devWarningsTagName.has(instance)) { + devWarningsTagName.add(instance); + console.warn('The innerTagName and outerTagName props have been deprecated. ' + 'Please use the innerElementType and outerElementType props instead.'); + } + } + + if (children == null) { + throw Error('An invalid "children" prop has been specified. ' + 'Value should be a React component. ' + ("\"" + (children === null ? 'null' : typeof children) + "\" was specified.")); + } + + switch (direction) { + case 'ltr': + case 'rtl': + // Valid values + break; + + default: + throw Error('An invalid "direction" prop has been specified. ' + 'Value should be either "ltr" or "rtl". ' + ("\"" + direction + "\" was specified.")); + } + + if (typeof width !== 'number') { + throw Error('An invalid "width" prop has been specified. ' + 'Grids must specify a number for width. ' + ("\"" + (width === null ? 'null' : typeof width) + "\" was specified.")); + } + + if (typeof height !== 'number') { + throw Error('An invalid "height" prop has been specified. ' + 'Grids must specify a number for height. ' + ("\"" + (height === null ? 'null' : typeof height) + "\" was specified.")); + } + } +}; + +var DEFAULT_ESTIMATED_ITEM_SIZE = 50; + +var getEstimatedTotalHeight = function getEstimatedTotalHeight(_ref, _ref2) { + var rowCount = _ref.rowCount; + var rowMetadataMap = _ref2.rowMetadataMap, + estimatedRowHeight = _ref2.estimatedRowHeight, + lastMeasuredRowIndex = _ref2.lastMeasuredRowIndex; + var totalSizeOfMeasuredRows = 0; // Edge case check for when the number of items decreases while a scroll is in progress. + // https://github.com/bvaughn/react-window/pull/138 + + if (lastMeasuredRowIndex >= rowCount) { + lastMeasuredRowIndex = rowCount - 1; + } + + if (lastMeasuredRowIndex >= 0) { + var itemMetadata = rowMetadataMap[lastMeasuredRowIndex]; + totalSizeOfMeasuredRows = itemMetadata.offset + itemMetadata.size; + } + + var numUnmeasuredItems = rowCount - lastMeasuredRowIndex - 1; + var totalSizeOfUnmeasuredItems = numUnmeasuredItems * estimatedRowHeight; + return totalSizeOfMeasuredRows + totalSizeOfUnmeasuredItems; +}; + +var getEstimatedTotalWidth = function getEstimatedTotalWidth(_ref3, _ref4) { + var columnCount = _ref3.columnCount; + var columnMetadataMap = _ref4.columnMetadataMap, + estimatedColumnWidth = _ref4.estimatedColumnWidth, + lastMeasuredColumnIndex = _ref4.lastMeasuredColumnIndex; + var totalSizeOfMeasuredRows = 0; // Edge case check for when the number of items decreases while a scroll is in progress. + // https://github.com/bvaughn/react-window/pull/138 + + if (lastMeasuredColumnIndex >= columnCount) { + lastMeasuredColumnIndex = columnCount - 1; + } + + if (lastMeasuredColumnIndex >= 0) { + var itemMetadata = columnMetadataMap[lastMeasuredColumnIndex]; + totalSizeOfMeasuredRows = itemMetadata.offset + itemMetadata.size; + } + + var numUnmeasuredItems = columnCount - lastMeasuredColumnIndex - 1; + var totalSizeOfUnmeasuredItems = numUnmeasuredItems * estimatedColumnWidth; + return totalSizeOfMeasuredRows + totalSizeOfUnmeasuredItems; +}; + +var getItemMetadata = function getItemMetadata(itemType, props, index, instanceProps) { + var itemMetadataMap, itemSize, lastMeasuredIndex; + + if (itemType === 'column') { + itemMetadataMap = instanceProps.columnMetadataMap; + itemSize = props.columnWidth; + lastMeasuredIndex = instanceProps.lastMeasuredColumnIndex; + } else { + itemMetadataMap = instanceProps.rowMetadataMap; + itemSize = props.rowHeight; + lastMeasuredIndex = instanceProps.lastMeasuredRowIndex; + } + + if (index > lastMeasuredIndex) { + var offset = 0; + + if (lastMeasuredIndex >= 0) { + var itemMetadata = itemMetadataMap[lastMeasuredIndex]; + offset = itemMetadata.offset + itemMetadata.size; + } + + for (var i = lastMeasuredIndex + 1; i <= index; i++) { + var size = itemSize(i); + itemMetadataMap[i] = { + offset: offset, + size: size + }; + offset += size; + } + + if (itemType === 'column') { + instanceProps.lastMeasuredColumnIndex = index; + } else { + instanceProps.lastMeasuredRowIndex = index; + } + } + + return itemMetadataMap[index]; +}; + +var findNearestItem = function findNearestItem(itemType, props, instanceProps, offset) { + var itemMetadataMap, lastMeasuredIndex; + + if (itemType === 'column') { + itemMetadataMap = instanceProps.columnMetadataMap; + lastMeasuredIndex = instanceProps.lastMeasuredColumnIndex; + } else { + itemMetadataMap = instanceProps.rowMetadataMap; + lastMeasuredIndex = instanceProps.lastMeasuredRowIndex; + } + + var lastMeasuredItemOffset = lastMeasuredIndex > 0 ? itemMetadataMap[lastMeasuredIndex].offset : 0; + + if (lastMeasuredItemOffset >= offset) { + // If we've already measured items within this range just use a binary search as it's faster. + return findNearestItemBinarySearch(itemType, props, instanceProps, lastMeasuredIndex, 0, offset); + } else { + // If we haven't yet measured this high, fallback to an exponential search with an inner binary search. + // The exponential search avoids pre-computing sizes for the full set of items as a binary search would. + // The overall complexity for this approach is O(log n). + return findNearestItemExponentialSearch(itemType, props, instanceProps, Math.max(0, lastMeasuredIndex), offset); + } +}; + +var findNearestItemBinarySearch = function findNearestItemBinarySearch(itemType, props, instanceProps, high, low, offset) { + while (low <= high) { + var middle = low + Math.floor((high - low) / 2); + var currentOffset = getItemMetadata(itemType, props, middle, instanceProps).offset; + + if (currentOffset === offset) { + return middle; + } else if (currentOffset < offset) { + low = middle + 1; + } else if (currentOffset > offset) { + high = middle - 1; + } + } + + if (low > 0) { + return low - 1; + } else { + return 0; + } +}; + +var findNearestItemExponentialSearch = function findNearestItemExponentialSearch(itemType, props, instanceProps, index, offset) { + var itemCount = itemType === 'column' ? props.columnCount : props.rowCount; + var interval = 1; + + while (index < itemCount && getItemMetadata(itemType, props, index, instanceProps).offset < offset) { + index += interval; + interval *= 2; + } + + return findNearestItemBinarySearch(itemType, props, instanceProps, Math.min(index, itemCount - 1), Math.floor(index / 2), offset); +}; + +var getOffsetForIndexAndAlignment = function getOffsetForIndexAndAlignment(itemType, props, index, align, scrollOffset, instanceProps, scrollbarSize) { + var size = itemType === 'column' ? props.width : props.height; + var itemMetadata = getItemMetadata(itemType, props, index, instanceProps); // Get estimated total size after ItemMetadata is computed, + // To ensure it reflects actual measurements instead of just estimates. + + var estimatedTotalSize = itemType === 'column' ? getEstimatedTotalWidth(props, instanceProps) : getEstimatedTotalHeight(props, instanceProps); + var maxOffset = Math.max(0, Math.min(estimatedTotalSize - size, itemMetadata.offset)); + var minOffset = Math.max(0, itemMetadata.offset - size + scrollbarSize + itemMetadata.size); + + if (align === 'smart') { + if (scrollOffset >= minOffset - size && scrollOffset <= maxOffset + size) { + align = 'auto'; + } else { + align = 'center'; + } + } + + switch (align) { + case 'start': + return maxOffset; + + case 'end': + return minOffset; + + case 'center': + return Math.round(minOffset + (maxOffset - minOffset) / 2); + + case 'auto': + default: + if (scrollOffset >= minOffset && scrollOffset <= maxOffset) { + return scrollOffset; + } else if (scrollOffset - minOffset < maxOffset - scrollOffset) { + return minOffset; + } else { + return maxOffset; + } + + } +}; + +var VariableSizeGrid = +/*#__PURE__*/ +createGridComponent({ + getColumnOffset: function getColumnOffset(props, index, instanceProps) { + return getItemMetadata('column', props, index, instanceProps).offset; + }, + getColumnStartIndexForOffset: function getColumnStartIndexForOffset(props, scrollLeft, instanceProps) { + return findNearestItem('column', props, instanceProps, scrollLeft); + }, + getColumnStopIndexForStartIndex: function getColumnStopIndexForStartIndex(props, startIndex, scrollLeft, instanceProps) { + var columnCount = props.columnCount, + width = props.width; + var itemMetadata = getItemMetadata('column', props, startIndex, instanceProps); + var maxOffset = scrollLeft + width; + var offset = itemMetadata.offset + itemMetadata.size; + var stopIndex = startIndex; + + while (stopIndex < columnCount - 1 && offset < maxOffset) { + stopIndex++; + offset += getItemMetadata('column', props, stopIndex, instanceProps).size; + } + + return stopIndex; + }, + getColumnWidth: function getColumnWidth(props, index, instanceProps) { + return instanceProps.columnMetadataMap[index].size; + }, + getEstimatedTotalHeight: getEstimatedTotalHeight, + getEstimatedTotalWidth: getEstimatedTotalWidth, + getOffsetForColumnAndAlignment: function getOffsetForColumnAndAlignment(props, index, align, scrollOffset, instanceProps, scrollbarSize) { + return getOffsetForIndexAndAlignment('column', props, index, align, scrollOffset, instanceProps, scrollbarSize); + }, + getOffsetForRowAndAlignment: function getOffsetForRowAndAlignment(props, index, align, scrollOffset, instanceProps, scrollbarSize) { + return getOffsetForIndexAndAlignment('row', props, index, align, scrollOffset, instanceProps, scrollbarSize); + }, + getRowOffset: function getRowOffset(props, index, instanceProps) { + return getItemMetadata('row', props, index, instanceProps).offset; + }, + getRowHeight: function getRowHeight(props, index, instanceProps) { + return instanceProps.rowMetadataMap[index].size; + }, + getRowStartIndexForOffset: function getRowStartIndexForOffset(props, scrollTop, instanceProps) { + return findNearestItem('row', props, instanceProps, scrollTop); + }, + getRowStopIndexForStartIndex: function getRowStopIndexForStartIndex(props, startIndex, scrollTop, instanceProps) { + var rowCount = props.rowCount, + height = props.height; + var itemMetadata = getItemMetadata('row', props, startIndex, instanceProps); + var maxOffset = scrollTop + height; + var offset = itemMetadata.offset + itemMetadata.size; + var stopIndex = startIndex; + + while (stopIndex < rowCount - 1 && offset < maxOffset) { + stopIndex++; + offset += getItemMetadata('row', props, stopIndex, instanceProps).size; + } + + return stopIndex; + }, + initInstanceProps: function initInstanceProps(props, instance) { + var _ref5 = props, + estimatedColumnWidth = _ref5.estimatedColumnWidth, + estimatedRowHeight = _ref5.estimatedRowHeight; + var instanceProps = { + columnMetadataMap: {}, + estimatedColumnWidth: estimatedColumnWidth || DEFAULT_ESTIMATED_ITEM_SIZE, + estimatedRowHeight: estimatedRowHeight || DEFAULT_ESTIMATED_ITEM_SIZE, + lastMeasuredColumnIndex: -1, + lastMeasuredRowIndex: -1, + rowMetadataMap: {} + }; + + instance.resetAfterColumnIndex = function (columnIndex, shouldForceUpdate) { + if (shouldForceUpdate === void 0) { + shouldForceUpdate = true; + } + + instance.resetAfterIndices({ + columnIndex: columnIndex, + shouldForceUpdate: shouldForceUpdate + }); + }; + + instance.resetAfterRowIndex = function (rowIndex, shouldForceUpdate) { + if (shouldForceUpdate === void 0) { + shouldForceUpdate = true; + } + + instance.resetAfterIndices({ + rowIndex: rowIndex, + shouldForceUpdate: shouldForceUpdate + }); + }; + + instance.resetAfterIndices = function (_ref6) { + var columnIndex = _ref6.columnIndex, + rowIndex = _ref6.rowIndex, + _ref6$shouldForceUpda = _ref6.shouldForceUpdate, + shouldForceUpdate = _ref6$shouldForceUpda === void 0 ? true : _ref6$shouldForceUpda; + + if (typeof columnIndex === 'number') { + instanceProps.lastMeasuredColumnIndex = Math.min(instanceProps.lastMeasuredColumnIndex, columnIndex - 1); + } + + if (typeof rowIndex === 'number') { + instanceProps.lastMeasuredRowIndex = Math.min(instanceProps.lastMeasuredRowIndex, rowIndex - 1); + } // We could potentially optimize further by only evicting styles after this index, + // But since styles are only cached while scrolling is in progress- + // It seems an unnecessary optimization. + // It's unlikely that resetAfterIndex() will be called while a user is scrolling. + + + instance._getItemStyleCache(-1); + + if (shouldForceUpdate) { + instance.forceUpdate(); + } + }; + + return instanceProps; + }, + shouldResetStyleCacheOnItemSizeChange: false, + validateProps: function validateProps(_ref7) { + var columnWidth = _ref7.columnWidth, + rowHeight = _ref7.rowHeight; + + if (process.env.NODE_ENV !== 'production') { + if (typeof columnWidth !== 'function') { + throw Error('An invalid "columnWidth" prop has been specified. ' + 'Value should be a function. ' + ("\"" + (columnWidth === null ? 'null' : typeof columnWidth) + "\" was specified.")); + } else if (typeof rowHeight !== 'function') { + throw Error('An invalid "rowHeight" prop has been specified. ' + 'Value should be a function. ' + ("\"" + (rowHeight === null ? 'null' : typeof rowHeight) + "\" was specified.")); + } + } + } +}); + +var IS_SCROLLING_DEBOUNCE_INTERVAL$1 = 150; + +var defaultItemKey$1 = function defaultItemKey(index, data) { + return index; +}; // In DEV mode, this Set helps us only log a warning once per component instance. +// This avoids spamming the console every time a render happens. + + +var devWarningsDirection = null; +var devWarningsTagName$1 = null; + +if (process.env.NODE_ENV !== 'production') { + if (typeof window !== 'undefined' && typeof window.WeakSet !== 'undefined') { + devWarningsDirection = + /*#__PURE__*/ + new WeakSet(); + devWarningsTagName$1 = + /*#__PURE__*/ + new WeakSet(); + } +} + +function createListComponent(_ref) { + var _class, _temp; + + var getItemOffset = _ref.getItemOffset, + getEstimatedTotalSize = _ref.getEstimatedTotalSize, + getItemSize = _ref.getItemSize, + getOffsetForIndexAndAlignment = _ref.getOffsetForIndexAndAlignment, + getStartIndexForOffset = _ref.getStartIndexForOffset, + getStopIndexForStartIndex = _ref.getStopIndexForStartIndex, + initInstanceProps = _ref.initInstanceProps, + shouldResetStyleCacheOnItemSizeChange = _ref.shouldResetStyleCacheOnItemSizeChange, + validateProps = _ref.validateProps; + return _temp = _class = + /*#__PURE__*/ + function (_PureComponent) { + _inheritsLoose(List, _PureComponent); + + // Always use explicit constructor for React components. + // It produces less code after transpilation. (#26) + // eslint-disable-next-line no-useless-constructor + function List(props) { + var _this; + + _this = _PureComponent.call(this, props) || this; + _this._instanceProps = initInstanceProps(_this.props, _assertThisInitialized(_assertThisInitialized(_this))); + _this._outerRef = void 0; + _this._resetIsScrollingTimeoutId = null; + _this.state = { + instance: _assertThisInitialized(_assertThisInitialized(_this)), + isScrolling: false, + scrollDirection: 'forward', + scrollOffset: typeof _this.props.initialScrollOffset === 'number' ? _this.props.initialScrollOffset : 0, + scrollUpdateWasRequested: false + }; + _this._callOnItemsRendered = void 0; + _this._callOnItemsRendered = memoizeOne(function (overscanStartIndex, overscanStopIndex, visibleStartIndex, visibleStopIndex) { + return _this.props.onItemsRendered({ + overscanStartIndex: overscanStartIndex, + overscanStopIndex: overscanStopIndex, + visibleStartIndex: visibleStartIndex, + visibleStopIndex: visibleStopIndex + }); + }); + _this._callOnScroll = void 0; + _this._callOnScroll = memoizeOne(function (scrollDirection, scrollOffset, scrollUpdateWasRequested) { + return _this.props.onScroll({ + scrollDirection: scrollDirection, + scrollOffset: scrollOffset, + scrollUpdateWasRequested: scrollUpdateWasRequested + }); + }); + _this._getItemStyle = void 0; + + _this._getItemStyle = function (index) { + var _this$props = _this.props, + direction = _this$props.direction, + itemSize = _this$props.itemSize, + layout = _this$props.layout; + + var itemStyleCache = _this._getItemStyleCache(shouldResetStyleCacheOnItemSizeChange && itemSize, shouldResetStyleCacheOnItemSizeChange && layout, shouldResetStyleCacheOnItemSizeChange && direction); + + var style; + + if (itemStyleCache.hasOwnProperty(index)) { + style = itemStyleCache[index]; + } else { + var _style; + + var _offset = getItemOffset(_this.props, index, _this._instanceProps); + + var size = getItemSize(_this.props, index, _this._instanceProps); // TODO Deprecate direction "horizontal" + + var isHorizontal = direction === 'horizontal' || layout === 'horizontal'; + itemStyleCache[index] = style = (_style = { + position: 'absolute' + }, _style[direction === 'rtl' ? 'right' : 'left'] = isHorizontal ? _offset : 0, _style.top = !isHorizontal ? _offset : 0, _style.height = !isHorizontal ? size : '100%', _style.width = isHorizontal ? size : '100%', _style); + } + + return style; + }; + + _this._getItemStyleCache = void 0; + _this._getItemStyleCache = memoizeOne(function (_, __, ___) { + return {}; + }); + + _this._onScrollHorizontal = function (event) { + var _event$currentTarget = event.currentTarget, + clientWidth = _event$currentTarget.clientWidth, + scrollLeft = _event$currentTarget.scrollLeft, + scrollWidth = _event$currentTarget.scrollWidth; + + _this.setState(function (prevState) { + if (prevState.scrollOffset === scrollLeft) { + // Scroll position may have been updated by cDM/cDU, + // In which case we don't need to trigger another render, + // And we don't want to update state.isScrolling. + return null; + } + + var direction = _this.props.direction; // HACK According to the spec, scrollLeft should be negative for RTL aligned elements. + // Chrome does not seem to adhere; its scrolLeft values are positive (measured relative to the left). + // See https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollLeft + + var scrollOffset = scrollLeft; + + if (direction === 'rtl') { + if (scrollLeft <= 0) { + scrollOffset = -scrollOffset; + } else { + scrollOffset = scrollWidth - clientWidth - scrollLeft; + } + } + + return { + isScrolling: true, + scrollDirection: prevState.scrollOffset < scrollLeft ? 'forward' : 'backward', + scrollOffset: scrollOffset, + scrollUpdateWasRequested: false + }; + }, _this._resetIsScrollingDebounced); + }; + + _this._onScrollVertical = function (event) { + var scrollTop = event.currentTarget.scrollTop; + + _this.setState(function (prevState) { + if (prevState.scrollOffset === scrollTop) { + // Scroll position may have been updated by cDM/cDU, + // In which case we don't need to trigger another render, + // And we don't want to update state.isScrolling. + return null; + } + + return { + isScrolling: true, + scrollDirection: prevState.scrollOffset < scrollTop ? 'forward' : 'backward', + scrollOffset: scrollTop, + scrollUpdateWasRequested: false + }; + }, _this._resetIsScrollingDebounced); + }; + + _this._outerRefSetter = function (ref) { + var outerRef = _this.props.outerRef; + _this._outerRef = ref; + + if (typeof outerRef === 'function') { + outerRef(ref); + } else if (outerRef != null && typeof outerRef === 'object' && outerRef.hasOwnProperty('current')) { + outerRef.current = ref; + } + }; + + _this._resetIsScrollingDebounced = function () { + if (_this._resetIsScrollingTimeoutId !== null) { + cancelTimeout(_this._resetIsScrollingTimeoutId); + } + + _this._resetIsScrollingTimeoutId = requestTimeout(_this._resetIsScrolling, IS_SCROLLING_DEBOUNCE_INTERVAL$1); + }; + + _this._resetIsScrolling = function () { + _this._resetIsScrollingTimeoutId = null; + + _this.setState({ + isScrolling: false + }, function () { + // Clear style cache after state update has been committed. + // This way we don't break pure sCU for items that don't use isScrolling param. + _this._getItemStyleCache(-1, null); + }); + }; + + return _this; + } + + List.getDerivedStateFromProps = function getDerivedStateFromProps(nextProps, prevState) { + validateSharedProps$1(nextProps, prevState); + validateProps(nextProps); + return null; + }; + + var _proto = List.prototype; + + _proto.scrollTo = function scrollTo(scrollOffset) { + scrollOffset = Math.max(0, scrollOffset); + this.setState(function (prevState) { + if (prevState.scrollOffset === scrollOffset) { + return null; + } + + return { + scrollDirection: prevState.scrollOffset < scrollOffset ? 'forward' : 'backward', + scrollOffset: scrollOffset, + scrollUpdateWasRequested: true + }; + }, this._resetIsScrollingDebounced); + }; + + _proto.scrollToItem = function scrollToItem(index, align) { + if (align === void 0) { + align = 'auto'; + } + + var itemCount = this.props.itemCount; + var scrollOffset = this.state.scrollOffset; + index = Math.max(0, Math.min(index, itemCount - 1)); + this.scrollTo(getOffsetForIndexAndAlignment(this.props, index, align, scrollOffset, this._instanceProps)); + }; + + _proto.componentDidMount = function componentDidMount() { + var _this$props2 = this.props, + direction = _this$props2.direction, + initialScrollOffset = _this$props2.initialScrollOffset, + layout = _this$props2.layout; + + if (typeof initialScrollOffset === 'number' && this._outerRef !== null) { + // TODO Deprecate direction "horizontal" + if (direction === 'horizontal' || layout === 'horizontal') { + this._outerRef.scrollLeft = initialScrollOffset; + } else { + this._outerRef.scrollTop = initialScrollOffset; + } + } + + this._callPropsCallbacks(); + }; + + _proto.componentDidUpdate = function componentDidUpdate() { + var _this$props3 = this.props, + direction = _this$props3.direction, + layout = _this$props3.layout; + var _this$state = this.state, + scrollOffset = _this$state.scrollOffset, + scrollUpdateWasRequested = _this$state.scrollUpdateWasRequested; + + if (scrollUpdateWasRequested && this._outerRef !== null) { + // TODO Deprecate direction "horizontal" + if (direction === 'horizontal' || layout === 'horizontal') { + this._outerRef.scrollLeft = scrollOffset; + } else { + this._outerRef.scrollTop = scrollOffset; + } + } + + this._callPropsCallbacks(); + }; + + _proto.componentWillUnmount = function componentWillUnmount() { + if (this._resetIsScrollingTimeoutId !== null) { + cancelTimeout(this._resetIsScrollingTimeoutId); + } + }; + + _proto.render = function render() { + var _this$props4 = this.props, + children = _this$props4.children, + className = _this$props4.className, + direction = _this$props4.direction, + height = _this$props4.height, + innerRef = _this$props4.innerRef, + innerElementType = _this$props4.innerElementType, + innerTagName = _this$props4.innerTagName, + itemCount = _this$props4.itemCount, + itemData = _this$props4.itemData, + _this$props4$itemKey = _this$props4.itemKey, + itemKey = _this$props4$itemKey === void 0 ? defaultItemKey$1 : _this$props4$itemKey, + layout = _this$props4.layout, + outerElementType = _this$props4.outerElementType, + outerTagName = _this$props4.outerTagName, + style = _this$props4.style, + useIsScrolling = _this$props4.useIsScrolling, + width = _this$props4.width; + var isScrolling = this.state.isScrolling; // TODO Deprecate direction "horizontal" + + var isHorizontal = direction === 'horizontal' || layout === 'horizontal'; + var onScroll = isHorizontal ? this._onScrollHorizontal : this._onScrollVertical; + + var _this$_getRangeToRend = this._getRangeToRender(), + startIndex = _this$_getRangeToRend[0], + stopIndex = _this$_getRangeToRend[1]; + + var items = []; + + if (itemCount > 0) { + for (var _index = startIndex; _index <= stopIndex; _index++) { + items.push(react.createElement(children, { + data: itemData, + key: itemKey(_index, itemData), + index: _index, + isScrolling: useIsScrolling ? isScrolling : undefined, + style: this._getItemStyle(_index) + })); + } + } // Read this value AFTER items have been created, + // So their actual sizes (if variable) are taken into consideration. + + + var estimatedTotalSize = getEstimatedTotalSize(this.props, this._instanceProps); + return react.createElement(outerElementType || outerTagName || 'div', { + className: className, + onScroll: onScroll, + ref: this._outerRefSetter, + style: _extends({ + position: 'relative', + height: height, + width: width, + overflow: 'auto', + WebkitOverflowScrolling: 'touch', + willChange: 'transform', + direction: direction + }, style) + }, react.createElement(innerElementType || innerTagName || 'div', { + children: items, + ref: innerRef, + style: { + height: isHorizontal ? '100%' : estimatedTotalSize, + pointerEvents: isScrolling ? 'none' : '', + width: isHorizontal ? estimatedTotalSize : '100%' + } + })); + }; + + _proto._callPropsCallbacks = function _callPropsCallbacks() { + if (typeof this.props.onItemsRendered === 'function') { + var itemCount = this.props.itemCount; + + if (itemCount > 0) { + var _this$_getRangeToRend2 = this._getRangeToRender(), + _overscanStartIndex = _this$_getRangeToRend2[0], + _overscanStopIndex = _this$_getRangeToRend2[1], + _visibleStartIndex = _this$_getRangeToRend2[2], + _visibleStopIndex = _this$_getRangeToRend2[3]; + + this._callOnItemsRendered(_overscanStartIndex, _overscanStopIndex, _visibleStartIndex, _visibleStopIndex); + } + } + + if (typeof this.props.onScroll === 'function') { + var _this$state2 = this.state, + _scrollDirection = _this$state2.scrollDirection, + _scrollOffset = _this$state2.scrollOffset, + _scrollUpdateWasRequested = _this$state2.scrollUpdateWasRequested; + + this._callOnScroll(_scrollDirection, _scrollOffset, _scrollUpdateWasRequested); + } + }; // Lazily create and cache item styles while scrolling, + // So that pure component sCU will prevent re-renders. + // We maintain this cache, and pass a style prop rather than index, + // So that List can clear cached styles and force item re-render if necessary. + + + _proto._getRangeToRender = function _getRangeToRender() { + var _this$props5 = this.props, + itemCount = _this$props5.itemCount, + overscanCount = _this$props5.overscanCount; + var _this$state3 = this.state, + isScrolling = _this$state3.isScrolling, + scrollDirection = _this$state3.scrollDirection, + scrollOffset = _this$state3.scrollOffset; + + if (itemCount === 0) { + return [0, 0, 0, 0]; + } + + var startIndex = getStartIndexForOffset(this.props, scrollOffset, this._instanceProps); + var stopIndex = getStopIndexForStartIndex(this.props, startIndex, scrollOffset, this._instanceProps); // Overscan by one item in each direction so that tab/focus works. + // If there isn't at least one extra item, tab loops back around. + + var overscanBackward = !isScrolling || scrollDirection === 'backward' ? Math.max(1, overscanCount) : 1; + var overscanForward = !isScrolling || scrollDirection === 'forward' ? Math.max(1, overscanCount) : 1; + return [Math.max(0, startIndex - overscanBackward), Math.max(0, Math.min(itemCount - 1, stopIndex + overscanForward)), startIndex, stopIndex]; + }; + + return List; + }(react.PureComponent), _class.defaultProps = { + direction: 'ltr', + itemData: undefined, + layout: 'vertical', + overscanCount: 2, + useIsScrolling: false + }, _temp; +} // NOTE: I considered further wrapping individual items with a pure ListItem component. +// This would avoid ever calling the render function for the same index more than once, +// But it would also add the overhead of a lot of components/fibers. +// I assume people already do this (render function returning a class component), +// So my doing it would just unnecessarily double the wrappers. + +var validateSharedProps$1 = function validateSharedProps(_ref2, _ref3) { + var children = _ref2.children, + direction = _ref2.direction, + height = _ref2.height, + layout = _ref2.layout, + innerTagName = _ref2.innerTagName, + outerTagName = _ref2.outerTagName, + width = _ref2.width; + var instance = _ref3.instance; + + if (process.env.NODE_ENV !== 'production') { + if (innerTagName != null || outerTagName != null) { + if (devWarningsTagName$1 && !devWarningsTagName$1.has(instance)) { + devWarningsTagName$1.add(instance); + console.warn('The innerTagName and outerTagName props have been deprecated. ' + 'Please use the innerElementType and outerElementType props instead.'); + } + } // TODO Deprecate direction "horizontal" + + + var isHorizontal = direction === 'horizontal' || layout === 'horizontal'; + + switch (direction) { + case 'horizontal': + case 'vertical': + if (devWarningsDirection && !devWarningsDirection.has(instance)) { + devWarningsDirection.add(instance); + console.warn('The direction prop should be either "ltr" (default) or "rtl". ' + 'Please use the layout prop to specify "vertical" (default) or "horizontal" orientation.'); + } + + break; + + case 'ltr': + case 'rtl': + // Valid values + break; + + default: + throw Error('An invalid "direction" prop has been specified. ' + 'Value should be either "ltr" or "rtl". ' + ("\"" + direction + "\" was specified.")); + } + + switch (layout) { + case 'horizontal': + case 'vertical': + // Valid values + break; + + default: + throw Error('An invalid "layout" prop has been specified. ' + 'Value should be either "horizontal" or "vertical". ' + ("\"" + layout + "\" was specified.")); + } + + if (children == null) { + throw Error('An invalid "children" prop has been specified. ' + 'Value should be a React component. ' + ("\"" + (children === null ? 'null' : typeof children) + "\" was specified.")); + } + + if (isHorizontal && typeof width !== 'number') { + throw Error('An invalid "width" prop has been specified. ' + 'Horizontal lists must specify a number for width. ' + ("\"" + (width === null ? 'null' : typeof width) + "\" was specified.")); + } else if (!isHorizontal && typeof height !== 'number') { + throw Error('An invalid "height" prop has been specified. ' + 'Vertical lists must specify a number for height. ' + ("\"" + (height === null ? 'null' : typeof height) + "\" was specified.")); + } + } +}; + +var DEFAULT_ESTIMATED_ITEM_SIZE$1 = 50; + +var getItemMetadata$1 = function getItemMetadata(props, index, instanceProps) { + var _ref = props, + itemSize = _ref.itemSize; + var itemMetadataMap = instanceProps.itemMetadataMap, + lastMeasuredIndex = instanceProps.lastMeasuredIndex; + + if (index > lastMeasuredIndex) { + var offset = 0; + + if (lastMeasuredIndex >= 0) { + var itemMetadata = itemMetadataMap[lastMeasuredIndex]; + offset = itemMetadata.offset + itemMetadata.size; + } + + for (var i = lastMeasuredIndex + 1; i <= index; i++) { + var size = itemSize(i); + itemMetadataMap[i] = { + offset: offset, + size: size + }; + offset += size; + } + + instanceProps.lastMeasuredIndex = index; + } + + return itemMetadataMap[index]; +}; + +var findNearestItem$1 = function findNearestItem(props, instanceProps, offset) { + var itemMetadataMap = instanceProps.itemMetadataMap, + lastMeasuredIndex = instanceProps.lastMeasuredIndex; + var lastMeasuredItemOffset = lastMeasuredIndex > 0 ? itemMetadataMap[lastMeasuredIndex].offset : 0; + + if (lastMeasuredItemOffset >= offset) { + // If we've already measured items within this range just use a binary search as it's faster. + return findNearestItemBinarySearch$1(props, instanceProps, lastMeasuredIndex, 0, offset); + } else { + // If we haven't yet measured this high, fallback to an exponential search with an inner binary search. + // The exponential search avoids pre-computing sizes for the full set of items as a binary search would. + // The overall complexity for this approach is O(log n). + return findNearestItemExponentialSearch$1(props, instanceProps, Math.max(0, lastMeasuredIndex), offset); + } +}; + +var findNearestItemBinarySearch$1 = function findNearestItemBinarySearch(props, instanceProps, high, low, offset) { + while (low <= high) { + var middle = low + Math.floor((high - low) / 2); + var currentOffset = getItemMetadata$1(props, middle, instanceProps).offset; + + if (currentOffset === offset) { + return middle; + } else if (currentOffset < offset) { + low = middle + 1; + } else if (currentOffset > offset) { + high = middle - 1; + } + } + + if (low > 0) { + return low - 1; + } else { + return 0; + } +}; + +var findNearestItemExponentialSearch$1 = function findNearestItemExponentialSearch(props, instanceProps, index, offset) { + var itemCount = props.itemCount; + var interval = 1; + + while (index < itemCount && getItemMetadata$1(props, index, instanceProps).offset < offset) { + index += interval; + interval *= 2; + } + + return findNearestItemBinarySearch$1(props, instanceProps, Math.min(index, itemCount - 1), Math.floor(index / 2), offset); +}; + +var getEstimatedTotalSize = function getEstimatedTotalSize(_ref2, _ref3) { + var itemCount = _ref2.itemCount; + var itemMetadataMap = _ref3.itemMetadataMap, + estimatedItemSize = _ref3.estimatedItemSize, + lastMeasuredIndex = _ref3.lastMeasuredIndex; + var totalSizeOfMeasuredItems = 0; // Edge case check for when the number of items decreases while a scroll is in progress. + // https://github.com/bvaughn/react-window/pull/138 + + if (lastMeasuredIndex >= itemCount) { + lastMeasuredIndex = itemCount - 1; + } + + if (lastMeasuredIndex >= 0) { + var itemMetadata = itemMetadataMap[lastMeasuredIndex]; + totalSizeOfMeasuredItems = itemMetadata.offset + itemMetadata.size; + } + + var numUnmeasuredItems = itemCount - lastMeasuredIndex - 1; + var totalSizeOfUnmeasuredItems = numUnmeasuredItems * estimatedItemSize; + return totalSizeOfMeasuredItems + totalSizeOfUnmeasuredItems; +}; + +var VariableSizeList = +/*#__PURE__*/ +createListComponent({ + getItemOffset: function getItemOffset(props, index, instanceProps) { + return getItemMetadata$1(props, index, instanceProps).offset; + }, + getItemSize: function getItemSize(props, index, instanceProps) { + return instanceProps.itemMetadataMap[index].size; + }, + getEstimatedTotalSize: getEstimatedTotalSize, + getOffsetForIndexAndAlignment: function getOffsetForIndexAndAlignment(props, index, align, scrollOffset, instanceProps) { + var direction = props.direction, + height = props.height, + layout = props.layout, + width = props.width; // TODO Deprecate direction "horizontal" + + var isHorizontal = direction === 'horizontal' || layout === 'horizontal'; + var size = isHorizontal ? width : height; + var itemMetadata = getItemMetadata$1(props, index, instanceProps); // Get estimated total size after ItemMetadata is computed, + // To ensure it reflects actual measurements instead of just estimates. + + var estimatedTotalSize = getEstimatedTotalSize(props, instanceProps); + var maxOffset = Math.max(0, Math.min(estimatedTotalSize - size, itemMetadata.offset)); + var minOffset = Math.max(0, itemMetadata.offset - size + itemMetadata.size); + + if (align === 'smart') { + if (scrollOffset >= minOffset - size && scrollOffset <= maxOffset + size) { + align = 'auto'; + } else { + align = 'center'; + } + } + + switch (align) { + case 'start': + return maxOffset; + + case 'end': + return minOffset; + + case 'center': + return Math.round(minOffset + (maxOffset - minOffset) / 2); + + case 'auto': + default: + if (scrollOffset >= minOffset && scrollOffset <= maxOffset) { + return scrollOffset; + } else if (scrollOffset - minOffset < maxOffset - scrollOffset) { + return minOffset; + } else { + return maxOffset; + } + + } + }, + getStartIndexForOffset: function getStartIndexForOffset(props, offset, instanceProps) { + return findNearestItem$1(props, instanceProps, offset); + }, + getStopIndexForStartIndex: function getStopIndexForStartIndex(props, startIndex, scrollOffset, instanceProps) { + var direction = props.direction, + height = props.height, + itemCount = props.itemCount, + layout = props.layout, + width = props.width; // TODO Deprecate direction "horizontal" + + var isHorizontal = direction === 'horizontal' || layout === 'horizontal'; + var size = isHorizontal ? width : height; + var itemMetadata = getItemMetadata$1(props, startIndex, instanceProps); + var maxOffset = scrollOffset + size; + var offset = itemMetadata.offset + itemMetadata.size; + var stopIndex = startIndex; + + while (stopIndex < itemCount - 1 && offset < maxOffset) { + stopIndex++; + offset += getItemMetadata$1(props, stopIndex, instanceProps).size; + } + + return stopIndex; + }, + initInstanceProps: function initInstanceProps(props, instance) { + var _ref4 = props, + estimatedItemSize = _ref4.estimatedItemSize; + var instanceProps = { + itemMetadataMap: {}, + estimatedItemSize: estimatedItemSize || DEFAULT_ESTIMATED_ITEM_SIZE$1, + lastMeasuredIndex: -1 + }; + + instance.resetAfterIndex = function (index, shouldForceUpdate) { + if (shouldForceUpdate === void 0) { + shouldForceUpdate = true; + } + + instanceProps.lastMeasuredIndex = Math.min(instanceProps.lastMeasuredIndex, index - 1); // We could potentially optimize further by only evicting styles after this index, + // But since styles are only cached while scrolling is in progress- + // It seems an unnecessary optimization. + // It's unlikely that resetAfterIndex() will be called while a user is scrolling. + + instance._getItemStyleCache(-1); + + if (shouldForceUpdate) { + instance.forceUpdate(); + } + }; + + return instanceProps; + }, + shouldResetStyleCacheOnItemSizeChange: false, + validateProps: function validateProps(_ref5) { + var itemSize = _ref5.itemSize; + + if (process.env.NODE_ENV !== 'production') { + if (typeof itemSize !== 'function') { + throw Error('An invalid "itemSize" prop has been specified. ' + 'Value should be a function. ' + ("\"" + (itemSize === null ? 'null' : typeof itemSize) + "\" was specified.")); + } + } + } +}); + +var FixedSizeGrid = +/*#__PURE__*/ +createGridComponent({ + getColumnOffset: function getColumnOffset(_ref, index) { + var columnWidth = _ref.columnWidth; + return index * columnWidth; + }, + getColumnWidth: function getColumnWidth(_ref2, index) { + var columnWidth = _ref2.columnWidth; + return columnWidth; + }, + getRowOffset: function getRowOffset(_ref3, index) { + var rowHeight = _ref3.rowHeight; + return index * rowHeight; + }, + getRowHeight: function getRowHeight(_ref4, index) { + var rowHeight = _ref4.rowHeight; + return rowHeight; + }, + getEstimatedTotalHeight: function getEstimatedTotalHeight(_ref5) { + var rowCount = _ref5.rowCount, + rowHeight = _ref5.rowHeight; + return rowHeight * rowCount; + }, + getEstimatedTotalWidth: function getEstimatedTotalWidth(_ref6) { + var columnCount = _ref6.columnCount, + columnWidth = _ref6.columnWidth; + return columnWidth * columnCount; + }, + getOffsetForColumnAndAlignment: function getOffsetForColumnAndAlignment(_ref7, columnIndex, align, scrollLeft, instanceProps, scrollbarSize) { + var columnCount = _ref7.columnCount, + columnWidth = _ref7.columnWidth, + width = _ref7.width; + var maxOffset = Math.max(0, Math.min(columnCount * columnWidth - width, columnIndex * columnWidth)); + var minOffset = Math.max(0, columnIndex * columnWidth - width + scrollbarSize + columnWidth); + + if (align === 'smart') { + if (scrollLeft >= minOffset - width && scrollLeft <= maxOffset + width) { + align = 'auto'; + } else { + align = 'center'; + } + } + + switch (align) { + case 'start': + return maxOffset; + + case 'end': + return minOffset; + + case 'center': + return Math.round(minOffset + (maxOffset - minOffset) / 2); + + case 'auto': + default: + if (scrollLeft >= minOffset && scrollLeft <= maxOffset) { + return scrollLeft; + } else if (scrollLeft - minOffset < maxOffset - scrollLeft) { + return minOffset; + } else { + return maxOffset; + } + + } + }, + getOffsetForRowAndAlignment: function getOffsetForRowAndAlignment(_ref8, rowIndex, align, scrollTop, instanceProps, scrollbarSize) { + var rowHeight = _ref8.rowHeight, + height = _ref8.height, + rowCount = _ref8.rowCount; + var maxOffset = Math.max(0, Math.min(rowCount * rowHeight - height, rowIndex * rowHeight)); + var minOffset = Math.max(0, rowIndex * rowHeight - height + scrollbarSize + rowHeight); + + if (align === 'smart') { + if (scrollTop >= minOffset - height && scrollTop <= maxOffset + height) { + align = 'auto'; + } else { + align = 'center'; + } + } + + switch (align) { + case 'start': + return maxOffset; + + case 'end': + return minOffset; + + case 'center': + return Math.round(minOffset + (maxOffset - minOffset) / 2); + + case 'auto': + default: + if (scrollTop >= minOffset && scrollTop <= maxOffset) { + return scrollTop; + } else if (scrollTop - minOffset < maxOffset - scrollTop) { + return minOffset; + } else { + return maxOffset; + } + + } + }, + getColumnStartIndexForOffset: function getColumnStartIndexForOffset(_ref9, scrollLeft) { + var columnWidth = _ref9.columnWidth, + columnCount = _ref9.columnCount; + return Math.max(0, Math.min(columnCount - 1, Math.floor(scrollLeft / columnWidth))); + }, + getColumnStopIndexForStartIndex: function getColumnStopIndexForStartIndex(_ref10, startIndex, scrollLeft) { + var columnWidth = _ref10.columnWidth, + columnCount = _ref10.columnCount, + width = _ref10.width; + var left = startIndex * columnWidth; + return Math.max(0, Math.min(columnCount - 1, startIndex + Math.floor((width + (scrollLeft - left)) / columnWidth))); + }, + getRowStartIndexForOffset: function getRowStartIndexForOffset(_ref11, scrollTop) { + var rowHeight = _ref11.rowHeight, + rowCount = _ref11.rowCount; + return Math.max(0, Math.min(rowCount - 1, Math.floor(scrollTop / rowHeight))); + }, + getRowStopIndexForStartIndex: function getRowStopIndexForStartIndex(_ref12, startIndex, scrollTop) { + var rowHeight = _ref12.rowHeight, + rowCount = _ref12.rowCount, + height = _ref12.height; + var left = startIndex * rowHeight; + return Math.max(0, Math.min(rowCount - 1, startIndex + Math.floor((height + (scrollTop - left)) / rowHeight))); + }, + initInstanceProps: function initInstanceProps(props) {// Noop + }, + shouldResetStyleCacheOnItemSizeChange: true, + validateProps: function validateProps(_ref13) { + var columnWidth = _ref13.columnWidth, + rowHeight = _ref13.rowHeight; + + if (process.env.NODE_ENV !== 'production') { + if (typeof columnWidth !== 'number') { + throw Error('An invalid "columnWidth" prop has been specified. ' + 'Value should be a number. ' + ("\"" + (columnWidth === null ? 'null' : typeof columnWidth) + "\" was specified.")); + } + + if (typeof rowHeight !== 'number') { + throw Error('An invalid "rowHeight" prop has been specified. ' + 'Value should be a number. ' + ("\"" + (rowHeight === null ? 'null' : typeof rowHeight) + "\" was specified.")); + } + } + } +}); + +var FixedSizeList = +/*#__PURE__*/ +createListComponent({ + getItemOffset: function getItemOffset(_ref, index) { + var itemSize = _ref.itemSize, + size = _ref.size; + return index * itemSize; + }, + getItemSize: function getItemSize(_ref2, index) { + var itemSize = _ref2.itemSize, + size = _ref2.size; + return itemSize; + }, + getEstimatedTotalSize: function getEstimatedTotalSize(_ref3) { + var itemCount = _ref3.itemCount, + itemSize = _ref3.itemSize; + return itemSize * itemCount; + }, + getOffsetForIndexAndAlignment: function getOffsetForIndexAndAlignment(_ref4, index, align, scrollOffset) { + var direction = _ref4.direction, + height = _ref4.height, + itemCount = _ref4.itemCount, + itemSize = _ref4.itemSize, + layout = _ref4.layout, + width = _ref4.width; + // TODO Deprecate direction "horizontal" + var isHorizontal = direction === 'horizontal' || layout === 'horizontal'; + var size = isHorizontal ? width : height; + var maxOffset = Math.max(0, Math.min(itemCount * itemSize - size, index * itemSize)); + var minOffset = Math.max(0, index * itemSize - size + itemSize); + + if (align === 'smart') { + if (scrollOffset >= minOffset - size && scrollOffset <= maxOffset + size) { + align = 'auto'; + } else { + align = 'center'; + } + } + + switch (align) { + case 'start': + return maxOffset; + + case 'end': + return minOffset; + + case 'center': + return Math.round(minOffset + (maxOffset - minOffset) / 2); + + case 'auto': + default: + if (scrollOffset >= minOffset && scrollOffset <= maxOffset) { + return scrollOffset; + } else if (scrollOffset - minOffset < maxOffset - scrollOffset) { + return minOffset; + } else { + return maxOffset; + } + + } + }, + getStartIndexForOffset: function getStartIndexForOffset(_ref5, offset) { + var itemCount = _ref5.itemCount, + itemSize = _ref5.itemSize; + return Math.max(0, Math.min(itemCount - 1, Math.floor(offset / itemSize))); + }, + getStopIndexForStartIndex: function getStopIndexForStartIndex(_ref6, startIndex, scrollOffset) { + var direction = _ref6.direction, + height = _ref6.height, + itemCount = _ref6.itemCount, + itemSize = _ref6.itemSize, + layout = _ref6.layout, + width = _ref6.width; + // TODO Deprecate direction "horizontal" + var isHorizontal = direction === 'horizontal' || layout === 'horizontal'; + var offset = startIndex * itemSize; + var size = isHorizontal ? width : height; + return Math.max(0, Math.min(itemCount - 1, startIndex + Math.floor((size + (scrollOffset - offset)) / itemSize))); + }, + initInstanceProps: function initInstanceProps(props) {// Noop + }, + shouldResetStyleCacheOnItemSizeChange: true, + validateProps: function validateProps(_ref7) { + var itemSize = _ref7.itemSize; + + if (process.env.NODE_ENV !== 'production') { + if (typeof itemSize !== 'number') { + throw Error('An invalid "itemSize" prop has been specified. ' + 'Value should be a number. ' + ("\"" + (itemSize === null ? 'null' : typeof itemSize) + "\" was specified.")); + } + } + } +}); + +// Pulled from react-compat +// https://github.com/developit/preact-compat/blob/7c5de00e7c85e2ffd011bf3af02899b63f699d3a/src/index.js#L349 +function shallowDiffers(prev, next) { + for (var attribute in prev) { + if (!(attribute in next)) { + return true; + } + } + + for (var _attribute in next) { + if (prev[_attribute] !== next[_attribute]) { + return true; + } + } + + return false; +} + +// It knows to compare individual style props and ignore the wrapper object. +// See https://reactjs.org/docs/react-api.html#reactmemo + +function areEqual(prevProps, nextProps) { + var prevStyle = prevProps.style, + prevRest = _objectWithoutPropertiesLoose(prevProps, ["style"]); + + var nextStyle = nextProps.style, + nextRest = _objectWithoutPropertiesLoose(nextProps, ["style"]); + + return !shallowDiffers(prevStyle, nextStyle) && !shallowDiffers(prevRest, nextRest); +} + +// It knows to compare individual style props and ignore the wrapper object. +// See https://reactjs.org/docs/react-component.html#shouldcomponentupdate + +function shouldComponentUpdate(nextProps, nextState) { + return !areEqual(this.props, nextProps) || shallowDiffers(this.state, nextState); +} + +exports.VariableSizeGrid = VariableSizeGrid; +exports.VariableSizeList = VariableSizeList; +exports.FixedSizeGrid = FixedSizeGrid; +exports.FixedSizeList = FixedSizeList; +exports.areEqual = areEqual; +exports.shouldComponentUpdate = shouldComponentUpdate; diff --git a/vendor/react-window/dist/index.cjs.js.flow b/vendor/react-window/dist/index.cjs.js.flow new file mode 100644 index 0000000000000..6a6528bbe5384 --- /dev/null +++ b/vendor/react-window/dist/index.cjs.js.flow @@ -0,0 +1,3 @@ +// @flow + +export * from '../src'; diff --git a/vendor/react-window/dist/index.esm.js b/vendor/react-window/dist/index.esm.js new file mode 100644 index 0000000000000..c1c2fab71ac78 --- /dev/null +++ b/vendor/react-window/dist/index.esm.js @@ -0,0 +1,1883 @@ +import _extends from '@babel/runtime/helpers/esm/extends'; +import _inheritsLoose from '@babel/runtime/helpers/esm/inheritsLoose'; +import _assertThisInitialized from '@babel/runtime/helpers/esm/assertThisInitialized'; +import memoizeOne from 'memoize-one'; +import { createElement, PureComponent } from 'react'; +import _objectWithoutPropertiesLoose from '@babel/runtime/helpers/esm/objectWithoutPropertiesLoose'; + +// Animation frame based implementation of setTimeout. +// Inspired by Joe Lambert, https://gist.github.com/joelambert/1002116#file-requesttimeout-js +var hasNativePerformanceNow = typeof performance === 'object' && typeof performance.now === 'function'; +var now = hasNativePerformanceNow ? function () { + return performance.now(); +} : function () { + return Date.now(); +}; +function cancelTimeout(timeoutID) { + cancelAnimationFrame(timeoutID.id); +} +function requestTimeout(callback, delay) { + var start = now(); + + function tick() { + if (now() - start >= delay) { + callback.call(null); + } else { + timeoutID.id = requestAnimationFrame(tick); + } + } + + var timeoutID = { + id: requestAnimationFrame(tick) + }; + return timeoutID; +} + +var size = -1; // This utility copied from "dom-helpers" package. + +function getScrollbarSize(recalculate) { + if (recalculate === void 0) { + recalculate = false; + } + + if (size === -1 || recalculate) { + var div = document.createElement('div'); + var style = div.style; + style.width = '50px'; + style.height = '50px'; + style.overflow = 'scroll'; + document.body.appendChild(div); + size = div.offsetWidth - div.clientWidth; + document.body.removeChild(div); + } + + return size; +} + +var IS_SCROLLING_DEBOUNCE_INTERVAL = 150; + +var defaultItemKey = function defaultItemKey(_ref) { + var columnIndex = _ref.columnIndex, + data = _ref.data, + rowIndex = _ref.rowIndex; + return rowIndex + ":" + columnIndex; +}; // In DEV mode, this Set helps us only log a warning once per component instance. +// This avoids spamming the console every time a render happens. + + +var devWarningsOverscanCount = null; +var devWarningsTagName = null; + +if (process.env.NODE_ENV !== 'production') { + if (typeof window !== 'undefined' && typeof window.WeakSet !== 'undefined') { + devWarningsOverscanCount = + /*#__PURE__*/ + new WeakSet(); + devWarningsTagName = + /*#__PURE__*/ + new WeakSet(); + } +} + +function createGridComponent(_ref2) { + var _class, _temp; + + var getColumnOffset = _ref2.getColumnOffset, + getColumnStartIndexForOffset = _ref2.getColumnStartIndexForOffset, + getColumnStopIndexForStartIndex = _ref2.getColumnStopIndexForStartIndex, + getColumnWidth = _ref2.getColumnWidth, + getEstimatedTotalHeight = _ref2.getEstimatedTotalHeight, + getEstimatedTotalWidth = _ref2.getEstimatedTotalWidth, + getOffsetForColumnAndAlignment = _ref2.getOffsetForColumnAndAlignment, + getOffsetForRowAndAlignment = _ref2.getOffsetForRowAndAlignment, + getRowHeight = _ref2.getRowHeight, + getRowOffset = _ref2.getRowOffset, + getRowStartIndexForOffset = _ref2.getRowStartIndexForOffset, + getRowStopIndexForStartIndex = _ref2.getRowStopIndexForStartIndex, + initInstanceProps = _ref2.initInstanceProps, + shouldResetStyleCacheOnItemSizeChange = _ref2.shouldResetStyleCacheOnItemSizeChange, + validateProps = _ref2.validateProps; + return _temp = _class = + /*#__PURE__*/ + function (_PureComponent) { + _inheritsLoose(Grid, _PureComponent); + + // Always use explicit constructor for React components. + // It produces less code after transpilation. (#26) + // eslint-disable-next-line no-useless-constructor + function Grid(props) { + var _this; + + _this = _PureComponent.call(this, props) || this; + _this._instanceProps = initInstanceProps(_this.props, _assertThisInitialized(_assertThisInitialized(_this))); + _this._resetIsScrollingTimeoutId = null; + _this._outerRef = void 0; + _this.state = { + instance: _assertThisInitialized(_assertThisInitialized(_this)), + isScrolling: false, + horizontalScrollDirection: 'forward', + scrollLeft: typeof _this.props.initialScrollLeft === 'number' ? _this.props.initialScrollLeft : 0, + scrollTop: typeof _this.props.initialScrollTop === 'number' ? _this.props.initialScrollTop : 0, + scrollUpdateWasRequested: false, + verticalScrollDirection: 'forward' + }; + _this._callOnItemsRendered = void 0; + _this._callOnItemsRendered = memoizeOne(function (overscanColumnStartIndex, overscanColumnStopIndex, overscanRowStartIndex, overscanRowStopIndex, visibleColumnStartIndex, visibleColumnStopIndex, visibleRowStartIndex, visibleRowStopIndex) { + return _this.props.onItemsRendered({ + overscanColumnStartIndex: overscanColumnStartIndex, + overscanColumnStopIndex: overscanColumnStopIndex, + overscanRowStartIndex: overscanRowStartIndex, + overscanRowStopIndex: overscanRowStopIndex, + visibleColumnStartIndex: visibleColumnStartIndex, + visibleColumnStopIndex: visibleColumnStopIndex, + visibleRowStartIndex: visibleRowStartIndex, + visibleRowStopIndex: visibleRowStopIndex + }); + }); + _this._callOnScroll = void 0; + _this._callOnScroll = memoizeOne(function (scrollLeft, scrollTop, horizontalScrollDirection, verticalScrollDirection, scrollUpdateWasRequested) { + return _this.props.onScroll({ + horizontalScrollDirection: horizontalScrollDirection, + scrollLeft: scrollLeft, + scrollTop: scrollTop, + verticalScrollDirection: verticalScrollDirection, + scrollUpdateWasRequested: scrollUpdateWasRequested + }); + }); + _this._getItemStyle = void 0; + + _this._getItemStyle = function (rowIndex, columnIndex) { + var _this$props = _this.props, + columnWidth = _this$props.columnWidth, + direction = _this$props.direction, + rowHeight = _this$props.rowHeight; + + var itemStyleCache = _this._getItemStyleCache(shouldResetStyleCacheOnItemSizeChange && columnWidth, shouldResetStyleCacheOnItemSizeChange && direction, shouldResetStyleCacheOnItemSizeChange && rowHeight); + + var key = rowIndex + ":" + columnIndex; + var style; + + if (itemStyleCache.hasOwnProperty(key)) { + style = itemStyleCache[key]; + } else { + var _style; + + itemStyleCache[key] = style = (_style = { + position: 'absolute' + }, _style[direction === 'rtl' ? 'right' : 'left'] = getColumnOffset(_this.props, columnIndex, _this._instanceProps), _style.top = getRowOffset(_this.props, rowIndex, _this._instanceProps), _style.height = getRowHeight(_this.props, rowIndex, _this._instanceProps), _style.width = getColumnWidth(_this.props, columnIndex, _this._instanceProps), _style); + } + + return style; + }; + + _this._getItemStyleCache = void 0; + _this._getItemStyleCache = memoizeOne(function (_, __, ___) { + return {}; + }); + + _this._onScroll = function (event) { + var _event$currentTarget = event.currentTarget, + clientWidth = _event$currentTarget.clientWidth, + scrollLeft = _event$currentTarget.scrollLeft, + scrollTop = _event$currentTarget.scrollTop, + scrollWidth = _event$currentTarget.scrollWidth; + + _this.setState(function (prevState) { + if (prevState.scrollLeft === scrollLeft && prevState.scrollTop === scrollTop) { + // Scroll position may have been updated by cDM/cDU, + // In which case we don't need to trigger another render, + // And we don't want to update state.isScrolling. + return null; + } + + var direction = _this.props.direction; // HACK According to the spec, scrollLeft should be negative for RTL aligned elements. + // Chrome does not seem to adhere; its scrollLeft values are positive (measured relative to the left). + // See https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollLeft + + var calculatedScrollLeft = scrollLeft; + + if (direction === 'rtl') { + if (scrollLeft <= 0) { + calculatedScrollLeft = -scrollLeft; + } else { + calculatedScrollLeft = scrollWidth - clientWidth - scrollLeft; + } + } + + return { + isScrolling: true, + horizontalScrollDirection: prevState.scrollLeft < scrollLeft ? 'forward' : 'backward', + scrollLeft: calculatedScrollLeft, + scrollTop: scrollTop, + verticalScrollDirection: prevState.scrollTop < scrollTop ? 'forward' : 'backward', + scrollUpdateWasRequested: false + }; + }, _this._resetIsScrollingDebounced); + }; + + _this._outerRefSetter = function (ref) { + var outerRef = _this.props.outerRef; + _this._outerRef = ref; + + if (typeof outerRef === 'function') { + outerRef(ref); + } else if (outerRef != null && typeof outerRef === 'object' && outerRef.hasOwnProperty('current')) { + outerRef.current = ref; + } + }; + + _this._resetIsScrollingDebounced = function () { + if (_this._resetIsScrollingTimeoutId !== null) { + cancelTimeout(_this._resetIsScrollingTimeoutId); + } + + _this._resetIsScrollingTimeoutId = requestTimeout(_this._resetIsScrolling, IS_SCROLLING_DEBOUNCE_INTERVAL); + }; + + _this._resetIsScrolling = function () { + _this._resetIsScrollingTimeoutId = null; + + _this.setState({ + isScrolling: false + }, function () { + // Clear style cache after state update has been committed. + // This way we don't break pure sCU for items that don't use isScrolling param. + _this._getItemStyleCache(-1); + }); + }; + + return _this; + } + + Grid.getDerivedStateFromProps = function getDerivedStateFromProps(nextProps, prevState) { + validateSharedProps(nextProps, prevState); + validateProps(nextProps); + return null; + }; + + var _proto = Grid.prototype; + + _proto.scrollTo = function scrollTo(_ref3) { + var scrollLeft = _ref3.scrollLeft, + scrollTop = _ref3.scrollTop; + + if (scrollLeft !== undefined) { + scrollLeft = Math.max(0, scrollLeft); + } + + if (scrollTop !== undefined) { + scrollTop = Math.max(0, scrollTop); + } + + this.setState(function (prevState) { + if (scrollLeft === undefined) { + scrollLeft = prevState.scrollLeft; + } + + if (scrollTop === undefined) { + scrollTop = prevState.scrollTop; + } + + if (prevState.scrollLeft === scrollLeft && prevState.scrollTop === scrollTop) { + return null; + } + + return { + horizontalScrollDirection: prevState.scrollLeft < scrollLeft ? 'forward' : 'backward', + scrollLeft: scrollLeft, + scrollTop: scrollTop, + scrollUpdateWasRequested: true, + verticalScrollDirection: prevState.scrollTop < scrollTop ? 'forward' : 'backward' + }; + }, this._resetIsScrollingDebounced); + }; + + _proto.scrollToItem = function scrollToItem(_ref4) { + var _ref4$align = _ref4.align, + align = _ref4$align === void 0 ? 'auto' : _ref4$align, + columnIndex = _ref4.columnIndex, + rowIndex = _ref4.rowIndex; + var _this$props2 = this.props, + columnCount = _this$props2.columnCount, + height = _this$props2.height, + rowCount = _this$props2.rowCount, + width = _this$props2.width; + var _this$state = this.state, + scrollLeft = _this$state.scrollLeft, + scrollTop = _this$state.scrollTop; + var scrollbarSize = getScrollbarSize(); + + if (columnIndex !== undefined) { + columnIndex = Math.max(0, Math.min(columnIndex, columnCount - 1)); + } + + if (rowIndex !== undefined) { + rowIndex = Math.max(0, Math.min(rowIndex, rowCount - 1)); + } + + var estimatedTotalHeight = getEstimatedTotalHeight(this.props, this._instanceProps); + var estimatedTotalWidth = getEstimatedTotalWidth(this.props, this._instanceProps); // The scrollbar size should be considered when scrolling an item into view, + // to ensure it's fully visible. + // But we only need to account for its size when it's actually visible. + + var horizontalScrollbarSize = estimatedTotalWidth > width ? scrollbarSize : 0; + var verticalScrollbarSize = estimatedTotalHeight > height ? scrollbarSize : 0; + this.scrollTo({ + scrollLeft: columnIndex !== undefined ? getOffsetForColumnAndAlignment(this.props, columnIndex, align, scrollLeft, this._instanceProps, verticalScrollbarSize) : scrollLeft, + scrollTop: rowIndex !== undefined ? getOffsetForRowAndAlignment(this.props, rowIndex, align, scrollTop, this._instanceProps, horizontalScrollbarSize) : scrollTop + }); + }; + + _proto.componentDidMount = function componentDidMount() { + var _this$props3 = this.props, + initialScrollLeft = _this$props3.initialScrollLeft, + initialScrollTop = _this$props3.initialScrollTop; + + if (typeof initialScrollLeft === 'number' && this._outerRef != null) { + this._outerRef.scrollLeft = initialScrollLeft; + } + + if (typeof initialScrollTop === 'number' && this._outerRef != null) { + this._outerRef.scrollTop = initialScrollTop; + } + + this._callPropsCallbacks(); + }; + + _proto.componentDidUpdate = function componentDidUpdate() { + var _this$state2 = this.state, + scrollLeft = _this$state2.scrollLeft, + scrollTop = _this$state2.scrollTop, + scrollUpdateWasRequested = _this$state2.scrollUpdateWasRequested; + + if (scrollUpdateWasRequested && this._outerRef !== null) { + this._outerRef.scrollLeft = scrollLeft; + this._outerRef.scrollTop = scrollTop; + } + + this._callPropsCallbacks(); + }; + + _proto.componentWillUnmount = function componentWillUnmount() { + if (this._resetIsScrollingTimeoutId !== null) { + cancelTimeout(this._resetIsScrollingTimeoutId); + } + }; + + _proto.render = function render() { + var _this$props4 = this.props, + children = _this$props4.children, + className = _this$props4.className, + columnCount = _this$props4.columnCount, + direction = _this$props4.direction, + height = _this$props4.height, + innerRef = _this$props4.innerRef, + innerElementType = _this$props4.innerElementType, + innerTagName = _this$props4.innerTagName, + itemData = _this$props4.itemData, + _this$props4$itemKey = _this$props4.itemKey, + itemKey = _this$props4$itemKey === void 0 ? defaultItemKey : _this$props4$itemKey, + outerElementType = _this$props4.outerElementType, + outerTagName = _this$props4.outerTagName, + rowCount = _this$props4.rowCount, + style = _this$props4.style, + useIsScrolling = _this$props4.useIsScrolling, + width = _this$props4.width; + var isScrolling = this.state.isScrolling; + + var _this$_getHorizontalR = this._getHorizontalRangeToRender(), + columnStartIndex = _this$_getHorizontalR[0], + columnStopIndex = _this$_getHorizontalR[1]; + + var _this$_getVerticalRan = this._getVerticalRangeToRender(), + rowStartIndex = _this$_getVerticalRan[0], + rowStopIndex = _this$_getVerticalRan[1]; + + var items = []; + + if (columnCount > 0 && rowCount) { + for (var _rowIndex = rowStartIndex; _rowIndex <= rowStopIndex; _rowIndex++) { + for (var _columnIndex = columnStartIndex; _columnIndex <= columnStopIndex; _columnIndex++) { + items.push(createElement(children, { + columnIndex: _columnIndex, + data: itemData, + isScrolling: useIsScrolling ? isScrolling : undefined, + key: itemKey({ + columnIndex: _columnIndex, + data: itemData, + rowIndex: _rowIndex + }), + rowIndex: _rowIndex, + style: this._getItemStyle(_rowIndex, _columnIndex) + })); + } + } + } // Read this value AFTER items have been created, + // So their actual sizes (if variable) are taken into consideration. + + + var estimatedTotalHeight = getEstimatedTotalHeight(this.props, this._instanceProps); + var estimatedTotalWidth = getEstimatedTotalWidth(this.props, this._instanceProps); + return createElement(outerElementType || outerTagName || 'div', { + className: className, + onScroll: this._onScroll, + ref: this._outerRefSetter, + style: _extends({ + position: 'relative', + height: height, + width: width, + overflow: 'auto', + WebkitOverflowScrolling: 'touch', + willChange: 'transform', + direction: direction + }, style) + }, createElement(innerElementType || innerTagName || 'div', { + children: items, + ref: innerRef, + style: { + height: estimatedTotalHeight, + pointerEvents: isScrolling ? 'none' : '', + width: estimatedTotalWidth + } + })); + }; + + _proto._callPropsCallbacks = function _callPropsCallbacks() { + var _this$props5 = this.props, + columnCount = _this$props5.columnCount, + onItemsRendered = _this$props5.onItemsRendered, + onScroll = _this$props5.onScroll, + rowCount = _this$props5.rowCount; + + if (typeof onItemsRendered === 'function') { + if (columnCount > 0 && rowCount > 0) { + var _this$_getHorizontalR2 = this._getHorizontalRangeToRender(), + _overscanColumnStartIndex = _this$_getHorizontalR2[0], + _overscanColumnStopIndex = _this$_getHorizontalR2[1], + _visibleColumnStartIndex = _this$_getHorizontalR2[2], + _visibleColumnStopIndex = _this$_getHorizontalR2[3]; + + var _this$_getVerticalRan2 = this._getVerticalRangeToRender(), + _overscanRowStartIndex = _this$_getVerticalRan2[0], + _overscanRowStopIndex = _this$_getVerticalRan2[1], + _visibleRowStartIndex = _this$_getVerticalRan2[2], + _visibleRowStopIndex = _this$_getVerticalRan2[3]; + + this._callOnItemsRendered(_overscanColumnStartIndex, _overscanColumnStopIndex, _overscanRowStartIndex, _overscanRowStopIndex, _visibleColumnStartIndex, _visibleColumnStopIndex, _visibleRowStartIndex, _visibleRowStopIndex); + } + } + + if (typeof onScroll === 'function') { + var _this$state3 = this.state, + _horizontalScrollDirection = _this$state3.horizontalScrollDirection, + _scrollLeft = _this$state3.scrollLeft, + _scrollTop = _this$state3.scrollTop, + _scrollUpdateWasRequested = _this$state3.scrollUpdateWasRequested, + _verticalScrollDirection = _this$state3.verticalScrollDirection; + + this._callOnScroll(_scrollLeft, _scrollTop, _horizontalScrollDirection, _verticalScrollDirection, _scrollUpdateWasRequested); + } + }; // Lazily create and cache item styles while scrolling, + // So that pure component sCU will prevent re-renders. + // We maintain this cache, and pass a style prop rather than index, + // So that List can clear cached styles and force item re-render if necessary. + + + _proto._getHorizontalRangeToRender = function _getHorizontalRangeToRender() { + var _this$props6 = this.props, + columnCount = _this$props6.columnCount, + overscanColumnsCount = _this$props6.overscanColumnsCount, + overscanCount = _this$props6.overscanCount, + rowCount = _this$props6.rowCount; + var _this$state4 = this.state, + horizontalScrollDirection = _this$state4.horizontalScrollDirection, + isScrolling = _this$state4.isScrolling, + scrollLeft = _this$state4.scrollLeft; + var overscanCountResolved = overscanColumnsCount || overscanCount || 1; + + if (columnCount === 0 || rowCount === 0) { + return [0, 0, 0, 0]; + } + + var startIndex = getColumnStartIndexForOffset(this.props, scrollLeft, this._instanceProps); + var stopIndex = getColumnStopIndexForStartIndex(this.props, startIndex, scrollLeft, this._instanceProps); // Overscan by one item in each direction so that tab/focus works. + // If there isn't at least one extra item, tab loops back around. + + var overscanBackward = !isScrolling || horizontalScrollDirection === 'backward' ? Math.max(1, overscanCountResolved) : 1; + var overscanForward = !isScrolling || horizontalScrollDirection === 'forward' ? Math.max(1, overscanCountResolved) : 1; + return [Math.max(0, startIndex - overscanBackward), Math.max(0, Math.min(columnCount - 1, stopIndex + overscanForward)), startIndex, stopIndex]; + }; + + _proto._getVerticalRangeToRender = function _getVerticalRangeToRender() { + var _this$props7 = this.props, + columnCount = _this$props7.columnCount, + overscanCount = _this$props7.overscanCount, + overscanRowsCount = _this$props7.overscanRowsCount, + rowCount = _this$props7.rowCount; + var _this$state5 = this.state, + isScrolling = _this$state5.isScrolling, + verticalScrollDirection = _this$state5.verticalScrollDirection, + scrollTop = _this$state5.scrollTop; + var overscanCountResolved = overscanRowsCount || overscanCount || 1; + + if (columnCount === 0 || rowCount === 0) { + return [0, 0, 0, 0]; + } + + var startIndex = getRowStartIndexForOffset(this.props, scrollTop, this._instanceProps); + var stopIndex = getRowStopIndexForStartIndex(this.props, startIndex, scrollTop, this._instanceProps); // Overscan by one item in each direction so that tab/focus works. + // If there isn't at least one extra item, tab loops back around. + + var overscanBackward = !isScrolling || verticalScrollDirection === 'backward' ? Math.max(1, overscanCountResolved) : 1; + var overscanForward = !isScrolling || verticalScrollDirection === 'forward' ? Math.max(1, overscanCountResolved) : 1; + return [Math.max(0, startIndex - overscanBackward), Math.max(0, Math.min(rowCount - 1, stopIndex + overscanForward)), startIndex, stopIndex]; + }; + + return Grid; + }(PureComponent), _class.defaultProps = { + direction: 'ltr', + itemData: undefined, + useIsScrolling: false + }, _temp; +} + +var validateSharedProps = function validateSharedProps(_ref5, _ref6) { + var children = _ref5.children, + direction = _ref5.direction, + height = _ref5.height, + innerTagName = _ref5.innerTagName, + outerTagName = _ref5.outerTagName, + overscanCount = _ref5.overscanCount, + width = _ref5.width; + var instance = _ref6.instance; + + if (process.env.NODE_ENV !== 'production') { + if (typeof overscanCount === 'number') { + if (devWarningsOverscanCount && !devWarningsOverscanCount.has(instance)) { + devWarningsOverscanCount.add(instance); + console.warn('The overscanCount prop has been deprecated. ' + 'Please use the overscanColumnsCount and overscanRowsCount props instead.'); + } + } + + if (innerTagName != null || outerTagName != null) { + if (devWarningsTagName && !devWarningsTagName.has(instance)) { + devWarningsTagName.add(instance); + console.warn('The innerTagName and outerTagName props have been deprecated. ' + 'Please use the innerElementType and outerElementType props instead.'); + } + } + + if (children == null) { + throw Error('An invalid "children" prop has been specified. ' + 'Value should be a React component. ' + ("\"" + (children === null ? 'null' : typeof children) + "\" was specified.")); + } + + switch (direction) { + case 'ltr': + case 'rtl': + // Valid values + break; + + default: + throw Error('An invalid "direction" prop has been specified. ' + 'Value should be either "ltr" or "rtl". ' + ("\"" + direction + "\" was specified.")); + } + + if (typeof width !== 'number') { + throw Error('An invalid "width" prop has been specified. ' + 'Grids must specify a number for width. ' + ("\"" + (width === null ? 'null' : typeof width) + "\" was specified.")); + } + + if (typeof height !== 'number') { + throw Error('An invalid "height" prop has been specified. ' + 'Grids must specify a number for height. ' + ("\"" + (height === null ? 'null' : typeof height) + "\" was specified.")); + } + } +}; + +var DEFAULT_ESTIMATED_ITEM_SIZE = 50; + +var getEstimatedTotalHeight = function getEstimatedTotalHeight(_ref, _ref2) { + var rowCount = _ref.rowCount; + var rowMetadataMap = _ref2.rowMetadataMap, + estimatedRowHeight = _ref2.estimatedRowHeight, + lastMeasuredRowIndex = _ref2.lastMeasuredRowIndex; + var totalSizeOfMeasuredRows = 0; // Edge case check for when the number of items decreases while a scroll is in progress. + // https://github.com/bvaughn/react-window/pull/138 + + if (lastMeasuredRowIndex >= rowCount) { + lastMeasuredRowIndex = rowCount - 1; + } + + if (lastMeasuredRowIndex >= 0) { + var itemMetadata = rowMetadataMap[lastMeasuredRowIndex]; + totalSizeOfMeasuredRows = itemMetadata.offset + itemMetadata.size; + } + + var numUnmeasuredItems = rowCount - lastMeasuredRowIndex - 1; + var totalSizeOfUnmeasuredItems = numUnmeasuredItems * estimatedRowHeight; + return totalSizeOfMeasuredRows + totalSizeOfUnmeasuredItems; +}; + +var getEstimatedTotalWidth = function getEstimatedTotalWidth(_ref3, _ref4) { + var columnCount = _ref3.columnCount; + var columnMetadataMap = _ref4.columnMetadataMap, + estimatedColumnWidth = _ref4.estimatedColumnWidth, + lastMeasuredColumnIndex = _ref4.lastMeasuredColumnIndex; + var totalSizeOfMeasuredRows = 0; // Edge case check for when the number of items decreases while a scroll is in progress. + // https://github.com/bvaughn/react-window/pull/138 + + if (lastMeasuredColumnIndex >= columnCount) { + lastMeasuredColumnIndex = columnCount - 1; + } + + if (lastMeasuredColumnIndex >= 0) { + var itemMetadata = columnMetadataMap[lastMeasuredColumnIndex]; + totalSizeOfMeasuredRows = itemMetadata.offset + itemMetadata.size; + } + + var numUnmeasuredItems = columnCount - lastMeasuredColumnIndex - 1; + var totalSizeOfUnmeasuredItems = numUnmeasuredItems * estimatedColumnWidth; + return totalSizeOfMeasuredRows + totalSizeOfUnmeasuredItems; +}; + +var getItemMetadata = function getItemMetadata(itemType, props, index, instanceProps) { + var itemMetadataMap, itemSize, lastMeasuredIndex; + + if (itemType === 'column') { + itemMetadataMap = instanceProps.columnMetadataMap; + itemSize = props.columnWidth; + lastMeasuredIndex = instanceProps.lastMeasuredColumnIndex; + } else { + itemMetadataMap = instanceProps.rowMetadataMap; + itemSize = props.rowHeight; + lastMeasuredIndex = instanceProps.lastMeasuredRowIndex; + } + + if (index > lastMeasuredIndex) { + var offset = 0; + + if (lastMeasuredIndex >= 0) { + var itemMetadata = itemMetadataMap[lastMeasuredIndex]; + offset = itemMetadata.offset + itemMetadata.size; + } + + for (var i = lastMeasuredIndex + 1; i <= index; i++) { + var size = itemSize(i); + itemMetadataMap[i] = { + offset: offset, + size: size + }; + offset += size; + } + + if (itemType === 'column') { + instanceProps.lastMeasuredColumnIndex = index; + } else { + instanceProps.lastMeasuredRowIndex = index; + } + } + + return itemMetadataMap[index]; +}; + +var findNearestItem = function findNearestItem(itemType, props, instanceProps, offset) { + var itemMetadataMap, lastMeasuredIndex; + + if (itemType === 'column') { + itemMetadataMap = instanceProps.columnMetadataMap; + lastMeasuredIndex = instanceProps.lastMeasuredColumnIndex; + } else { + itemMetadataMap = instanceProps.rowMetadataMap; + lastMeasuredIndex = instanceProps.lastMeasuredRowIndex; + } + + var lastMeasuredItemOffset = lastMeasuredIndex > 0 ? itemMetadataMap[lastMeasuredIndex].offset : 0; + + if (lastMeasuredItemOffset >= offset) { + // If we've already measured items within this range just use a binary search as it's faster. + return findNearestItemBinarySearch(itemType, props, instanceProps, lastMeasuredIndex, 0, offset); + } else { + // If we haven't yet measured this high, fallback to an exponential search with an inner binary search. + // The exponential search avoids pre-computing sizes for the full set of items as a binary search would. + // The overall complexity for this approach is O(log n). + return findNearestItemExponentialSearch(itemType, props, instanceProps, Math.max(0, lastMeasuredIndex), offset); + } +}; + +var findNearestItemBinarySearch = function findNearestItemBinarySearch(itemType, props, instanceProps, high, low, offset) { + while (low <= high) { + var middle = low + Math.floor((high - low) / 2); + var currentOffset = getItemMetadata(itemType, props, middle, instanceProps).offset; + + if (currentOffset === offset) { + return middle; + } else if (currentOffset < offset) { + low = middle + 1; + } else if (currentOffset > offset) { + high = middle - 1; + } + } + + if (low > 0) { + return low - 1; + } else { + return 0; + } +}; + +var findNearestItemExponentialSearch = function findNearestItemExponentialSearch(itemType, props, instanceProps, index, offset) { + var itemCount = itemType === 'column' ? props.columnCount : props.rowCount; + var interval = 1; + + while (index < itemCount && getItemMetadata(itemType, props, index, instanceProps).offset < offset) { + index += interval; + interval *= 2; + } + + return findNearestItemBinarySearch(itemType, props, instanceProps, Math.min(index, itemCount - 1), Math.floor(index / 2), offset); +}; + +var getOffsetForIndexAndAlignment = function getOffsetForIndexAndAlignment(itemType, props, index, align, scrollOffset, instanceProps, scrollbarSize) { + var size = itemType === 'column' ? props.width : props.height; + var itemMetadata = getItemMetadata(itemType, props, index, instanceProps); // Get estimated total size after ItemMetadata is computed, + // To ensure it reflects actual measurements instead of just estimates. + + var estimatedTotalSize = itemType === 'column' ? getEstimatedTotalWidth(props, instanceProps) : getEstimatedTotalHeight(props, instanceProps); + var maxOffset = Math.max(0, Math.min(estimatedTotalSize - size, itemMetadata.offset)); + var minOffset = Math.max(0, itemMetadata.offset - size + scrollbarSize + itemMetadata.size); + + if (align === 'smart') { + if (scrollOffset >= minOffset - size && scrollOffset <= maxOffset + size) { + align = 'auto'; + } else { + align = 'center'; + } + } + + switch (align) { + case 'start': + return maxOffset; + + case 'end': + return minOffset; + + case 'center': + return Math.round(minOffset + (maxOffset - minOffset) / 2); + + case 'auto': + default: + if (scrollOffset >= minOffset && scrollOffset <= maxOffset) { + return scrollOffset; + } else if (scrollOffset - minOffset < maxOffset - scrollOffset) { + return minOffset; + } else { + return maxOffset; + } + + } +}; + +var VariableSizeGrid = +/*#__PURE__*/ +createGridComponent({ + getColumnOffset: function getColumnOffset(props, index, instanceProps) { + return getItemMetadata('column', props, index, instanceProps).offset; + }, + getColumnStartIndexForOffset: function getColumnStartIndexForOffset(props, scrollLeft, instanceProps) { + return findNearestItem('column', props, instanceProps, scrollLeft); + }, + getColumnStopIndexForStartIndex: function getColumnStopIndexForStartIndex(props, startIndex, scrollLeft, instanceProps) { + var columnCount = props.columnCount, + width = props.width; + var itemMetadata = getItemMetadata('column', props, startIndex, instanceProps); + var maxOffset = scrollLeft + width; + var offset = itemMetadata.offset + itemMetadata.size; + var stopIndex = startIndex; + + while (stopIndex < columnCount - 1 && offset < maxOffset) { + stopIndex++; + offset += getItemMetadata('column', props, stopIndex, instanceProps).size; + } + + return stopIndex; + }, + getColumnWidth: function getColumnWidth(props, index, instanceProps) { + return instanceProps.columnMetadataMap[index].size; + }, + getEstimatedTotalHeight: getEstimatedTotalHeight, + getEstimatedTotalWidth: getEstimatedTotalWidth, + getOffsetForColumnAndAlignment: function getOffsetForColumnAndAlignment(props, index, align, scrollOffset, instanceProps, scrollbarSize) { + return getOffsetForIndexAndAlignment('column', props, index, align, scrollOffset, instanceProps, scrollbarSize); + }, + getOffsetForRowAndAlignment: function getOffsetForRowAndAlignment(props, index, align, scrollOffset, instanceProps, scrollbarSize) { + return getOffsetForIndexAndAlignment('row', props, index, align, scrollOffset, instanceProps, scrollbarSize); + }, + getRowOffset: function getRowOffset(props, index, instanceProps) { + return getItemMetadata('row', props, index, instanceProps).offset; + }, + getRowHeight: function getRowHeight(props, index, instanceProps) { + return instanceProps.rowMetadataMap[index].size; + }, + getRowStartIndexForOffset: function getRowStartIndexForOffset(props, scrollTop, instanceProps) { + return findNearestItem('row', props, instanceProps, scrollTop); + }, + getRowStopIndexForStartIndex: function getRowStopIndexForStartIndex(props, startIndex, scrollTop, instanceProps) { + var rowCount = props.rowCount, + height = props.height; + var itemMetadata = getItemMetadata('row', props, startIndex, instanceProps); + var maxOffset = scrollTop + height; + var offset = itemMetadata.offset + itemMetadata.size; + var stopIndex = startIndex; + + while (stopIndex < rowCount - 1 && offset < maxOffset) { + stopIndex++; + offset += getItemMetadata('row', props, stopIndex, instanceProps).size; + } + + return stopIndex; + }, + initInstanceProps: function initInstanceProps(props, instance) { + var _ref5 = props, + estimatedColumnWidth = _ref5.estimatedColumnWidth, + estimatedRowHeight = _ref5.estimatedRowHeight; + var instanceProps = { + columnMetadataMap: {}, + estimatedColumnWidth: estimatedColumnWidth || DEFAULT_ESTIMATED_ITEM_SIZE, + estimatedRowHeight: estimatedRowHeight || DEFAULT_ESTIMATED_ITEM_SIZE, + lastMeasuredColumnIndex: -1, + lastMeasuredRowIndex: -1, + rowMetadataMap: {} + }; + + instance.resetAfterColumnIndex = function (columnIndex, shouldForceUpdate) { + if (shouldForceUpdate === void 0) { + shouldForceUpdate = true; + } + + instance.resetAfterIndices({ + columnIndex: columnIndex, + shouldForceUpdate: shouldForceUpdate + }); + }; + + instance.resetAfterRowIndex = function (rowIndex, shouldForceUpdate) { + if (shouldForceUpdate === void 0) { + shouldForceUpdate = true; + } + + instance.resetAfterIndices({ + rowIndex: rowIndex, + shouldForceUpdate: shouldForceUpdate + }); + }; + + instance.resetAfterIndices = function (_ref6) { + var columnIndex = _ref6.columnIndex, + rowIndex = _ref6.rowIndex, + _ref6$shouldForceUpda = _ref6.shouldForceUpdate, + shouldForceUpdate = _ref6$shouldForceUpda === void 0 ? true : _ref6$shouldForceUpda; + + if (typeof columnIndex === 'number') { + instanceProps.lastMeasuredColumnIndex = Math.min(instanceProps.lastMeasuredColumnIndex, columnIndex - 1); + } + + if (typeof rowIndex === 'number') { + instanceProps.lastMeasuredRowIndex = Math.min(instanceProps.lastMeasuredRowIndex, rowIndex - 1); + } // We could potentially optimize further by only evicting styles after this index, + // But since styles are only cached while scrolling is in progress- + // It seems an unnecessary optimization. + // It's unlikely that resetAfterIndex() will be called while a user is scrolling. + + + instance._getItemStyleCache(-1); + + if (shouldForceUpdate) { + instance.forceUpdate(); + } + }; + + return instanceProps; + }, + shouldResetStyleCacheOnItemSizeChange: false, + validateProps: function validateProps(_ref7) { + var columnWidth = _ref7.columnWidth, + rowHeight = _ref7.rowHeight; + + if (process.env.NODE_ENV !== 'production') { + if (typeof columnWidth !== 'function') { + throw Error('An invalid "columnWidth" prop has been specified. ' + 'Value should be a function. ' + ("\"" + (columnWidth === null ? 'null' : typeof columnWidth) + "\" was specified.")); + } else if (typeof rowHeight !== 'function') { + throw Error('An invalid "rowHeight" prop has been specified. ' + 'Value should be a function. ' + ("\"" + (rowHeight === null ? 'null' : typeof rowHeight) + "\" was specified.")); + } + } + } +}); + +var IS_SCROLLING_DEBOUNCE_INTERVAL$1 = 150; + +var defaultItemKey$1 = function defaultItemKey(index, data) { + return index; +}; // In DEV mode, this Set helps us only log a warning once per component instance. +// This avoids spamming the console every time a render happens. + + +var devWarningsDirection = null; +var devWarningsTagName$1 = null; + +if (process.env.NODE_ENV !== 'production') { + if (typeof window !== 'undefined' && typeof window.WeakSet !== 'undefined') { + devWarningsDirection = + /*#__PURE__*/ + new WeakSet(); + devWarningsTagName$1 = + /*#__PURE__*/ + new WeakSet(); + } +} + +function createListComponent(_ref) { + var _class, _temp; + + var getItemOffset = _ref.getItemOffset, + getEstimatedTotalSize = _ref.getEstimatedTotalSize, + getItemSize = _ref.getItemSize, + getOffsetForIndexAndAlignment = _ref.getOffsetForIndexAndAlignment, + getStartIndexForOffset = _ref.getStartIndexForOffset, + getStopIndexForStartIndex = _ref.getStopIndexForStartIndex, + initInstanceProps = _ref.initInstanceProps, + shouldResetStyleCacheOnItemSizeChange = _ref.shouldResetStyleCacheOnItemSizeChange, + validateProps = _ref.validateProps; + return _temp = _class = + /*#__PURE__*/ + function (_PureComponent) { + _inheritsLoose(List, _PureComponent); + + // Always use explicit constructor for React components. + // It produces less code after transpilation. (#26) + // eslint-disable-next-line no-useless-constructor + function List(props) { + var _this; + + _this = _PureComponent.call(this, props) || this; + _this._instanceProps = initInstanceProps(_this.props, _assertThisInitialized(_assertThisInitialized(_this))); + _this._outerRef = void 0; + _this._resetIsScrollingTimeoutId = null; + _this.state = { + instance: _assertThisInitialized(_assertThisInitialized(_this)), + isScrolling: false, + scrollDirection: 'forward', + scrollOffset: typeof _this.props.initialScrollOffset === 'number' ? _this.props.initialScrollOffset : 0, + scrollUpdateWasRequested: false + }; + _this._callOnItemsRendered = void 0; + _this._callOnItemsRendered = memoizeOne(function (overscanStartIndex, overscanStopIndex, visibleStartIndex, visibleStopIndex) { + return _this.props.onItemsRendered({ + overscanStartIndex: overscanStartIndex, + overscanStopIndex: overscanStopIndex, + visibleStartIndex: visibleStartIndex, + visibleStopIndex: visibleStopIndex + }); + }); + _this._callOnScroll = void 0; + _this._callOnScroll = memoizeOne(function (scrollDirection, scrollOffset, scrollUpdateWasRequested) { + return _this.props.onScroll({ + scrollDirection: scrollDirection, + scrollOffset: scrollOffset, + scrollUpdateWasRequested: scrollUpdateWasRequested + }); + }); + _this._getItemStyle = void 0; + + _this._getItemStyle = function (index) { + var _this$props = _this.props, + direction = _this$props.direction, + itemSize = _this$props.itemSize, + layout = _this$props.layout; + + var itemStyleCache = _this._getItemStyleCache(shouldResetStyleCacheOnItemSizeChange && itemSize, shouldResetStyleCacheOnItemSizeChange && layout, shouldResetStyleCacheOnItemSizeChange && direction); + + var style; + + if (itemStyleCache.hasOwnProperty(index)) { + style = itemStyleCache[index]; + } else { + var _style; + + var _offset = getItemOffset(_this.props, index, _this._instanceProps); + + var size = getItemSize(_this.props, index, _this._instanceProps); // TODO Deprecate direction "horizontal" + + var isHorizontal = direction === 'horizontal' || layout === 'horizontal'; + itemStyleCache[index] = style = (_style = { + position: 'absolute' + }, _style[direction === 'rtl' ? 'right' : 'left'] = isHorizontal ? _offset : 0, _style.top = !isHorizontal ? _offset : 0, _style.height = !isHorizontal ? size : '100%', _style.width = isHorizontal ? size : '100%', _style); + } + + return style; + }; + + _this._getItemStyleCache = void 0; + _this._getItemStyleCache = memoizeOne(function (_, __, ___) { + return {}; + }); + + _this._onScrollHorizontal = function (event) { + var _event$currentTarget = event.currentTarget, + clientWidth = _event$currentTarget.clientWidth, + scrollLeft = _event$currentTarget.scrollLeft, + scrollWidth = _event$currentTarget.scrollWidth; + + _this.setState(function (prevState) { + if (prevState.scrollOffset === scrollLeft) { + // Scroll position may have been updated by cDM/cDU, + // In which case we don't need to trigger another render, + // And we don't want to update state.isScrolling. + return null; + } + + var direction = _this.props.direction; // HACK According to the spec, scrollLeft should be negative for RTL aligned elements. + // Chrome does not seem to adhere; its scrolLeft values are positive (measured relative to the left). + // See https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollLeft + + var scrollOffset = scrollLeft; + + if (direction === 'rtl') { + if (scrollLeft <= 0) { + scrollOffset = -scrollOffset; + } else { + scrollOffset = scrollWidth - clientWidth - scrollLeft; + } + } + + return { + isScrolling: true, + scrollDirection: prevState.scrollOffset < scrollLeft ? 'forward' : 'backward', + scrollOffset: scrollOffset, + scrollUpdateWasRequested: false + }; + }, _this._resetIsScrollingDebounced); + }; + + _this._onScrollVertical = function (event) { + var scrollTop = event.currentTarget.scrollTop; + + _this.setState(function (prevState) { + if (prevState.scrollOffset === scrollTop) { + // Scroll position may have been updated by cDM/cDU, + // In which case we don't need to trigger another render, + // And we don't want to update state.isScrolling. + return null; + } + + return { + isScrolling: true, + scrollDirection: prevState.scrollOffset < scrollTop ? 'forward' : 'backward', + scrollOffset: scrollTop, + scrollUpdateWasRequested: false + }; + }, _this._resetIsScrollingDebounced); + }; + + _this._outerRefSetter = function (ref) { + var outerRef = _this.props.outerRef; + _this._outerRef = ref; + + if (typeof outerRef === 'function') { + outerRef(ref); + } else if (outerRef != null && typeof outerRef === 'object' && outerRef.hasOwnProperty('current')) { + outerRef.current = ref; + } + }; + + _this._resetIsScrollingDebounced = function () { + if (_this._resetIsScrollingTimeoutId !== null) { + cancelTimeout(_this._resetIsScrollingTimeoutId); + } + + _this._resetIsScrollingTimeoutId = requestTimeout(_this._resetIsScrolling, IS_SCROLLING_DEBOUNCE_INTERVAL$1); + }; + + _this._resetIsScrolling = function () { + _this._resetIsScrollingTimeoutId = null; + + _this.setState({ + isScrolling: false + }, function () { + // Clear style cache after state update has been committed. + // This way we don't break pure sCU for items that don't use isScrolling param. + _this._getItemStyleCache(-1, null); + }); + }; + + return _this; + } + + List.getDerivedStateFromProps = function getDerivedStateFromProps(nextProps, prevState) { + validateSharedProps$1(nextProps, prevState); + validateProps(nextProps); + return null; + }; + + var _proto = List.prototype; + + _proto.scrollTo = function scrollTo(scrollOffset) { + scrollOffset = Math.max(0, scrollOffset); + this.setState(function (prevState) { + if (prevState.scrollOffset === scrollOffset) { + return null; + } + + return { + scrollDirection: prevState.scrollOffset < scrollOffset ? 'forward' : 'backward', + scrollOffset: scrollOffset, + scrollUpdateWasRequested: true + }; + }, this._resetIsScrollingDebounced); + }; + + _proto.scrollToItem = function scrollToItem(index, align) { + if (align === void 0) { + align = 'auto'; + } + + var itemCount = this.props.itemCount; + var scrollOffset = this.state.scrollOffset; + index = Math.max(0, Math.min(index, itemCount - 1)); + this.scrollTo(getOffsetForIndexAndAlignment(this.props, index, align, scrollOffset, this._instanceProps)); + }; + + _proto.componentDidMount = function componentDidMount() { + var _this$props2 = this.props, + direction = _this$props2.direction, + initialScrollOffset = _this$props2.initialScrollOffset, + layout = _this$props2.layout; + + if (typeof initialScrollOffset === 'number' && this._outerRef !== null) { + // TODO Deprecate direction "horizontal" + if (direction === 'horizontal' || layout === 'horizontal') { + this._outerRef.scrollLeft = initialScrollOffset; + } else { + this._outerRef.scrollTop = initialScrollOffset; + } + } + + this._callPropsCallbacks(); + }; + + _proto.componentDidUpdate = function componentDidUpdate() { + var _this$props3 = this.props, + direction = _this$props3.direction, + layout = _this$props3.layout; + var _this$state = this.state, + scrollOffset = _this$state.scrollOffset, + scrollUpdateWasRequested = _this$state.scrollUpdateWasRequested; + + if (scrollUpdateWasRequested && this._outerRef !== null) { + // TODO Deprecate direction "horizontal" + if (direction === 'horizontal' || layout === 'horizontal') { + this._outerRef.scrollLeft = scrollOffset; + } else { + this._outerRef.scrollTop = scrollOffset; + } + } + + this._callPropsCallbacks(); + }; + + _proto.componentWillUnmount = function componentWillUnmount() { + if (this._resetIsScrollingTimeoutId !== null) { + cancelTimeout(this._resetIsScrollingTimeoutId); + } + }; + + _proto.render = function render() { + var _this$props4 = this.props, + children = _this$props4.children, + className = _this$props4.className, + direction = _this$props4.direction, + height = _this$props4.height, + innerRef = _this$props4.innerRef, + innerElementType = _this$props4.innerElementType, + innerTagName = _this$props4.innerTagName, + itemCount = _this$props4.itemCount, + itemData = _this$props4.itemData, + _this$props4$itemKey = _this$props4.itemKey, + itemKey = _this$props4$itemKey === void 0 ? defaultItemKey$1 : _this$props4$itemKey, + layout = _this$props4.layout, + outerElementType = _this$props4.outerElementType, + outerTagName = _this$props4.outerTagName, + style = _this$props4.style, + useIsScrolling = _this$props4.useIsScrolling, + width = _this$props4.width; + var isScrolling = this.state.isScrolling; // TODO Deprecate direction "horizontal" + + var isHorizontal = direction === 'horizontal' || layout === 'horizontal'; + var onScroll = isHorizontal ? this._onScrollHorizontal : this._onScrollVertical; + + var _this$_getRangeToRend = this._getRangeToRender(), + startIndex = _this$_getRangeToRend[0], + stopIndex = _this$_getRangeToRend[1]; + + var items = []; + + if (itemCount > 0) { + for (var _index = startIndex; _index <= stopIndex; _index++) { + items.push(createElement(children, { + data: itemData, + key: itemKey(_index, itemData), + index: _index, + isScrolling: useIsScrolling ? isScrolling : undefined, + style: this._getItemStyle(_index) + })); + } + } // Read this value AFTER items have been created, + // So their actual sizes (if variable) are taken into consideration. + + + var estimatedTotalSize = getEstimatedTotalSize(this.props, this._instanceProps); + return createElement(outerElementType || outerTagName || 'div', { + className: className, + onScroll: onScroll, + ref: this._outerRefSetter, + style: _extends({ + position: 'relative', + height: height, + width: width, + overflow: 'auto', + WebkitOverflowScrolling: 'touch', + willChange: 'transform', + direction: direction + }, style) + }, createElement(innerElementType || innerTagName || 'div', { + children: items, + ref: innerRef, + style: { + height: isHorizontal ? '100%' : estimatedTotalSize, + pointerEvents: isScrolling ? 'none' : '', + width: isHorizontal ? estimatedTotalSize : '100%' + } + })); + }; + + _proto._callPropsCallbacks = function _callPropsCallbacks() { + if (typeof this.props.onItemsRendered === 'function') { + var itemCount = this.props.itemCount; + + if (itemCount > 0) { + var _this$_getRangeToRend2 = this._getRangeToRender(), + _overscanStartIndex = _this$_getRangeToRend2[0], + _overscanStopIndex = _this$_getRangeToRend2[1], + _visibleStartIndex = _this$_getRangeToRend2[2], + _visibleStopIndex = _this$_getRangeToRend2[3]; + + this._callOnItemsRendered(_overscanStartIndex, _overscanStopIndex, _visibleStartIndex, _visibleStopIndex); + } + } + + if (typeof this.props.onScroll === 'function') { + var _this$state2 = this.state, + _scrollDirection = _this$state2.scrollDirection, + _scrollOffset = _this$state2.scrollOffset, + _scrollUpdateWasRequested = _this$state2.scrollUpdateWasRequested; + + this._callOnScroll(_scrollDirection, _scrollOffset, _scrollUpdateWasRequested); + } + }; // Lazily create and cache item styles while scrolling, + // So that pure component sCU will prevent re-renders. + // We maintain this cache, and pass a style prop rather than index, + // So that List can clear cached styles and force item re-render if necessary. + + + _proto._getRangeToRender = function _getRangeToRender() { + var _this$props5 = this.props, + itemCount = _this$props5.itemCount, + overscanCount = _this$props5.overscanCount; + var _this$state3 = this.state, + isScrolling = _this$state3.isScrolling, + scrollDirection = _this$state3.scrollDirection, + scrollOffset = _this$state3.scrollOffset; + + if (itemCount === 0) { + return [0, 0, 0, 0]; + } + + var startIndex = getStartIndexForOffset(this.props, scrollOffset, this._instanceProps); + var stopIndex = getStopIndexForStartIndex(this.props, startIndex, scrollOffset, this._instanceProps); // Overscan by one item in each direction so that tab/focus works. + // If there isn't at least one extra item, tab loops back around. + + var overscanBackward = !isScrolling || scrollDirection === 'backward' ? Math.max(1, overscanCount) : 1; + var overscanForward = !isScrolling || scrollDirection === 'forward' ? Math.max(1, overscanCount) : 1; + return [Math.max(0, startIndex - overscanBackward), Math.max(0, Math.min(itemCount - 1, stopIndex + overscanForward)), startIndex, stopIndex]; + }; + + return List; + }(PureComponent), _class.defaultProps = { + direction: 'ltr', + itemData: undefined, + layout: 'vertical', + overscanCount: 2, + useIsScrolling: false + }, _temp; +} // NOTE: I considered further wrapping individual items with a pure ListItem component. +// This would avoid ever calling the render function for the same index more than once, +// But it would also add the overhead of a lot of components/fibers. +// I assume people already do this (render function returning a class component), +// So my doing it would just unnecessarily double the wrappers. + +var validateSharedProps$1 = function validateSharedProps(_ref2, _ref3) { + var children = _ref2.children, + direction = _ref2.direction, + height = _ref2.height, + layout = _ref2.layout, + innerTagName = _ref2.innerTagName, + outerTagName = _ref2.outerTagName, + width = _ref2.width; + var instance = _ref3.instance; + + if (process.env.NODE_ENV !== 'production') { + if (innerTagName != null || outerTagName != null) { + if (devWarningsTagName$1 && !devWarningsTagName$1.has(instance)) { + devWarningsTagName$1.add(instance); + console.warn('The innerTagName and outerTagName props have been deprecated. ' + 'Please use the innerElementType and outerElementType props instead.'); + } + } // TODO Deprecate direction "horizontal" + + + var isHorizontal = direction === 'horizontal' || layout === 'horizontal'; + + switch (direction) { + case 'horizontal': + case 'vertical': + if (devWarningsDirection && !devWarningsDirection.has(instance)) { + devWarningsDirection.add(instance); + console.warn('The direction prop should be either "ltr" (default) or "rtl". ' + 'Please use the layout prop to specify "vertical" (default) or "horizontal" orientation.'); + } + + break; + + case 'ltr': + case 'rtl': + // Valid values + break; + + default: + throw Error('An invalid "direction" prop has been specified. ' + 'Value should be either "ltr" or "rtl". ' + ("\"" + direction + "\" was specified.")); + } + + switch (layout) { + case 'horizontal': + case 'vertical': + // Valid values + break; + + default: + throw Error('An invalid "layout" prop has been specified. ' + 'Value should be either "horizontal" or "vertical". ' + ("\"" + layout + "\" was specified.")); + } + + if (children == null) { + throw Error('An invalid "children" prop has been specified. ' + 'Value should be a React component. ' + ("\"" + (children === null ? 'null' : typeof children) + "\" was specified.")); + } + + if (isHorizontal && typeof width !== 'number') { + throw Error('An invalid "width" prop has been specified. ' + 'Horizontal lists must specify a number for width. ' + ("\"" + (width === null ? 'null' : typeof width) + "\" was specified.")); + } else if (!isHorizontal && typeof height !== 'number') { + throw Error('An invalid "height" prop has been specified. ' + 'Vertical lists must specify a number for height. ' + ("\"" + (height === null ? 'null' : typeof height) + "\" was specified.")); + } + } +}; + +var DEFAULT_ESTIMATED_ITEM_SIZE$1 = 50; + +var getItemMetadata$1 = function getItemMetadata(props, index, instanceProps) { + var _ref = props, + itemSize = _ref.itemSize; + var itemMetadataMap = instanceProps.itemMetadataMap, + lastMeasuredIndex = instanceProps.lastMeasuredIndex; + + if (index > lastMeasuredIndex) { + var offset = 0; + + if (lastMeasuredIndex >= 0) { + var itemMetadata = itemMetadataMap[lastMeasuredIndex]; + offset = itemMetadata.offset + itemMetadata.size; + } + + for (var i = lastMeasuredIndex + 1; i <= index; i++) { + var size = itemSize(i); + itemMetadataMap[i] = { + offset: offset, + size: size + }; + offset += size; + } + + instanceProps.lastMeasuredIndex = index; + } + + return itemMetadataMap[index]; +}; + +var findNearestItem$1 = function findNearestItem(props, instanceProps, offset) { + var itemMetadataMap = instanceProps.itemMetadataMap, + lastMeasuredIndex = instanceProps.lastMeasuredIndex; + var lastMeasuredItemOffset = lastMeasuredIndex > 0 ? itemMetadataMap[lastMeasuredIndex].offset : 0; + + if (lastMeasuredItemOffset >= offset) { + // If we've already measured items within this range just use a binary search as it's faster. + return findNearestItemBinarySearch$1(props, instanceProps, lastMeasuredIndex, 0, offset); + } else { + // If we haven't yet measured this high, fallback to an exponential search with an inner binary search. + // The exponential search avoids pre-computing sizes for the full set of items as a binary search would. + // The overall complexity for this approach is O(log n). + return findNearestItemExponentialSearch$1(props, instanceProps, Math.max(0, lastMeasuredIndex), offset); + } +}; + +var findNearestItemBinarySearch$1 = function findNearestItemBinarySearch(props, instanceProps, high, low, offset) { + while (low <= high) { + var middle = low + Math.floor((high - low) / 2); + var currentOffset = getItemMetadata$1(props, middle, instanceProps).offset; + + if (currentOffset === offset) { + return middle; + } else if (currentOffset < offset) { + low = middle + 1; + } else if (currentOffset > offset) { + high = middle - 1; + } + } + + if (low > 0) { + return low - 1; + } else { + return 0; + } +}; + +var findNearestItemExponentialSearch$1 = function findNearestItemExponentialSearch(props, instanceProps, index, offset) { + var itemCount = props.itemCount; + var interval = 1; + + while (index < itemCount && getItemMetadata$1(props, index, instanceProps).offset < offset) { + index += interval; + interval *= 2; + } + + return findNearestItemBinarySearch$1(props, instanceProps, Math.min(index, itemCount - 1), Math.floor(index / 2), offset); +}; + +var getEstimatedTotalSize = function getEstimatedTotalSize(_ref2, _ref3) { + var itemCount = _ref2.itemCount; + var itemMetadataMap = _ref3.itemMetadataMap, + estimatedItemSize = _ref3.estimatedItemSize, + lastMeasuredIndex = _ref3.lastMeasuredIndex; + var totalSizeOfMeasuredItems = 0; // Edge case check for when the number of items decreases while a scroll is in progress. + // https://github.com/bvaughn/react-window/pull/138 + + if (lastMeasuredIndex >= itemCount) { + lastMeasuredIndex = itemCount - 1; + } + + if (lastMeasuredIndex >= 0) { + var itemMetadata = itemMetadataMap[lastMeasuredIndex]; + totalSizeOfMeasuredItems = itemMetadata.offset + itemMetadata.size; + } + + var numUnmeasuredItems = itemCount - lastMeasuredIndex - 1; + var totalSizeOfUnmeasuredItems = numUnmeasuredItems * estimatedItemSize; + return totalSizeOfMeasuredItems + totalSizeOfUnmeasuredItems; +}; + +var VariableSizeList = +/*#__PURE__*/ +createListComponent({ + getItemOffset: function getItemOffset(props, index, instanceProps) { + return getItemMetadata$1(props, index, instanceProps).offset; + }, + getItemSize: function getItemSize(props, index, instanceProps) { + return instanceProps.itemMetadataMap[index].size; + }, + getEstimatedTotalSize: getEstimatedTotalSize, + getOffsetForIndexAndAlignment: function getOffsetForIndexAndAlignment(props, index, align, scrollOffset, instanceProps) { + var direction = props.direction, + height = props.height, + layout = props.layout, + width = props.width; // TODO Deprecate direction "horizontal" + + var isHorizontal = direction === 'horizontal' || layout === 'horizontal'; + var size = isHorizontal ? width : height; + var itemMetadata = getItemMetadata$1(props, index, instanceProps); // Get estimated total size after ItemMetadata is computed, + // To ensure it reflects actual measurements instead of just estimates. + + var estimatedTotalSize = getEstimatedTotalSize(props, instanceProps); + var maxOffset = Math.max(0, Math.min(estimatedTotalSize - size, itemMetadata.offset)); + var minOffset = Math.max(0, itemMetadata.offset - size + itemMetadata.size); + + if (align === 'smart') { + if (scrollOffset >= minOffset - size && scrollOffset <= maxOffset + size) { + align = 'auto'; + } else { + align = 'center'; + } + } + + switch (align) { + case 'start': + return maxOffset; + + case 'end': + return minOffset; + + case 'center': + return Math.round(minOffset + (maxOffset - minOffset) / 2); + + case 'auto': + default: + if (scrollOffset >= minOffset && scrollOffset <= maxOffset) { + return scrollOffset; + } else if (scrollOffset - minOffset < maxOffset - scrollOffset) { + return minOffset; + } else { + return maxOffset; + } + + } + }, + getStartIndexForOffset: function getStartIndexForOffset(props, offset, instanceProps) { + return findNearestItem$1(props, instanceProps, offset); + }, + getStopIndexForStartIndex: function getStopIndexForStartIndex(props, startIndex, scrollOffset, instanceProps) { + var direction = props.direction, + height = props.height, + itemCount = props.itemCount, + layout = props.layout, + width = props.width; // TODO Deprecate direction "horizontal" + + var isHorizontal = direction === 'horizontal' || layout === 'horizontal'; + var size = isHorizontal ? width : height; + var itemMetadata = getItemMetadata$1(props, startIndex, instanceProps); + var maxOffset = scrollOffset + size; + var offset = itemMetadata.offset + itemMetadata.size; + var stopIndex = startIndex; + + while (stopIndex < itemCount - 1 && offset < maxOffset) { + stopIndex++; + offset += getItemMetadata$1(props, stopIndex, instanceProps).size; + } + + return stopIndex; + }, + initInstanceProps: function initInstanceProps(props, instance) { + var _ref4 = props, + estimatedItemSize = _ref4.estimatedItemSize; + var instanceProps = { + itemMetadataMap: {}, + estimatedItemSize: estimatedItemSize || DEFAULT_ESTIMATED_ITEM_SIZE$1, + lastMeasuredIndex: -1 + }; + + instance.resetAfterIndex = function (index, shouldForceUpdate) { + if (shouldForceUpdate === void 0) { + shouldForceUpdate = true; + } + + instanceProps.lastMeasuredIndex = Math.min(instanceProps.lastMeasuredIndex, index - 1); // We could potentially optimize further by only evicting styles after this index, + // But since styles are only cached while scrolling is in progress- + // It seems an unnecessary optimization. + // It's unlikely that resetAfterIndex() will be called while a user is scrolling. + + instance._getItemStyleCache(-1); + + if (shouldForceUpdate) { + instance.forceUpdate(); + } + }; + + return instanceProps; + }, + shouldResetStyleCacheOnItemSizeChange: false, + validateProps: function validateProps(_ref5) { + var itemSize = _ref5.itemSize; + + if (process.env.NODE_ENV !== 'production') { + if (typeof itemSize !== 'function') { + throw Error('An invalid "itemSize" prop has been specified. ' + 'Value should be a function. ' + ("\"" + (itemSize === null ? 'null' : typeof itemSize) + "\" was specified.")); + } + } + } +}); + +var FixedSizeGrid = +/*#__PURE__*/ +createGridComponent({ + getColumnOffset: function getColumnOffset(_ref, index) { + var columnWidth = _ref.columnWidth; + return index * columnWidth; + }, + getColumnWidth: function getColumnWidth(_ref2, index) { + var columnWidth = _ref2.columnWidth; + return columnWidth; + }, + getRowOffset: function getRowOffset(_ref3, index) { + var rowHeight = _ref3.rowHeight; + return index * rowHeight; + }, + getRowHeight: function getRowHeight(_ref4, index) { + var rowHeight = _ref4.rowHeight; + return rowHeight; + }, + getEstimatedTotalHeight: function getEstimatedTotalHeight(_ref5) { + var rowCount = _ref5.rowCount, + rowHeight = _ref5.rowHeight; + return rowHeight * rowCount; + }, + getEstimatedTotalWidth: function getEstimatedTotalWidth(_ref6) { + var columnCount = _ref6.columnCount, + columnWidth = _ref6.columnWidth; + return columnWidth * columnCount; + }, + getOffsetForColumnAndAlignment: function getOffsetForColumnAndAlignment(_ref7, columnIndex, align, scrollLeft, instanceProps, scrollbarSize) { + var columnCount = _ref7.columnCount, + columnWidth = _ref7.columnWidth, + width = _ref7.width; + var maxOffset = Math.max(0, Math.min(columnCount * columnWidth - width, columnIndex * columnWidth)); + var minOffset = Math.max(0, columnIndex * columnWidth - width + scrollbarSize + columnWidth); + + if (align === 'smart') { + if (scrollLeft >= minOffset - width && scrollLeft <= maxOffset + width) { + align = 'auto'; + } else { + align = 'center'; + } + } + + switch (align) { + case 'start': + return maxOffset; + + case 'end': + return minOffset; + + case 'center': + return Math.round(minOffset + (maxOffset - minOffset) / 2); + + case 'auto': + default: + if (scrollLeft >= minOffset && scrollLeft <= maxOffset) { + return scrollLeft; + } else if (scrollLeft - minOffset < maxOffset - scrollLeft) { + return minOffset; + } else { + return maxOffset; + } + + } + }, + getOffsetForRowAndAlignment: function getOffsetForRowAndAlignment(_ref8, rowIndex, align, scrollTop, instanceProps, scrollbarSize) { + var rowHeight = _ref8.rowHeight, + height = _ref8.height, + rowCount = _ref8.rowCount; + var maxOffset = Math.max(0, Math.min(rowCount * rowHeight - height, rowIndex * rowHeight)); + var minOffset = Math.max(0, rowIndex * rowHeight - height + scrollbarSize + rowHeight); + + if (align === 'smart') { + if (scrollTop >= minOffset - height && scrollTop <= maxOffset + height) { + align = 'auto'; + } else { + align = 'center'; + } + } + + switch (align) { + case 'start': + return maxOffset; + + case 'end': + return minOffset; + + case 'center': + return Math.round(minOffset + (maxOffset - minOffset) / 2); + + case 'auto': + default: + if (scrollTop >= minOffset && scrollTop <= maxOffset) { + return scrollTop; + } else if (scrollTop - minOffset < maxOffset - scrollTop) { + return minOffset; + } else { + return maxOffset; + } + + } + }, + getColumnStartIndexForOffset: function getColumnStartIndexForOffset(_ref9, scrollLeft) { + var columnWidth = _ref9.columnWidth, + columnCount = _ref9.columnCount; + return Math.max(0, Math.min(columnCount - 1, Math.floor(scrollLeft / columnWidth))); + }, + getColumnStopIndexForStartIndex: function getColumnStopIndexForStartIndex(_ref10, startIndex, scrollLeft) { + var columnWidth = _ref10.columnWidth, + columnCount = _ref10.columnCount, + width = _ref10.width; + var left = startIndex * columnWidth; + return Math.max(0, Math.min(columnCount - 1, startIndex + Math.floor((width + (scrollLeft - left)) / columnWidth))); + }, + getRowStartIndexForOffset: function getRowStartIndexForOffset(_ref11, scrollTop) { + var rowHeight = _ref11.rowHeight, + rowCount = _ref11.rowCount; + return Math.max(0, Math.min(rowCount - 1, Math.floor(scrollTop / rowHeight))); + }, + getRowStopIndexForStartIndex: function getRowStopIndexForStartIndex(_ref12, startIndex, scrollTop) { + var rowHeight = _ref12.rowHeight, + rowCount = _ref12.rowCount, + height = _ref12.height; + var left = startIndex * rowHeight; + return Math.max(0, Math.min(rowCount - 1, startIndex + Math.floor((height + (scrollTop - left)) / rowHeight))); + }, + initInstanceProps: function initInstanceProps(props) {// Noop + }, + shouldResetStyleCacheOnItemSizeChange: true, + validateProps: function validateProps(_ref13) { + var columnWidth = _ref13.columnWidth, + rowHeight = _ref13.rowHeight; + + if (process.env.NODE_ENV !== 'production') { + if (typeof columnWidth !== 'number') { + throw Error('An invalid "columnWidth" prop has been specified. ' + 'Value should be a number. ' + ("\"" + (columnWidth === null ? 'null' : typeof columnWidth) + "\" was specified.")); + } + + if (typeof rowHeight !== 'number') { + throw Error('An invalid "rowHeight" prop has been specified. ' + 'Value should be a number. ' + ("\"" + (rowHeight === null ? 'null' : typeof rowHeight) + "\" was specified.")); + } + } + } +}); + +var FixedSizeList = +/*#__PURE__*/ +createListComponent({ + getItemOffset: function getItemOffset(_ref, index) { + var itemSize = _ref.itemSize, + size = _ref.size; + return index * itemSize; + }, + getItemSize: function getItemSize(_ref2, index) { + var itemSize = _ref2.itemSize, + size = _ref2.size; + return itemSize; + }, + getEstimatedTotalSize: function getEstimatedTotalSize(_ref3) { + var itemCount = _ref3.itemCount, + itemSize = _ref3.itemSize; + return itemSize * itemCount; + }, + getOffsetForIndexAndAlignment: function getOffsetForIndexAndAlignment(_ref4, index, align, scrollOffset) { + var direction = _ref4.direction, + height = _ref4.height, + itemCount = _ref4.itemCount, + itemSize = _ref4.itemSize, + layout = _ref4.layout, + width = _ref4.width; + // TODO Deprecate direction "horizontal" + var isHorizontal = direction === 'horizontal' || layout === 'horizontal'; + var size = isHorizontal ? width : height; + var maxOffset = Math.max(0, Math.min(itemCount * itemSize - size, index * itemSize)); + var minOffset = Math.max(0, index * itemSize - size + itemSize); + + if (align === 'smart') { + if (scrollOffset >= minOffset - size && scrollOffset <= maxOffset + size) { + align = 'auto'; + } else { + align = 'center'; + } + } + + switch (align) { + case 'start': + return maxOffset; + + case 'end': + return minOffset; + + case 'center': + return Math.round(minOffset + (maxOffset - minOffset) / 2); + + case 'auto': + default: + if (scrollOffset >= minOffset && scrollOffset <= maxOffset) { + return scrollOffset; + } else if (scrollOffset - minOffset < maxOffset - scrollOffset) { + return minOffset; + } else { + return maxOffset; + } + + } + }, + getStartIndexForOffset: function getStartIndexForOffset(_ref5, offset) { + var itemCount = _ref5.itemCount, + itemSize = _ref5.itemSize; + return Math.max(0, Math.min(itemCount - 1, Math.floor(offset / itemSize))); + }, + getStopIndexForStartIndex: function getStopIndexForStartIndex(_ref6, startIndex, scrollOffset) { + var direction = _ref6.direction, + height = _ref6.height, + itemCount = _ref6.itemCount, + itemSize = _ref6.itemSize, + layout = _ref6.layout, + width = _ref6.width; + // TODO Deprecate direction "horizontal" + var isHorizontal = direction === 'horizontal' || layout === 'horizontal'; + var offset = startIndex * itemSize; + var size = isHorizontal ? width : height; + return Math.max(0, Math.min(itemCount - 1, startIndex + Math.floor((size + (scrollOffset - offset)) / itemSize))); + }, + initInstanceProps: function initInstanceProps(props) {// Noop + }, + shouldResetStyleCacheOnItemSizeChange: true, + validateProps: function validateProps(_ref7) { + var itemSize = _ref7.itemSize; + + if (process.env.NODE_ENV !== 'production') { + if (typeof itemSize !== 'number') { + throw Error('An invalid "itemSize" prop has been specified. ' + 'Value should be a number. ' + ("\"" + (itemSize === null ? 'null' : typeof itemSize) + "\" was specified.")); + } + } + } +}); + +// Pulled from react-compat +// https://github.com/developit/preact-compat/blob/7c5de00e7c85e2ffd011bf3af02899b63f699d3a/src/index.js#L349 +function shallowDiffers(prev, next) { + for (var attribute in prev) { + if (!(attribute in next)) { + return true; + } + } + + for (var _attribute in next) { + if (prev[_attribute] !== next[_attribute]) { + return true; + } + } + + return false; +} + +// It knows to compare individual style props and ignore the wrapper object. +// See https://reactjs.org/docs/react-api.html#reactmemo + +function areEqual(prevProps, nextProps) { + var prevStyle = prevProps.style, + prevRest = _objectWithoutPropertiesLoose(prevProps, ["style"]); + + var nextStyle = nextProps.style, + nextRest = _objectWithoutPropertiesLoose(nextProps, ["style"]); + + return !shallowDiffers(prevStyle, nextStyle) && !shallowDiffers(prevRest, nextRest); +} + +// It knows to compare individual style props and ignore the wrapper object. +// See https://reactjs.org/docs/react-component.html#shouldcomponentupdate + +function shouldComponentUpdate(nextProps, nextState) { + return !areEqual(this.props, nextProps) || shallowDiffers(this.state, nextState); +} + +export { VariableSizeGrid, VariableSizeList, FixedSizeGrid, FixedSizeList, areEqual, shouldComponentUpdate }; diff --git a/vendor/react-window/dist/index.esm.js.flow b/vendor/react-window/dist/index.esm.js.flow new file mode 100644 index 0000000000000..6a6528bbe5384 --- /dev/null +++ b/vendor/react-window/dist/index.esm.js.flow @@ -0,0 +1,3 @@ +// @flow + +export * from '../src'; diff --git a/vendor/react-window/package.json b/vendor/react-window/package.json new file mode 100644 index 0000000000000..adaea80fdb37d --- /dev/null +++ b/vendor/react-window/package.json @@ -0,0 +1,108 @@ +{ + "name": "react-window", + "version": "1.8.0", + "description": + "React components for efficiently rendering large, scrollable lists and tabular data", + "author": + "Brian Vaughn (https://github.com/bvaughn/)", + "contributors": [ + "Brian Vaughn (https://github.com/bvaughn/)" + ], + "license": "MIT", + "homepage": "http://react-window.now.sh/", + "repository": { + "type": "git", + "url": "https://github.com/bvaughn/react-window.git" + }, + "bugs": { + "url": "https://github.com/bvaughn/react-window/issues" + }, + "engines": { + "node": ">8.0.0" + }, + "keywords": [ + "react", + "reactjs", + "virtual", + "window", + "windowed", + "list", + "scrolling", + "infinite", + "virtualized", + "table", + "grid", + "spreadsheet" + ], + "main": "dist/index.cjs.js", + "module": "dist/index.esm.js", + "files": ["dist", "src/*.js"], + "scripts": { + "flow": "flow check --max-warnings=0 src && flow check website", + "precommit": "lint-staged", + "prettier": "prettier --write '**/*.{js,json,css}'", + "linc": "lint-staged", + "lint": "eslint '**/*.js'", + "test": "cross-env CI=1 react-scripts test --env=jsdom", + "test:watch": "react-scripts test --env=jsdom", + "build:flow": + "cp flow-template dist/index.cjs.js.flow && cp flow-template dist/index.esm.js.flow", + "build:source": "rollup -c", + "build": "del dist && mkdir dist && yarn build:flow && yarn build:source", + "start": "rollup -c -w", + "prepare": "yarn run build", + "website:build": "cd website && yarn build", + "website:deploy": "cd website && yarn deploy", + "website:run": "cd website && yarn start" + }, + "lint-staged": { + "{website,src}/**/*.{js,json,css}": ["prettier --write", "git add"], + "**/*.js": "eslint --max-warnings 0" + }, + "dependencies": { + "@babel/runtime": "^7.0.0", + "memoize-one": ">=3.1.1 <6" + }, + "peerDependencies": { + "react": "^15.0.0 || ^16.0.0", + "react-dom": "^15.0.0 || ^16.0.0" + }, + "devDependencies": { + "@babel/core": "^7.0.0", + "@babel/plugin-proposal-class-properties": "^7.0.0", + "@babel/plugin-transform-runtime": "^7.0.0", + "@babel/preset-env": "^7.0.0", + "@babel/preset-flow": "^7.0.0", + "babel-core": "^7.0.0-bridge.0", + "babel-eslint": "^9.0.0", + "babel-plugin-annotate-pure-calls": "^0.3.0", + "cross-env": "^5.1.4", + "del-cli": "^1.1.0", + "eslint": "^4.19.1", + "eslint-config-prettier": "^2.9.0", + "eslint-config-react-app": "^2.1.0", + "eslint-config-standard": "^11.0.0", + "eslint-config-standard-react": "^6.0.0", + "eslint-plugin-flowtype": "^2.47.1", + "eslint-plugin-import": "^2.11.0", + "eslint-plugin-jsx-a11y": "^5", + "eslint-plugin-node": "^6.0.1", + "eslint-plugin-prettier": "^2.6.0", + "eslint-plugin-promise": "^3.7.0", + "eslint-plugin-react": "^7.7.0", + "eslint-plugin-standard": "^3.0.1", + "flow-bin": "^0.80.0", + "gh-pages": "^1.1.0", + "lint-staged": "^7.0.5", + "prettier": "^1.12.1", + "react": "^16.8.4", + "react-dom": "^16.8.4", + "react-is": "^16.8.4", + "react-scripts": "^1.1.1", + "react-test-renderer": "^16.7.0", + "rollup": "^1.4.1", + "rollup-plugin-babel": "^4.3.2", + "rollup-plugin-commonjs": "^9.2.1", + "rollup-plugin-node-resolve": "^4.0.1" + } +} diff --git a/vendor/react-window/src/FixedSizeGrid.js b/vendor/react-window/src/FixedSizeGrid.js new file mode 100644 index 0000000000000..452536a9df4b3 --- /dev/null +++ b/vendor/react-window/src/FixedSizeGrid.js @@ -0,0 +1,210 @@ +// @flow + +import createGridComponent from './createGridComponent'; + +import type { Props, ScrollToAlign } from './createGridComponent'; + +const FixedSizeGrid = createGridComponent({ + getColumnOffset: ({ columnWidth }: Props, index: number): number => + index * ((columnWidth: any): number), + + getColumnWidth: ({ columnWidth }: Props, index: number): number => + ((columnWidth: any): number), + + getRowOffset: ({ rowHeight }: Props, index: number): number => + index * ((rowHeight: any): number), + + getRowHeight: ({ rowHeight }: Props, index: number): number => + ((rowHeight: any): number), + + getEstimatedTotalHeight: ({ rowCount, rowHeight }: Props) => + ((rowHeight: any): number) * rowCount, + + getEstimatedTotalWidth: ({ columnCount, columnWidth }: Props) => + ((columnWidth: any): number) * columnCount, + + getOffsetForColumnAndAlignment: ( + { columnCount, columnWidth, width }: Props, + columnIndex: number, + align: ScrollToAlign, + scrollLeft: number, + instanceProps: typeof undefined, + scrollbarSize: number + ): number => { + const maxOffset = Math.max( + 0, + Math.min( + columnCount * ((columnWidth: any): number) - width, + columnIndex * ((columnWidth: any): number) + ) + ); + const minOffset = Math.max( + 0, + columnIndex * ((columnWidth: any): number) - + width + + scrollbarSize + + ((columnWidth: any): number) + ); + + if (align === 'smart') { + if (scrollLeft >= minOffset - width && scrollLeft <= maxOffset + width) { + align = 'auto'; + } else { + align = 'center'; + } + } + + switch (align) { + case 'start': + return maxOffset; + case 'end': + return minOffset; + case 'center': + return Math.round(minOffset + (maxOffset - minOffset) / 2); + case 'auto': + default: + if (scrollLeft >= minOffset && scrollLeft <= maxOffset) { + return scrollLeft; + } else if (scrollLeft - minOffset < maxOffset - scrollLeft) { + return minOffset; + } else { + return maxOffset; + } + } + }, + + getOffsetForRowAndAlignment: ( + { rowHeight, height, rowCount }: Props, + rowIndex: number, + align: ScrollToAlign, + scrollTop: number, + instanceProps: typeof undefined, + scrollbarSize: number + ): number => { + const maxOffset = Math.max( + 0, + Math.min( + rowCount * ((rowHeight: any): number) - height, + rowIndex * ((rowHeight: any): number) + ) + ); + const minOffset = Math.max( + 0, + rowIndex * ((rowHeight: any): number) - + height + + scrollbarSize + + ((rowHeight: any): number) + ); + + if (align === 'smart') { + if (scrollTop >= minOffset - height && scrollTop <= maxOffset + height) { + align = 'auto'; + } else { + align = 'center'; + } + } + + switch (align) { + case 'start': + return maxOffset; + case 'end': + return minOffset; + case 'center': + return Math.round(minOffset + (maxOffset - minOffset) / 2); + case 'auto': + default: + if (scrollTop >= minOffset && scrollTop <= maxOffset) { + return scrollTop; + } else if (scrollTop - minOffset < maxOffset - scrollTop) { + return minOffset; + } else { + return maxOffset; + } + } + }, + + getColumnStartIndexForOffset: ( + { columnWidth, columnCount }: Props, + scrollLeft: number + ): number => + Math.max( + 0, + Math.min( + columnCount - 1, + Math.floor(scrollLeft / ((columnWidth: any): number)) + ) + ), + + getColumnStopIndexForStartIndex: ( + { columnWidth, columnCount, width }: Props, + startIndex: number, + scrollLeft: number + ): number => { + const left = startIndex * ((columnWidth: any): number); + return Math.max( + 0, + Math.min( + columnCount - 1, + startIndex + + Math.floor( + (width + (scrollLeft - left)) / ((columnWidth: any): number) + ) + ) + ); + }, + + getRowStartIndexForOffset: ( + { rowHeight, rowCount }: Props, + scrollTop: number + ): number => + Math.max( + 0, + Math.min(rowCount - 1, Math.floor(scrollTop / ((rowHeight: any): number))) + ), + + getRowStopIndexForStartIndex: ( + { rowHeight, rowCount, height }: Props, + startIndex: number, + scrollTop: number + ): number => { + const left = startIndex * ((rowHeight: any): number); + return Math.max( + 0, + Math.min( + rowCount - 1, + startIndex + + Math.floor((height + (scrollTop - left)) / ((rowHeight: any): number)) + ) + ); + }, + + initInstanceProps(props: Props): any { + // Noop + }, + + shouldResetStyleCacheOnItemSizeChange: true, + + validateProps: ({ columnWidth, rowHeight }: Props): void => { + if (process.env.NODE_ENV !== 'production') { + if (typeof columnWidth !== 'number') { + throw Error( + 'An invalid "columnWidth" prop has been specified. ' + + 'Value should be a number. ' + + `"${ + columnWidth === null ? 'null' : typeof columnWidth + }" was specified.` + ); + } + + if (typeof rowHeight !== 'number') { + throw Error( + 'An invalid "rowHeight" prop has been specified. ' + + 'Value should be a number. ' + + `"${rowHeight === null ? 'null' : typeof rowHeight}" was specified.` + ); + } + } + }, +}); + +export default FixedSizeGrid; diff --git a/vendor/react-window/src/FixedSizeList.js b/vendor/react-window/src/FixedSizeList.js new file mode 100644 index 0000000000000..991f9004305bc --- /dev/null +++ b/vendor/react-window/src/FixedSizeList.js @@ -0,0 +1,117 @@ +// @flow + +import createListComponent from './createListComponent'; + +import type { Props, ScrollToAlign } from './createListComponent'; + +const FixedSizeList = createListComponent({ + getItemOffset: ({ itemSize, size }: Props, index: number): number => + index * ((itemSize: any): number), + + getItemSize: ({ itemSize, size }: Props, index: number): number => + ((itemSize: any): number), + + getEstimatedTotalSize: ({ itemCount, itemSize }: Props) => + ((itemSize: any): number) * itemCount, + + getOffsetForIndexAndAlignment: ( + { direction, height, itemCount, itemSize, layout, width }: Props, + index: number, + align: ScrollToAlign, + scrollOffset: number + ): number => { + // TODO Deprecate direction "horizontal" + const isHorizontal = direction === 'horizontal' || layout === 'horizontal'; + const size = (((isHorizontal ? width : height): any): number); + const maxOffset = Math.max( + 0, + Math.min( + itemCount * ((itemSize: any): number) - size, + index * ((itemSize: any): number) + ) + ); + const minOffset = Math.max( + 0, + index * ((itemSize: any): number) - size + ((itemSize: any): number) + ); + + if (align === 'smart') { + if ( + scrollOffset >= minOffset - size && + scrollOffset <= maxOffset + size + ) { + align = 'auto'; + } else { + align = 'center'; + } + } + + switch (align) { + case 'start': + return maxOffset; + case 'end': + return minOffset; + case 'center': + return Math.round(minOffset + (maxOffset - minOffset) / 2); + case 'auto': + default: + if (scrollOffset >= minOffset && scrollOffset <= maxOffset) { + return scrollOffset; + } else if (scrollOffset - minOffset < maxOffset - scrollOffset) { + return minOffset; + } else { + return maxOffset; + } + } + }, + + getStartIndexForOffset: ( + { itemCount, itemSize }: Props, + offset: number + ): number => + Math.max( + 0, + Math.min(itemCount - 1, Math.floor(offset / ((itemSize: any): number))) + ), + + getStopIndexForStartIndex: ( + { direction, height, itemCount, itemSize, layout, width }: Props, + startIndex: number, + scrollOffset: number + ): number => { + // TODO Deprecate direction "horizontal" + const isHorizontal = direction === 'horizontal' || layout === 'horizontal'; + const offset = startIndex * ((itemSize: any): number); + const size = (((isHorizontal ? width : height): any): number); + return Math.max( + 0, + Math.min( + itemCount - 1, + startIndex + + Math.floor( + (size + (scrollOffset - offset)) / ((itemSize: any): number) + ) + ) + ); + }, + + initInstanceProps(props: Props): any { + // Noop + }, + + shouldResetStyleCacheOnItemSizeChange: true, + + validateProps: ({ itemSize }: Props): void => { + if (process.env.NODE_ENV !== 'production') { + if (typeof itemSize !== 'number') { + throw Error( + 'An invalid "itemSize" prop has been specified. ' + + 'Value should be a number. ' + + `"${itemSize === null ? 'null' : typeof itemSize}" was specified.` + ); + } + } + }, +}); + +export default FixedSizeList; diff --git a/vendor/react-window/src/VariableSizeGrid.js b/vendor/react-window/src/VariableSizeGrid.js new file mode 100644 index 0000000000000..a1273e198e0d4 --- /dev/null +++ b/vendor/react-window/src/VariableSizeGrid.js @@ -0,0 +1,503 @@ +// @flow + +import createGridComponent from './createGridComponent'; + +import type { Props, ScrollToAlign } from './createGridComponent'; + +const DEFAULT_ESTIMATED_ITEM_SIZE = 50; + +type VariableSizeProps = {| + estimatedColumnWidth: number, + estimatedRowHeight: number, + ...Props, +|}; + +type itemSizeGetter = (index: number) => number; +type ItemType = 'column' | 'row'; + +type ItemMetadata = {| + offset: number, + size: number, +|}; +type ItemMetadataMap = { [index: number]: ItemMetadata }; +type InstanceProps = {| + columnMetadataMap: ItemMetadataMap, + estimatedColumnWidth: number, + estimatedRowHeight: number, + lastMeasuredColumnIndex: number, + lastMeasuredRowIndex: number, + rowMetadataMap: ItemMetadataMap, +|}; + +const getEstimatedTotalHeight = ( + { rowCount }: Props, + { rowMetadataMap, estimatedRowHeight, lastMeasuredRowIndex }: InstanceProps +) => { + let totalSizeOfMeasuredRows = 0; + + // Edge case check for when the number of items decreases while a scroll is in progress. + // https://github.com/bvaughn/react-window/pull/138 + if (lastMeasuredRowIndex >= rowCount) { + lastMeasuredRowIndex = rowCount - 1; + } + + if (lastMeasuredRowIndex >= 0) { + const itemMetadata = rowMetadataMap[lastMeasuredRowIndex]; + totalSizeOfMeasuredRows = itemMetadata.offset + itemMetadata.size; + } + + const numUnmeasuredItems = rowCount - lastMeasuredRowIndex - 1; + const totalSizeOfUnmeasuredItems = numUnmeasuredItems * estimatedRowHeight; + + return totalSizeOfMeasuredRows + totalSizeOfUnmeasuredItems; +}; + +const getEstimatedTotalWidth = ( + { columnCount }: Props, + { + columnMetadataMap, + estimatedColumnWidth, + lastMeasuredColumnIndex, + }: InstanceProps +) => { + let totalSizeOfMeasuredRows = 0; + + // Edge case check for when the number of items decreases while a scroll is in progress. + // https://github.com/bvaughn/react-window/pull/138 + if (lastMeasuredColumnIndex >= columnCount) { + lastMeasuredColumnIndex = columnCount - 1; + } + + if (lastMeasuredColumnIndex >= 0) { + const itemMetadata = columnMetadataMap[lastMeasuredColumnIndex]; + totalSizeOfMeasuredRows = itemMetadata.offset + itemMetadata.size; + } + + const numUnmeasuredItems = columnCount - lastMeasuredColumnIndex - 1; + const totalSizeOfUnmeasuredItems = numUnmeasuredItems * estimatedColumnWidth; + + return totalSizeOfMeasuredRows + totalSizeOfUnmeasuredItems; +}; + +const getItemMetadata = ( + itemType: ItemType, + props: Props, + index: number, + instanceProps: InstanceProps +): ItemMetadata => { + let itemMetadataMap, itemSize, lastMeasuredIndex; + if (itemType === 'column') { + itemMetadataMap = instanceProps.columnMetadataMap; + itemSize = ((props.columnWidth: any): itemSizeGetter); + lastMeasuredIndex = instanceProps.lastMeasuredColumnIndex; + } else { + itemMetadataMap = instanceProps.rowMetadataMap; + itemSize = ((props.rowHeight: any): itemSizeGetter); + lastMeasuredIndex = instanceProps.lastMeasuredRowIndex; + } + + if (index > lastMeasuredIndex) { + let offset = 0; + if (lastMeasuredIndex >= 0) { + const itemMetadata = itemMetadataMap[lastMeasuredIndex]; + offset = itemMetadata.offset + itemMetadata.size; + } + + for (let i = lastMeasuredIndex + 1; i <= index; i++) { + let size = itemSize(i); + + itemMetadataMap[i] = { + offset, + size, + }; + + offset += size; + } + + if (itemType === 'column') { + instanceProps.lastMeasuredColumnIndex = index; + } else { + instanceProps.lastMeasuredRowIndex = index; + } + } + + return itemMetadataMap[index]; +}; + +const findNearestItem = ( + itemType: ItemType, + props: Props, + instanceProps: InstanceProps, + offset: number +) => { + let itemMetadataMap, lastMeasuredIndex; + if (itemType === 'column') { + itemMetadataMap = instanceProps.columnMetadataMap; + lastMeasuredIndex = instanceProps.lastMeasuredColumnIndex; + } else { + itemMetadataMap = instanceProps.rowMetadataMap; + lastMeasuredIndex = instanceProps.lastMeasuredRowIndex; + } + + const lastMeasuredItemOffset = + lastMeasuredIndex > 0 ? itemMetadataMap[lastMeasuredIndex].offset : 0; + + if (lastMeasuredItemOffset >= offset) { + // If we've already measured items within this range just use a binary search as it's faster. + return findNearestItemBinarySearch( + itemType, + props, + instanceProps, + lastMeasuredIndex, + 0, + offset + ); + } else { + // If we haven't yet measured this high, fallback to an exponential search with an inner binary search. + // The exponential search avoids pre-computing sizes for the full set of items as a binary search would. + // The overall complexity for this approach is O(log n). + return findNearestItemExponentialSearch( + itemType, + props, + instanceProps, + Math.max(0, lastMeasuredIndex), + offset + ); + } +}; + +const findNearestItemBinarySearch = ( + itemType: ItemType, + props: Props, + instanceProps: InstanceProps, + high: number, + low: number, + offset: number +): number => { + while (low <= high) { + const middle = low + Math.floor((high - low) / 2); + const currentOffset = getItemMetadata( + itemType, + props, + middle, + instanceProps + ).offset; + + if (currentOffset === offset) { + return middle; + } else if (currentOffset < offset) { + low = middle + 1; + } else if (currentOffset > offset) { + high = middle - 1; + } + } + + if (low > 0) { + return low - 1; + } else { + return 0; + } +}; + +const findNearestItemExponentialSearch = ( + itemType: ItemType, + props: Props, + instanceProps: InstanceProps, + index: number, + offset: number +): number => { + const itemCount = itemType === 'column' ? props.columnCount : props.rowCount; + let interval = 1; + + while ( + index < itemCount && + getItemMetadata(itemType, props, index, instanceProps).offset < offset + ) { + index += interval; + interval *= 2; + } + + return findNearestItemBinarySearch( + itemType, + props, + instanceProps, + Math.min(index, itemCount - 1), + Math.floor(index / 2), + offset + ); +}; + +const getOffsetForIndexAndAlignment = ( + itemType: ItemType, + props: Props, + index: number, + align: ScrollToAlign, + scrollOffset: number, + instanceProps: InstanceProps, + scrollbarSize: number +): number => { + const size = itemType === 'column' ? props.width : props.height; + const itemMetadata = getItemMetadata(itemType, props, index, instanceProps); + + // Get estimated total size after ItemMetadata is computed, + // To ensure it reflects actual measurements instead of just estimates. + const estimatedTotalSize = + itemType === 'column' + ? getEstimatedTotalWidth(props, instanceProps) + : getEstimatedTotalHeight(props, instanceProps); + + const maxOffset = Math.max( + 0, + Math.min(estimatedTotalSize - size, itemMetadata.offset) + ); + const minOffset = Math.max( + 0, + itemMetadata.offset - size + scrollbarSize + itemMetadata.size + ); + + if (align === 'smart') { + if (scrollOffset >= minOffset - size && scrollOffset <= maxOffset + size) { + align = 'auto'; + } else { + align = 'center'; + } + } + + switch (align) { + case 'start': + return maxOffset; + case 'end': + return minOffset; + case 'center': + return Math.round(minOffset + (maxOffset - minOffset) / 2); + case 'auto': + default: + if (scrollOffset >= minOffset && scrollOffset <= maxOffset) { + return scrollOffset; + } else if (scrollOffset - minOffset < maxOffset - scrollOffset) { + return minOffset; + } else { + return maxOffset; + } + } +}; + +const VariableSizeGrid = createGridComponent({ + getColumnOffset: ( + props: Props, + index: number, + instanceProps: InstanceProps + ): number => getItemMetadata('column', props, index, instanceProps).offset, + + getColumnStartIndexForOffset: ( + props: Props, + scrollLeft: number, + instanceProps: InstanceProps + ): number => findNearestItem('column', props, instanceProps, scrollLeft), + + getColumnStopIndexForStartIndex: ( + props: Props, + startIndex: number, + scrollLeft: number, + instanceProps: InstanceProps + ): number => { + const { columnCount, width } = props; + + const itemMetadata = getItemMetadata( + 'column', + props, + startIndex, + instanceProps + ); + const maxOffset = scrollLeft + width; + + let offset = itemMetadata.offset + itemMetadata.size; + let stopIndex = startIndex; + + while (stopIndex < columnCount - 1 && offset < maxOffset) { + stopIndex++; + offset += getItemMetadata('column', props, stopIndex, instanceProps).size; + } + + return stopIndex; + }, + + getColumnWidth: ( + props: Props, + index: number, + instanceProps: InstanceProps + ): number => instanceProps.columnMetadataMap[index].size, + + getEstimatedTotalHeight, + getEstimatedTotalWidth, + + getOffsetForColumnAndAlignment: ( + props: Props, + index: number, + align: ScrollToAlign, + scrollOffset: number, + instanceProps: InstanceProps, + scrollbarSize: number + ): number => + getOffsetForIndexAndAlignment( + 'column', + props, + index, + align, + scrollOffset, + instanceProps, + scrollbarSize + ), + + getOffsetForRowAndAlignment: ( + props: Props, + index: number, + align: ScrollToAlign, + scrollOffset: number, + instanceProps: InstanceProps, + scrollbarSize: number + ): number => + getOffsetForIndexAndAlignment( + 'row', + props, + index, + align, + scrollOffset, + instanceProps, + scrollbarSize + ), + + getRowOffset: ( + props: Props, + index: number, + instanceProps: InstanceProps + ): number => getItemMetadata('row', props, index, instanceProps).offset, + + getRowHeight: ( + props: Props, + index: number, + instanceProps: InstanceProps + ): number => instanceProps.rowMetadataMap[index].size, + + getRowStartIndexForOffset: ( + props: Props, + scrollTop: number, + instanceProps: InstanceProps + ): number => findNearestItem('row', props, instanceProps, scrollTop), + + getRowStopIndexForStartIndex: ( + props: Props, + startIndex: number, + scrollTop: number, + instanceProps: InstanceProps + ): number => { + const { rowCount, height } = props; + + const itemMetadata = getItemMetadata( + 'row', + props, + startIndex, + instanceProps + ); + const maxOffset = scrollTop + height; + + let offset = itemMetadata.offset + itemMetadata.size; + let stopIndex = startIndex; + + while (stopIndex < rowCount - 1 && offset < maxOffset) { + stopIndex++; + offset += getItemMetadata('row', props, stopIndex, instanceProps).size; + } + + return stopIndex; + }, + + initInstanceProps(props: Props, instance: any): InstanceProps { + const { + estimatedColumnWidth, + estimatedRowHeight, + } = ((props: any): VariableSizeProps); + + const instanceProps = { + columnMetadataMap: {}, + estimatedColumnWidth: estimatedColumnWidth || DEFAULT_ESTIMATED_ITEM_SIZE, + estimatedRowHeight: estimatedRowHeight || DEFAULT_ESTIMATED_ITEM_SIZE, + lastMeasuredColumnIndex: -1, + lastMeasuredRowIndex: -1, + rowMetadataMap: {}, + }; + + instance.resetAfterColumnIndex = ( + columnIndex: number, + shouldForceUpdate?: boolean = true + ) => { + instance.resetAfterIndices({ columnIndex, shouldForceUpdate }); + }; + + instance.resetAfterRowIndex = ( + rowIndex: number, + shouldForceUpdate?: boolean = true + ) => { + instance.resetAfterIndices({ rowIndex, shouldForceUpdate }); + }; + + instance.resetAfterIndices = ({ + columnIndex, + rowIndex, + shouldForceUpdate = true, + }: { + columnIndex?: number, + rowIndex?: number, + shouldForceUpdate: boolean, + }) => { + if (typeof columnIndex === 'number') { + instanceProps.lastMeasuredColumnIndex = Math.min( + instanceProps.lastMeasuredColumnIndex, + columnIndex - 1 + ); + } + if (typeof rowIndex === 'number') { + instanceProps.lastMeasuredRowIndex = Math.min( + instanceProps.lastMeasuredRowIndex, + rowIndex - 1 + ); + } + + // We could potentially optimize further by only evicting styles after this index, + // But since styles are only cached while scrolling is in progress- + // It seems an unnecessary optimization. + // It's unlikely that resetAfterIndex() will be called while a user is scrolling. + instance._getItemStyleCache(-1); + + if (shouldForceUpdate) { + instance.forceUpdate(); + } + }; + + return instanceProps; + }, + + shouldResetStyleCacheOnItemSizeChange: false, + + validateProps: ({ columnWidth, rowHeight }: Props): void => { + if (process.env.NODE_ENV !== 'production') { + if (typeof columnWidth !== 'function') { + throw Error( + 'An invalid "columnWidth" prop has been specified. ' + + 'Value should be a function. ' + + `"${ + columnWidth === null ? 'null' : typeof columnWidth + }" was specified.` + ); + } else if (typeof rowHeight !== 'function') { + throw Error( + 'An invalid "rowHeight" prop has been specified. ' + + 'Value should be a function. ' + + `"${rowHeight === null ? 'null' : typeof rowHeight}" was specified.` + ); + } + } + }, +}); + +export default VariableSizeGrid; diff --git a/vendor/react-window/src/VariableSizeList.js b/vendor/react-window/src/VariableSizeList.js new file mode 100644 index 0000000000000..cebadc663eb22 --- /dev/null +++ b/vendor/react-window/src/VariableSizeList.js @@ -0,0 +1,316 @@ +// @flow + +import createListComponent from './createListComponent'; + +import type { Props, ScrollToAlign } from './createListComponent'; + +const DEFAULT_ESTIMATED_ITEM_SIZE = 50; + +type VariableSizeProps = {| + estimatedItemSize: number, + ...Props, +|}; + +type itemSizeGetter = (index: number) => number; + +type ItemMetadata = {| + offset: number, + size: number, +|}; +type InstanceProps = {| + itemMetadataMap: { [index: number]: ItemMetadata }, + estimatedItemSize: number, + lastMeasuredIndex: number, +|}; + +const getItemMetadata = ( + props: Props, + index: number, + instanceProps: InstanceProps +): ItemMetadata => { + const { itemSize } = ((props: any): VariableSizeProps); + const { itemMetadataMap, lastMeasuredIndex } = instanceProps; + + if (index > lastMeasuredIndex) { + let offset = 0; + if (lastMeasuredIndex >= 0) { + const itemMetadata = itemMetadataMap[lastMeasuredIndex]; + offset = itemMetadata.offset + itemMetadata.size; + } + + for (let i = lastMeasuredIndex + 1; i <= index; i++) { + let size = ((itemSize: any): itemSizeGetter)(i); + + itemMetadataMap[i] = { + offset, + size, + }; + + offset += size; + } + + instanceProps.lastMeasuredIndex = index; + } + + return itemMetadataMap[index]; +}; + +const findNearestItem = ( + props: Props, + instanceProps: InstanceProps, + offset: number +) => { + const { itemMetadataMap, lastMeasuredIndex } = instanceProps; + + const lastMeasuredItemOffset = + lastMeasuredIndex > 0 ? itemMetadataMap[lastMeasuredIndex].offset : 0; + + if (lastMeasuredItemOffset >= offset) { + // If we've already measured items within this range just use a binary search as it's faster. + return findNearestItemBinarySearch( + props, + instanceProps, + lastMeasuredIndex, + 0, + offset + ); + } else { + // If we haven't yet measured this high, fallback to an exponential search with an inner binary search. + // The exponential search avoids pre-computing sizes for the full set of items as a binary search would. + // The overall complexity for this approach is O(log n). + return findNearestItemExponentialSearch( + props, + instanceProps, + Math.max(0, lastMeasuredIndex), + offset + ); + } +}; + +const findNearestItemBinarySearch = ( + props: Props, + instanceProps: InstanceProps, + high: number, + low: number, + offset: number +): number => { + while (low <= high) { + const middle = low + Math.floor((high - low) / 2); + const currentOffset = getItemMetadata(props, middle, instanceProps).offset; + + if (currentOffset === offset) { + return middle; + } else if (currentOffset < offset) { + low = middle + 1; + } else if (currentOffset > offset) { + high = middle - 1; + } + } + + if (low > 0) { + return low - 1; + } else { + return 0; + } +}; + +const findNearestItemExponentialSearch = ( + props: Props, + instanceProps: InstanceProps, + index: number, + offset: number +): number => { + const { itemCount } = props; + let interval = 1; + + while ( + index < itemCount && + getItemMetadata(props, index, instanceProps).offset < offset + ) { + index += interval; + interval *= 2; + } + + return findNearestItemBinarySearch( + props, + instanceProps, + Math.min(index, itemCount - 1), + Math.floor(index / 2), + offset + ); +}; + +const getEstimatedTotalSize = ( + { itemCount }: Props, + { itemMetadataMap, estimatedItemSize, lastMeasuredIndex }: InstanceProps +) => { + let totalSizeOfMeasuredItems = 0; + + // Edge case check for when the number of items decreases while a scroll is in progress. + // https://github.com/bvaughn/react-window/pull/138 + if (lastMeasuredIndex >= itemCount) { + lastMeasuredIndex = itemCount - 1; + } + + if (lastMeasuredIndex >= 0) { + const itemMetadata = itemMetadataMap[lastMeasuredIndex]; + totalSizeOfMeasuredItems = itemMetadata.offset + itemMetadata.size; + } + + const numUnmeasuredItems = itemCount - lastMeasuredIndex - 1; + const totalSizeOfUnmeasuredItems = numUnmeasuredItems * estimatedItemSize; + + return totalSizeOfMeasuredItems + totalSizeOfUnmeasuredItems; +}; + +const VariableSizeList = createListComponent({ + getItemOffset: ( + props: Props, + index: number, + instanceProps: InstanceProps + ): number => getItemMetadata(props, index, instanceProps).offset, + + getItemSize: ( + props: Props, + index: number, + instanceProps: InstanceProps + ): number => instanceProps.itemMetadataMap[index].size, + + getEstimatedTotalSize, + + getOffsetForIndexAndAlignment: ( + props: Props, + index: number, + align: ScrollToAlign, + scrollOffset: number, + instanceProps: InstanceProps + ): number => { + const { direction, height, layout, width } = props; + + // TODO Deprecate direction "horizontal" + const isHorizontal = direction === 'horizontal' || layout === 'horizontal'; + const size = (((isHorizontal ? width : height): any): number); + const itemMetadata = getItemMetadata(props, index, instanceProps); + + // Get estimated total size after ItemMetadata is computed, + // To ensure it reflects actual measurements instead of just estimates. + const estimatedTotalSize = getEstimatedTotalSize(props, instanceProps); + + const maxOffset = Math.max( + 0, + Math.min(estimatedTotalSize - size, itemMetadata.offset) + ); + const minOffset = Math.max( + 0, + itemMetadata.offset - size + itemMetadata.size + ); + + if (align === 'smart') { + if ( + scrollOffset >= minOffset - size && + scrollOffset <= maxOffset + size + ) { + align = 'auto'; + } else { + align = 'center'; + } + } + + switch (align) { + case 'start': + return maxOffset; + case 'end': + return minOffset; + case 'center': + return Math.round(minOffset + (maxOffset - minOffset) / 2); + case 'auto': + default: + if (scrollOffset >= minOffset && scrollOffset <= maxOffset) { + return scrollOffset; + } else if (scrollOffset - minOffset < maxOffset - scrollOffset) { + return minOffset; + } else { + return maxOffset; + } + } + }, + + getStartIndexForOffset: ( + props: Props, + offset: number, + instanceProps: InstanceProps + ): number => findNearestItem(props, instanceProps, offset), + + getStopIndexForStartIndex: ( + props: Props, + startIndex: number, + scrollOffset: number, + instanceProps: InstanceProps + ): number => { + const { direction, height, itemCount, layout, width } = props; + + // TODO Deprecate direction "horizontal" + const isHorizontal = direction === 'horizontal' || layout === 'horizontal'; + const size = (((isHorizontal ? width : height): any): number); + const itemMetadata = getItemMetadata(props, startIndex, instanceProps); + const maxOffset = scrollOffset + size; + + let offset = itemMetadata.offset + itemMetadata.size; + let stopIndex = startIndex; + + while (stopIndex < itemCount - 1 && offset < maxOffset) { + stopIndex++; + offset += getItemMetadata(props, stopIndex, instanceProps).size; + } + + return stopIndex; + }, + + initInstanceProps(props: Props, instance: any): InstanceProps { + const { estimatedItemSize } = ((props: any): VariableSizeProps); + + const instanceProps = { + itemMetadataMap: {}, + estimatedItemSize: estimatedItemSize || DEFAULT_ESTIMATED_ITEM_SIZE, + lastMeasuredIndex: -1, + }; + + instance.resetAfterIndex = ( + index: number, + shouldForceUpdate?: boolean = true + ) => { + instanceProps.lastMeasuredIndex = Math.min( + instanceProps.lastMeasuredIndex, + index - 1 + ); + + // We could potentially optimize further by only evicting styles after this index, + // But since styles are only cached while scrolling is in progress- + // It seems an unnecessary optimization. + // It's unlikely that resetAfterIndex() will be called while a user is scrolling. + instance._getItemStyleCache(-1); + + if (shouldForceUpdate) { + instance.forceUpdate(); + } + }; + + return instanceProps; + }, + + shouldResetStyleCacheOnItemSizeChange: false, + + validateProps: ({ itemSize }: Props): void => { + if (process.env.NODE_ENV !== 'production') { + if (typeof itemSize !== 'function') { + throw Error( + 'An invalid "itemSize" prop has been specified. ' + + 'Value should be a function. ' + + `"${itemSize === null ? 'null' : typeof itemSize}" was specified.` + ); + } + } + }, +}); + +export default VariableSizeList; diff --git a/vendor/react-window/src/areEqual.js b/vendor/react-window/src/areEqual.js new file mode 100644 index 0000000000000..072951ef67753 --- /dev/null +++ b/vendor/react-window/src/areEqual.js @@ -0,0 +1,18 @@ +// @flow + +import shallowDiffers from './shallowDiffers'; + +// Custom comparison function for React.memo(). +// It knows to compare individual style props and ignore the wrapper object. +// See https://reactjs.org/docs/react-api.html#reactmemo +export default function areEqual( + prevProps: Object, + nextProps: Object +): boolean { + const { style: prevStyle, ...prevRest } = prevProps; + const { style: nextStyle, ...nextRest } = nextProps; + + return ( + !shallowDiffers(prevStyle, nextStyle) && !shallowDiffers(prevRest, nextRest) + ); +} diff --git a/vendor/react-window/src/createGridComponent.js b/vendor/react-window/src/createGridComponent.js new file mode 100644 index 0000000000000..c72af855c7b22 --- /dev/null +++ b/vendor/react-window/src/createGridComponent.js @@ -0,0 +1,834 @@ +// @flow + +import memoizeOne from 'memoize-one'; +import { createElement, PureComponent } from 'react'; +import { cancelTimeout, requestTimeout } from './timer'; +import { getScrollbarSize } from './domHelpers'; + +import type { TimeoutID } from './timer'; + +type Direction = 'ltr' | 'rtl'; +export type ScrollToAlign = 'auto' | 'smart' | 'center' | 'start' | 'end'; + +type itemSize = number | ((index: number) => number); + +type RenderComponentProps = {| + columnIndex: number, + data: T, + isScrolling?: boolean, + rowIndex: number, + style: Object, +|}; +export type RenderComponent = React$ComponentType< + $Shape> +>; + +type ScrollDirection = 'forward' | 'backward'; + +type OnItemsRenderedCallback = ({ + overscanColumnStartIndex: number, + overscanColumnStopIndex: number, + overscanRowStartIndex: number, + overscanRowStopIndex: number, + visibleColumnStartIndex: number, + visibleColumnStopIndex: number, + visibleRowStartIndex: number, + visibleRowStopIndex: number, +}) => void; +type OnScrollCallback = ({ + horizontalScrollDirection: ScrollDirection, + scrollLeft: number, + scrollTop: number, + scrollUpdateWasRequested: boolean, + verticalScrollDirection: ScrollDirection, +}) => void; + +type ScrollEvent = SyntheticEvent; +type ItemStyleCache = { [key: string]: Object }; + +export type Props = {| + children: RenderComponent, + className?: string, + columnCount: number, + columnWidth: itemSize, + direction: Direction, + height: number, + initialScrollLeft?: number, + initialScrollTop?: number, + innerRef?: any, + innerElementType?: React$ElementType, + innerTagName?: string, // deprecated + itemData: T, + itemKey?: (params: {| + columnIndex: number, + data: T, + rowIndex: number, + |}) => any, + onItemsRendered?: OnItemsRenderedCallback, + onScroll?: OnScrollCallback, + outerRef?: any, + outerElementType?: React$ElementType, + outerTagName?: string, // deprecated + overscanColumnsCount?: number, + overscanCount?: number, // deprecated + overscanRowsCount?: number, + rowCount: number, + rowHeight: itemSize, + style?: Object, + useIsScrolling: boolean, + width: number, +|}; + +type State = {| + instance: any, + isScrolling: boolean, + horizontalScrollDirection: ScrollDirection, + scrollLeft: number, + scrollTop: number, + scrollUpdateWasRequested: boolean, + verticalScrollDirection: ScrollDirection, +|}; + +type getItemOffset = ( + props: Props, + index: number, + instanceProps: any +) => number; +type getItemSize = ( + props: Props, + index: number, + instanceProps: any +) => number; +type getEstimatedTotalSize = (props: Props, instanceProps: any) => number; +type GetOffsetForItemAndAlignment = ( + props: Props, + index: number, + align: ScrollToAlign, + scrollOffset: number, + instanceProps: any, + scrollbarSize: number +) => number; +type GetStartIndexForOffset = ( + props: Props, + offset: number, + instanceProps: any +) => number; +type GetStopIndexForStartIndex = ( + props: Props, + startIndex: number, + scrollOffset: number, + instanceProps: any +) => number; +type InitInstanceProps = (props: Props, instance: any) => any; +type ValidateProps = (props: Props) => void; + +const IS_SCROLLING_DEBOUNCE_INTERVAL = 150; + +const defaultItemKey = ({ columnIndex, data, rowIndex }) => + `${rowIndex}:${columnIndex}`; + +// In DEV mode, this Set helps us only log a warning once per component instance. +// This avoids spamming the console every time a render happens. +let devWarningsOverscanCount = null; +let devWarningsTagName = null; +if (process.env.NODE_ENV !== 'production') { + if (typeof window !== 'undefined' && typeof window.WeakSet !== 'undefined') { + devWarningsOverscanCount = new WeakSet(); + devWarningsTagName = new WeakSet(); + } +} + +export default function createGridComponent({ + getColumnOffset, + getColumnStartIndexForOffset, + getColumnStopIndexForStartIndex, + getColumnWidth, + getEstimatedTotalHeight, + getEstimatedTotalWidth, + getOffsetForColumnAndAlignment, + getOffsetForRowAndAlignment, + getRowHeight, + getRowOffset, + getRowStartIndexForOffset, + getRowStopIndexForStartIndex, + initInstanceProps, + shouldResetStyleCacheOnItemSizeChange, + validateProps, +}: {| + getColumnOffset: getItemOffset, + getColumnStartIndexForOffset: GetStartIndexForOffset, + getColumnStopIndexForStartIndex: GetStopIndexForStartIndex, + getColumnWidth: getItemSize, + getEstimatedTotalHeight: getEstimatedTotalSize, + getEstimatedTotalWidth: getEstimatedTotalSize, + getOffsetForColumnAndAlignment: GetOffsetForItemAndAlignment, + getOffsetForRowAndAlignment: GetOffsetForItemAndAlignment, + getRowOffset: getItemOffset, + getRowHeight: getItemSize, + getRowStartIndexForOffset: GetStartIndexForOffset, + getRowStopIndexForStartIndex: GetStopIndexForStartIndex, + initInstanceProps: InitInstanceProps, + shouldResetStyleCacheOnItemSizeChange: boolean, + validateProps: ValidateProps, +|}) { + return class Grid extends PureComponent, State> { + _instanceProps: any = initInstanceProps(this.props, this); + _resetIsScrollingTimeoutId: TimeoutID | null = null; + _outerRef: ?HTMLDivElement; + + static defaultProps = { + direction: 'ltr', + itemData: undefined, + useIsScrolling: false, + }; + + state: State = { + instance: this, + isScrolling: false, + horizontalScrollDirection: 'forward', + scrollLeft: + typeof this.props.initialScrollLeft === 'number' + ? this.props.initialScrollLeft + : 0, + scrollTop: + typeof this.props.initialScrollTop === 'number' + ? this.props.initialScrollTop + : 0, + scrollUpdateWasRequested: false, + verticalScrollDirection: 'forward', + }; + + // Always use explicit constructor for React components. + // It produces less code after transpilation. (#26) + // eslint-disable-next-line no-useless-constructor + constructor(props: Props) { + super(props); + } + + static getDerivedStateFromProps( + nextProps: Props, + prevState: State + ): $Shape | null { + validateSharedProps(nextProps, prevState); + validateProps(nextProps); + return null; + } + + scrollTo({ + scrollLeft, + scrollTop, + }: { + scrollLeft: number, + scrollTop: number, + }): void { + if (scrollLeft !== undefined) { + scrollLeft = Math.max(0, scrollLeft); + } + if (scrollTop !== undefined) { + scrollTop = Math.max(0, scrollTop); + } + + this.setState(prevState => { + if (scrollLeft === undefined) { + scrollLeft = prevState.scrollLeft; + } + if (scrollTop === undefined) { + scrollTop = prevState.scrollTop; + } + + if ( + prevState.scrollLeft === scrollLeft && + prevState.scrollTop === scrollTop + ) { + return null; + } + + return { + horizontalScrollDirection: + prevState.scrollLeft < scrollLeft ? 'forward' : 'backward', + scrollLeft: scrollLeft, + scrollTop: scrollTop, + scrollUpdateWasRequested: true, + verticalScrollDirection: + prevState.scrollTop < scrollTop ? 'forward' : 'backward', + }; + }, this._resetIsScrollingDebounced); + } + + scrollToItem({ + align = 'auto', + columnIndex, + rowIndex, + }: { + align: ScrollToAlign, + columnIndex?: number, + rowIndex?: number, + }): void { + const { columnCount, height, rowCount, width } = this.props; + const { scrollLeft, scrollTop } = this.state; + const scrollbarSize = getScrollbarSize(); + + if (columnIndex !== undefined) { + columnIndex = Math.max(0, Math.min(columnIndex, columnCount - 1)); + } + if (rowIndex !== undefined) { + rowIndex = Math.max(0, Math.min(rowIndex, rowCount - 1)); + } + + const estimatedTotalHeight = getEstimatedTotalHeight( + this.props, + this._instanceProps + ); + const estimatedTotalWidth = getEstimatedTotalWidth( + this.props, + this._instanceProps + ); + + // The scrollbar size should be considered when scrolling an item into view, + // to ensure it's fully visible. + // But we only need to account for its size when it's actually visible. + const horizontalScrollbarSize = + estimatedTotalWidth > width ? scrollbarSize : 0; + const verticalScrollbarSize = + estimatedTotalHeight > height ? scrollbarSize : 0; + + this.scrollTo({ + scrollLeft: + columnIndex !== undefined + ? getOffsetForColumnAndAlignment( + this.props, + columnIndex, + align, + scrollLeft, + this._instanceProps, + verticalScrollbarSize + ) + : scrollLeft, + scrollTop: + rowIndex !== undefined + ? getOffsetForRowAndAlignment( + this.props, + rowIndex, + align, + scrollTop, + this._instanceProps, + horizontalScrollbarSize + ) + : scrollTop, + }); + } + + componentDidMount() { + const { initialScrollLeft, initialScrollTop } = this.props; + if (typeof initialScrollLeft === 'number' && this._outerRef != null) { + ((this._outerRef: any): HTMLDivElement).scrollLeft = initialScrollLeft; + } + if (typeof initialScrollTop === 'number' && this._outerRef != null) { + ((this._outerRef: any): HTMLDivElement).scrollTop = initialScrollTop; + } + + this._callPropsCallbacks(); + } + + componentDidUpdate() { + const { scrollLeft, scrollTop, scrollUpdateWasRequested } = this.state; + if (scrollUpdateWasRequested && this._outerRef !== null) { + ((this._outerRef: any): HTMLDivElement).scrollLeft = scrollLeft; + ((this._outerRef: any): HTMLDivElement).scrollTop = scrollTop; + } + + this._callPropsCallbacks(); + } + + componentWillUnmount() { + if (this._resetIsScrollingTimeoutId !== null) { + cancelTimeout(this._resetIsScrollingTimeoutId); + } + } + + render() { + const { + children, + className, + columnCount, + direction, + height, + innerRef, + innerElementType, + innerTagName, + itemData, + itemKey = defaultItemKey, + outerElementType, + outerTagName, + rowCount, + style, + useIsScrolling, + width, + } = this.props; + const { isScrolling } = this.state; + + const [ + columnStartIndex, + columnStopIndex, + ] = this._getHorizontalRangeToRender(); + const [rowStartIndex, rowStopIndex] = this._getVerticalRangeToRender(); + + const items = []; + if (columnCount > 0 && rowCount) { + for ( + let rowIndex = rowStartIndex; + rowIndex <= rowStopIndex; + rowIndex++ + ) { + for ( + let columnIndex = columnStartIndex; + columnIndex <= columnStopIndex; + columnIndex++ + ) { + items.push( + createElement(children, { + columnIndex, + data: itemData, + isScrolling: useIsScrolling ? isScrolling : undefined, + key: itemKey({ columnIndex, data: itemData, rowIndex }), + rowIndex, + style: this._getItemStyle(rowIndex, columnIndex), + }) + ); + } + } + } + + // Read this value AFTER items have been created, + // So their actual sizes (if variable) are taken into consideration. + const estimatedTotalHeight = getEstimatedTotalHeight( + this.props, + this._instanceProps + ); + const estimatedTotalWidth = getEstimatedTotalWidth( + this.props, + this._instanceProps + ); + + return createElement( + outerElementType || outerTagName || 'div', + { + className, + onScroll: this._onScroll, + ref: this._outerRefSetter, + style: { + position: 'relative', + height, + width, + overflow: 'auto', + WebkitOverflowScrolling: 'touch', + willChange: 'transform', + direction, + ...style, + }, + }, + createElement(innerElementType || innerTagName || 'div', { + children: items, + ref: innerRef, + style: { + height: estimatedTotalHeight, + pointerEvents: isScrolling ? 'none' : '', + width: estimatedTotalWidth, + }, + }) + ); + } + + _callOnItemsRendered: ( + overscanColumnStartIndex: number, + overscanColumnStopIndex: number, + overscanRowStartIndex: number, + overscanRowStopIndex: number, + visibleColumnStartIndex: number, + visibleColumnStopIndex: number, + visibleRowStartIndex: number, + visibleRowStopIndex: number + ) => void; + _callOnItemsRendered = memoizeOne( + ( + overscanColumnStartIndex: number, + overscanColumnStopIndex: number, + overscanRowStartIndex: number, + overscanRowStopIndex: number, + visibleColumnStartIndex: number, + visibleColumnStopIndex: number, + visibleRowStartIndex: number, + visibleRowStopIndex: number + ) => + ((this.props.onItemsRendered: any): OnItemsRenderedCallback)({ + overscanColumnStartIndex, + overscanColumnStopIndex, + overscanRowStartIndex, + overscanRowStopIndex, + visibleColumnStartIndex, + visibleColumnStopIndex, + visibleRowStartIndex, + visibleRowStopIndex, + }) + ); + + _callOnScroll: ( + scrollLeft: number, + scrollTop: number, + horizontalScrollDirection: ScrollDirection, + verticalScrollDirection: ScrollDirection, + scrollUpdateWasRequested: boolean + ) => void; + _callOnScroll = memoizeOne( + ( + scrollLeft: number, + scrollTop: number, + horizontalScrollDirection: ScrollDirection, + verticalScrollDirection: ScrollDirection, + scrollUpdateWasRequested: boolean + ) => + ((this.props.onScroll: any): OnScrollCallback)({ + horizontalScrollDirection, + scrollLeft, + scrollTop, + verticalScrollDirection, + scrollUpdateWasRequested, + }) + ); + + _callPropsCallbacks() { + const { columnCount, onItemsRendered, onScroll, rowCount } = this.props; + + if (typeof onItemsRendered === 'function') { + if (columnCount > 0 && rowCount > 0) { + const [ + overscanColumnStartIndex, + overscanColumnStopIndex, + visibleColumnStartIndex, + visibleColumnStopIndex, + ] = this._getHorizontalRangeToRender(); + const [ + overscanRowStartIndex, + overscanRowStopIndex, + visibleRowStartIndex, + visibleRowStopIndex, + ] = this._getVerticalRangeToRender(); + this._callOnItemsRendered( + overscanColumnStartIndex, + overscanColumnStopIndex, + overscanRowStartIndex, + overscanRowStopIndex, + visibleColumnStartIndex, + visibleColumnStopIndex, + visibleRowStartIndex, + visibleRowStopIndex + ); + } + } + + if (typeof onScroll === 'function') { + const { + horizontalScrollDirection, + scrollLeft, + scrollTop, + scrollUpdateWasRequested, + verticalScrollDirection, + } = this.state; + this._callOnScroll( + scrollLeft, + scrollTop, + horizontalScrollDirection, + verticalScrollDirection, + scrollUpdateWasRequested + ); + } + } + + // Lazily create and cache item styles while scrolling, + // So that pure component sCU will prevent re-renders. + // We maintain this cache, and pass a style prop rather than index, + // So that List can clear cached styles and force item re-render if necessary. + _getItemStyle: (rowIndex: number, columnIndex: number) => Object; + _getItemStyle = (rowIndex: number, columnIndex: number): Object => { + const { columnWidth, direction, rowHeight } = this.props; + + const itemStyleCache = this._getItemStyleCache( + shouldResetStyleCacheOnItemSizeChange && columnWidth, + shouldResetStyleCacheOnItemSizeChange && direction, + shouldResetStyleCacheOnItemSizeChange && rowHeight + ); + + const key = `${rowIndex}:${columnIndex}`; + + let style; + if (itemStyleCache.hasOwnProperty(key)) { + style = itemStyleCache[key]; + } else { + itemStyleCache[key] = style = { + position: 'absolute', + [direction === 'rtl' ? 'right' : 'left']: getColumnOffset( + this.props, + columnIndex, + this._instanceProps + ), + top: getRowOffset(this.props, rowIndex, this._instanceProps), + height: getRowHeight(this.props, rowIndex, this._instanceProps), + width: getColumnWidth(this.props, columnIndex, this._instanceProps), + }; + } + + return style; + }; + + _getItemStyleCache: (_: any, __: any, ___: any) => ItemStyleCache; + _getItemStyleCache = memoizeOne((_: any, __: any, ___: any) => ({})); + + _getHorizontalRangeToRender(): [number, number, number, number] { + const { + columnCount, + overscanColumnsCount, + overscanCount, + rowCount, + } = this.props; + const { horizontalScrollDirection, isScrolling, scrollLeft } = this.state; + + const overscanCountResolved: number = + overscanColumnsCount || overscanCount || 1; + + if (columnCount === 0 || rowCount === 0) { + return [0, 0, 0, 0]; + } + + const startIndex = getColumnStartIndexForOffset( + this.props, + scrollLeft, + this._instanceProps + ); + const stopIndex = getColumnStopIndexForStartIndex( + this.props, + startIndex, + scrollLeft, + this._instanceProps + ); + + // Overscan by one item in each direction so that tab/focus works. + // If there isn't at least one extra item, tab loops back around. + const overscanBackward = + !isScrolling || horizontalScrollDirection === 'backward' + ? Math.max(1, overscanCountResolved) + : 1; + const overscanForward = + !isScrolling || horizontalScrollDirection === 'forward' + ? Math.max(1, overscanCountResolved) + : 1; + + return [ + Math.max(0, startIndex - overscanBackward), + Math.max(0, Math.min(columnCount - 1, stopIndex + overscanForward)), + startIndex, + stopIndex, + ]; + } + + _getVerticalRangeToRender(): [number, number, number, number] { + const { + columnCount, + overscanCount, + overscanRowsCount, + rowCount, + } = this.props; + const { isScrolling, verticalScrollDirection, scrollTop } = this.state; + + const overscanCountResolved: number = + overscanRowsCount || overscanCount || 1; + + if (columnCount === 0 || rowCount === 0) { + return [0, 0, 0, 0]; + } + + const startIndex = getRowStartIndexForOffset( + this.props, + scrollTop, + this._instanceProps + ); + const stopIndex = getRowStopIndexForStartIndex( + this.props, + startIndex, + scrollTop, + this._instanceProps + ); + + // Overscan by one item in each direction so that tab/focus works. + // If there isn't at least one extra item, tab loops back around. + const overscanBackward = + !isScrolling || verticalScrollDirection === 'backward' + ? Math.max(1, overscanCountResolved) + : 1; + const overscanForward = + !isScrolling || verticalScrollDirection === 'forward' + ? Math.max(1, overscanCountResolved) + : 1; + + return [ + Math.max(0, startIndex - overscanBackward), + Math.max(0, Math.min(rowCount - 1, stopIndex + overscanForward)), + startIndex, + stopIndex, + ]; + } + + _onScroll = (event: ScrollEvent): void => { + const { + clientWidth, + scrollLeft, + scrollTop, + scrollWidth, + } = event.currentTarget; + this.setState(prevState => { + if ( + prevState.scrollLeft === scrollLeft && + prevState.scrollTop === scrollTop + ) { + // Scroll position may have been updated by cDM/cDU, + // In which case we don't need to trigger another render, + // And we don't want to update state.isScrolling. + return null; + } + + const { direction } = this.props; + + // HACK According to the spec, scrollLeft should be negative for RTL aligned elements. + // Chrome does not seem to adhere; its scrollLeft values are positive (measured relative to the left). + // See https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollLeft + let calculatedScrollLeft = scrollLeft; + if (direction === 'rtl') { + if (scrollLeft <= 0) { + calculatedScrollLeft = -scrollLeft; + } else { + calculatedScrollLeft = scrollWidth - clientWidth - scrollLeft; + } + } + + return { + isScrolling: true, + horizontalScrollDirection: + prevState.scrollLeft < scrollLeft ? 'forward' : 'backward', + scrollLeft: calculatedScrollLeft, + scrollTop, + verticalScrollDirection: + prevState.scrollTop < scrollTop ? 'forward' : 'backward', + scrollUpdateWasRequested: false, + }; + }, this._resetIsScrollingDebounced); + }; + + _outerRefSetter = (ref: any): void => { + const { outerRef } = this.props; + + this._outerRef = ((ref: any): HTMLDivElement); + + if (typeof outerRef === 'function') { + outerRef(ref); + } else if ( + outerRef != null && + typeof outerRef === 'object' && + outerRef.hasOwnProperty('current') + ) { + outerRef.current = ref; + } + }; + + _resetIsScrollingDebounced = () => { + if (this._resetIsScrollingTimeoutId !== null) { + cancelTimeout(this._resetIsScrollingTimeoutId); + } + + this._resetIsScrollingTimeoutId = requestTimeout( + this._resetIsScrolling, + IS_SCROLLING_DEBOUNCE_INTERVAL + ); + }; + + _resetIsScrolling = () => { + this._resetIsScrollingTimeoutId = null; + + this.setState({ isScrolling: false }, () => { + // Clear style cache after state update has been committed. + // This way we don't break pure sCU for items that don't use isScrolling param. + this._getItemStyleCache(-1); + }); + }; + }; +} + +const validateSharedProps = ( + { + children, + direction, + height, + innerTagName, + outerTagName, + overscanCount, + width, + }: Props, + { instance }: State +): void => { + if (process.env.NODE_ENV !== 'production') { + if (typeof overscanCount === 'number') { + if (devWarningsOverscanCount && !devWarningsOverscanCount.has(instance)) { + devWarningsOverscanCount.add(instance); + console.warn( + 'The overscanCount prop has been deprecated. ' + + 'Please use the overscanColumnsCount and overscanRowsCount props instead.' + ); + } + } + + if (innerTagName != null || outerTagName != null) { + if (devWarningsTagName && !devWarningsTagName.has(instance)) { + devWarningsTagName.add(instance); + console.warn( + 'The innerTagName and outerTagName props have been deprecated. ' + + 'Please use the innerElementType and outerElementType props instead.' + ); + } + } + + if (children == null) { + throw Error( + 'An invalid "children" prop has been specified. ' + + 'Value should be a React component. ' + + `"${children === null ? 'null' : typeof children}" was specified.` + ); + } + + switch (direction) { + case 'ltr': + case 'rtl': + // Valid values + break; + default: + throw Error( + 'An invalid "direction" prop has been specified. ' + + 'Value should be either "ltr" or "rtl". ' + + `"${direction}" was specified.` + ); + } + + if (typeof width !== 'number') { + throw Error( + 'An invalid "width" prop has been specified. ' + + 'Grids must specify a number for width. ' + + `"${width === null ? 'null' : typeof width}" was specified.` + ); + } + + if (typeof height !== 'number') { + throw Error( + 'An invalid "height" prop has been specified. ' + + 'Grids must specify a number for height. ' + + `"${height === null ? 'null' : typeof height}" was specified.` + ); + } + } +}; diff --git a/vendor/react-window/src/createListComponent.js b/vendor/react-window/src/createListComponent.js new file mode 100644 index 0000000000000..86696a0b803e6 --- /dev/null +++ b/vendor/react-window/src/createListComponent.js @@ -0,0 +1,670 @@ +// @flow + +import memoizeOne from 'memoize-one'; +import { createElement, PureComponent } from 'react'; +import { cancelTimeout, requestTimeout } from './timer'; + +import type { TimeoutID } from './timer'; + +export type ScrollToAlign = 'auto' | 'smart' | 'center' | 'start' | 'end'; + +type itemSize = number | ((index: number) => number); +// TODO Deprecate directions "horizontal" and "vertical" +type Direction = 'ltr' | 'rtl' | 'horizontal' | 'vertical'; +type Layout = 'horizontal' | 'vertical'; + +type RenderComponentProps = {| + data: T, + index: number, + isScrolling?: boolean, + style: Object, +|}; +type RenderComponent = React$ComponentType<$Shape>>; + +type ScrollDirection = 'forward' | 'backward'; + +type onItemsRenderedCallback = ({ + overscanStartIndex: number, + overscanStopIndex: number, + visibleStartIndex: number, + visibleStopIndex: number, +}) => void; +type onScrollCallback = ({ + scrollDirection: ScrollDirection, + scrollOffset: number, + scrollUpdateWasRequested: boolean, +}) => void; + +type ScrollEvent = SyntheticEvent; +type ItemStyleCache = { [index: number]: Object }; + +export type Props = {| + children: RenderComponent, + className?: string, + direction: Direction, + height: number | string, + initialScrollOffset?: number, + innerRef?: any, + innerElementType?: React$ElementType, + innerTagName?: string, // deprecated + itemCount: number, + itemData: T, + itemKey?: (index: number, data: T) => any, + itemSize: itemSize, + layout: Layout, + onItemsRendered?: onItemsRenderedCallback, + onScroll?: onScrollCallback, + outerRef?: any, + outerElementType?: React$ElementType, + outerTagName?: string, // deprecated + overscanCount: number, + style?: Object, + useIsScrolling: boolean, + width: number | string, +|}; + +type State = {| + instance: any, + isScrolling: boolean, + scrollDirection: ScrollDirection, + scrollOffset: number, + scrollUpdateWasRequested: boolean, +|}; + +type GetItemOffset = ( + props: Props, + index: number, + instanceProps: any +) => number; +type GetItemSize = ( + props: Props, + index: number, + instanceProps: any +) => number; +type GetEstimatedTotalSize = (props: Props, instanceProps: any) => number; +type GetOffsetForIndexAndAlignment = ( + props: Props, + index: number, + align: ScrollToAlign, + scrollOffset: number, + instanceProps: any +) => number; +type GetStartIndexForOffset = ( + props: Props, + offset: number, + instanceProps: any +) => number; +type GetStopIndexForStartIndex = ( + props: Props, + startIndex: number, + scrollOffset: number, + instanceProps: any +) => number; +type InitInstanceProps = (props: Props, instance: any) => any; +type ValidateProps = (props: Props) => void; + +const IS_SCROLLING_DEBOUNCE_INTERVAL = 150; + +const defaultItemKey = (index: number, data: any) => index; + +// In DEV mode, this Set helps us only log a warning once per component instance. +// This avoids spamming the console every time a render happens. +let devWarningsDirection = null; +let devWarningsTagName = null; +if (process.env.NODE_ENV !== 'production') { + if (typeof window !== 'undefined' && typeof window.WeakSet !== 'undefined') { + devWarningsDirection = new WeakSet(); + devWarningsTagName = new WeakSet(); + } +} + +export default function createListComponent({ + getItemOffset, + getEstimatedTotalSize, + getItemSize, + getOffsetForIndexAndAlignment, + getStartIndexForOffset, + getStopIndexForStartIndex, + initInstanceProps, + shouldResetStyleCacheOnItemSizeChange, + validateProps, +}: {| + getItemOffset: GetItemOffset, + getEstimatedTotalSize: GetEstimatedTotalSize, + getItemSize: GetItemSize, + getOffsetForIndexAndAlignment: GetOffsetForIndexAndAlignment, + getStartIndexForOffset: GetStartIndexForOffset, + getStopIndexForStartIndex: GetStopIndexForStartIndex, + initInstanceProps: InitInstanceProps, + shouldResetStyleCacheOnItemSizeChange: boolean, + validateProps: ValidateProps, +|}) { + return class List extends PureComponent, State> { + _instanceProps: any = initInstanceProps(this.props, this); + _outerRef: ?HTMLDivElement; + _resetIsScrollingTimeoutId: TimeoutID | null = null; + + static defaultProps = { + direction: 'ltr', + itemData: undefined, + layout: 'vertical', + overscanCount: 2, + useIsScrolling: false, + }; + + state: State = { + instance: this, + isScrolling: false, + scrollDirection: 'forward', + scrollOffset: + typeof this.props.initialScrollOffset === 'number' + ? this.props.initialScrollOffset + : 0, + scrollUpdateWasRequested: false, + }; + + // Always use explicit constructor for React components. + // It produces less code after transpilation. (#26) + // eslint-disable-next-line no-useless-constructor + constructor(props: Props) { + super(props); + } + + static getDerivedStateFromProps( + nextProps: Props, + prevState: State + ): $Shape | null { + validateSharedProps(nextProps, prevState); + validateProps(nextProps); + return null; + } + + scrollTo(scrollOffset: number): void { + scrollOffset = Math.max(0, scrollOffset); + + this.setState(prevState => { + if (prevState.scrollOffset === scrollOffset) { + return null; + } + return { + scrollDirection: + prevState.scrollOffset < scrollOffset ? 'forward' : 'backward', + scrollOffset: scrollOffset, + scrollUpdateWasRequested: true, + }; + }, this._resetIsScrollingDebounced); + } + + scrollToItem(index: number, align: ScrollToAlign = 'auto'): void { + const { itemCount } = this.props; + const { scrollOffset } = this.state; + + index = Math.max(0, Math.min(index, itemCount - 1)); + + this.scrollTo( + getOffsetForIndexAndAlignment( + this.props, + index, + align, + scrollOffset, + this._instanceProps + ) + ); + } + + componentDidMount() { + const { direction, initialScrollOffset, layout } = this.props; + + if (typeof initialScrollOffset === 'number' && this._outerRef !== null) { + // TODO Deprecate direction "horizontal" + if (direction === 'horizontal' || layout === 'horizontal') { + ((this + ._outerRef: any): HTMLDivElement).scrollLeft = initialScrollOffset; + } else { + ((this + ._outerRef: any): HTMLDivElement).scrollTop = initialScrollOffset; + } + } + + this._callPropsCallbacks(); + } + + componentDidUpdate() { + const { direction, layout } = this.props; + const { scrollOffset, scrollUpdateWasRequested } = this.state; + + if (scrollUpdateWasRequested && this._outerRef !== null) { + // TODO Deprecate direction "horizontal" + if (direction === 'horizontal' || layout === 'horizontal') { + ((this._outerRef: any): HTMLDivElement).scrollLeft = scrollOffset; + } else { + ((this._outerRef: any): HTMLDivElement).scrollTop = scrollOffset; + } + } + + this._callPropsCallbacks(); + } + + componentWillUnmount() { + if (this._resetIsScrollingTimeoutId !== null) { + cancelTimeout(this._resetIsScrollingTimeoutId); + } + } + + render() { + const { + children, + className, + direction, + height, + innerRef, + innerElementType, + innerTagName, + itemCount, + itemData, + itemKey = defaultItemKey, + layout, + outerElementType, + outerTagName, + style, + useIsScrolling, + width, + } = this.props; + const { isScrolling } = this.state; + + // TODO Deprecate direction "horizontal" + const isHorizontal = + direction === 'horizontal' || layout === 'horizontal'; + + const onScroll = isHorizontal + ? this._onScrollHorizontal + : this._onScrollVertical; + + const [startIndex, stopIndex] = this._getRangeToRender(); + + const items = []; + if (itemCount > 0) { + for (let index = startIndex; index <= stopIndex; index++) { + items.push( + createElement(children, { + data: itemData, + key: itemKey(index, itemData), + index, + isScrolling: useIsScrolling ? isScrolling : undefined, + style: this._getItemStyle(index), + }) + ); + } + } + + // Read this value AFTER items have been created, + // So their actual sizes (if variable) are taken into consideration. + const estimatedTotalSize = getEstimatedTotalSize( + this.props, + this._instanceProps + ); + + return createElement( + outerElementType || outerTagName || 'div', + { + className, + onScroll, + ref: this._outerRefSetter, + style: { + position: 'relative', + height, + width, + overflow: 'auto', + WebkitOverflowScrolling: 'touch', + willChange: 'transform', + direction, + ...style, + }, + }, + createElement(innerElementType || innerTagName || 'div', { + children: items, + ref: innerRef, + style: { + height: isHorizontal ? '100%' : estimatedTotalSize, + pointerEvents: isScrolling ? 'none' : '', + width: isHorizontal ? estimatedTotalSize : '100%', + }, + }) + ); + } + + _callOnItemsRendered: ( + overscanStartIndex: number, + overscanStopIndex: number, + visibleStartIndex: number, + visibleStopIndex: number + ) => void; + _callOnItemsRendered = memoizeOne( + ( + overscanStartIndex: number, + overscanStopIndex: number, + visibleStartIndex: number, + visibleStopIndex: number + ) => + ((this.props.onItemsRendered: any): onItemsRenderedCallback)({ + overscanStartIndex, + overscanStopIndex, + visibleStartIndex, + visibleStopIndex, + }) + ); + + _callOnScroll: ( + scrollDirection: ScrollDirection, + scrollOffset: number, + scrollUpdateWasRequested: boolean + ) => void; + _callOnScroll = memoizeOne( + ( + scrollDirection: ScrollDirection, + scrollOffset: number, + scrollUpdateWasRequested: boolean + ) => + ((this.props.onScroll: any): onScrollCallback)({ + scrollDirection, + scrollOffset, + scrollUpdateWasRequested, + }) + ); + + _callPropsCallbacks() { + if (typeof this.props.onItemsRendered === 'function') { + const { itemCount } = this.props; + if (itemCount > 0) { + const [ + overscanStartIndex, + overscanStopIndex, + visibleStartIndex, + visibleStopIndex, + ] = this._getRangeToRender(); + this._callOnItemsRendered( + overscanStartIndex, + overscanStopIndex, + visibleStartIndex, + visibleStopIndex + ); + } + } + + if (typeof this.props.onScroll === 'function') { + const { + scrollDirection, + scrollOffset, + scrollUpdateWasRequested, + } = this.state; + this._callOnScroll( + scrollDirection, + scrollOffset, + scrollUpdateWasRequested + ); + } + } + + // Lazily create and cache item styles while scrolling, + // So that pure component sCU will prevent re-renders. + // We maintain this cache, and pass a style prop rather than index, + // So that List can clear cached styles and force item re-render if necessary. + _getItemStyle: (index: number) => Object; + _getItemStyle = (index: number): Object => { + const { direction, itemSize, layout } = this.props; + + const itemStyleCache = this._getItemStyleCache( + shouldResetStyleCacheOnItemSizeChange && itemSize, + shouldResetStyleCacheOnItemSizeChange && layout, + shouldResetStyleCacheOnItemSizeChange && direction + ); + + let style; + if (itemStyleCache.hasOwnProperty(index)) { + style = itemStyleCache[index]; + } else { + const offset = getItemOffset(this.props, index, this._instanceProps); + const size = getItemSize(this.props, index, this._instanceProps); + + // TODO Deprecate direction "horizontal" + const isHorizontal = + direction === 'horizontal' || layout === 'horizontal'; + + itemStyleCache[index] = style = { + position: 'absolute', + [direction === 'rtl' ? 'right' : 'left']: isHorizontal ? offset : 0, + top: !isHorizontal ? offset : 0, + height: !isHorizontal ? size : '100%', + width: isHorizontal ? size : '100%', + }; + } + + return style; + }; + + _getItemStyleCache: (_: any, __: any, ___: any) => ItemStyleCache; + _getItemStyleCache = memoizeOne((_: any, __: any, ___: any) => ({})); + + _getRangeToRender(): [number, number, number, number] { + const { itemCount, overscanCount } = this.props; + const { isScrolling, scrollDirection, scrollOffset } = this.state; + + if (itemCount === 0) { + return [0, 0, 0, 0]; + } + + const startIndex = getStartIndexForOffset( + this.props, + scrollOffset, + this._instanceProps + ); + const stopIndex = getStopIndexForStartIndex( + this.props, + startIndex, + scrollOffset, + this._instanceProps + ); + + // Overscan by one item in each direction so that tab/focus works. + // If there isn't at least one extra item, tab loops back around. + const overscanBackward = + !isScrolling || scrollDirection === 'backward' + ? Math.max(1, overscanCount) + : 1; + const overscanForward = + !isScrolling || scrollDirection === 'forward' + ? Math.max(1, overscanCount) + : 1; + + return [ + Math.max(0, startIndex - overscanBackward), + Math.max(0, Math.min(itemCount - 1, stopIndex + overscanForward)), + startIndex, + stopIndex, + ]; + } + + _onScrollHorizontal = (event: ScrollEvent): void => { + const { clientWidth, scrollLeft, scrollWidth } = event.currentTarget; + this.setState(prevState => { + if (prevState.scrollOffset === scrollLeft) { + // Scroll position may have been updated by cDM/cDU, + // In which case we don't need to trigger another render, + // And we don't want to update state.isScrolling. + return null; + } + + const { direction } = this.props; + + // HACK According to the spec, scrollLeft should be negative for RTL aligned elements. + // Chrome does not seem to adhere; its scrolLeft values are positive (measured relative to the left). + // See https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollLeft + let scrollOffset = scrollLeft; + if (direction === 'rtl') { + if (scrollLeft <= 0) { + scrollOffset = -scrollOffset; + } else { + scrollOffset = scrollWidth - clientWidth - scrollLeft; + } + } + + return { + isScrolling: true, + scrollDirection: + prevState.scrollOffset < scrollLeft ? 'forward' : 'backward', + scrollOffset, + scrollUpdateWasRequested: false, + }; + }, this._resetIsScrollingDebounced); + }; + + _onScrollVertical = (event: ScrollEvent): void => { + const { scrollTop } = event.currentTarget; + this.setState(prevState => { + if (prevState.scrollOffset === scrollTop) { + // Scroll position may have been updated by cDM/cDU, + // In which case we don't need to trigger another render, + // And we don't want to update state.isScrolling. + return null; + } + + return { + isScrolling: true, + scrollDirection: + prevState.scrollOffset < scrollTop ? 'forward' : 'backward', + scrollOffset: scrollTop, + scrollUpdateWasRequested: false, + }; + }, this._resetIsScrollingDebounced); + }; + + _outerRefSetter = (ref: any): void => { + const { outerRef } = this.props; + + this._outerRef = ((ref: any): HTMLDivElement); + + if (typeof outerRef === 'function') { + outerRef(ref); + } else if ( + outerRef != null && + typeof outerRef === 'object' && + outerRef.hasOwnProperty('current') + ) { + outerRef.current = ref; + } + }; + + _resetIsScrollingDebounced = () => { + if (this._resetIsScrollingTimeoutId !== null) { + cancelTimeout(this._resetIsScrollingTimeoutId); + } + + this._resetIsScrollingTimeoutId = requestTimeout( + this._resetIsScrolling, + IS_SCROLLING_DEBOUNCE_INTERVAL + ); + }; + + _resetIsScrolling = () => { + this._resetIsScrollingTimeoutId = null; + + this.setState({ isScrolling: false }, () => { + // Clear style cache after state update has been committed. + // This way we don't break pure sCU for items that don't use isScrolling param. + this._getItemStyleCache(-1, null); + }); + }; + }; +} + +// NOTE: I considered further wrapping individual items with a pure ListItem component. +// This would avoid ever calling the render function for the same index more than once, +// But it would also add the overhead of a lot of components/fibers. +// I assume people already do this (render function returning a class component), +// So my doing it would just unnecessarily double the wrappers. + +const validateSharedProps = ( + { + children, + direction, + height, + layout, + innerTagName, + outerTagName, + width, + }: Props, + { instance }: State +): void => { + if (process.env.NODE_ENV !== 'production') { + if (innerTagName != null || outerTagName != null) { + if (devWarningsTagName && !devWarningsTagName.has(instance)) { + devWarningsTagName.add(instance); + console.warn( + 'The innerTagName and outerTagName props have been deprecated. ' + + 'Please use the innerElementType and outerElementType props instead.' + ); + } + } + + // TODO Deprecate direction "horizontal" + const isHorizontal = direction === 'horizontal' || layout === 'horizontal'; + + switch (direction) { + case 'horizontal': + case 'vertical': + if (devWarningsDirection && !devWarningsDirection.has(instance)) { + devWarningsDirection.add(instance); + console.warn( + 'The direction prop should be either "ltr" (default) or "rtl". ' + + 'Please use the layout prop to specify "vertical" (default) or "horizontal" orientation.' + ); + } + break; + case 'ltr': + case 'rtl': + // Valid values + break; + default: + throw Error( + 'An invalid "direction" prop has been specified. ' + + 'Value should be either "ltr" or "rtl". ' + + `"${direction}" was specified.` + ); + } + + switch (layout) { + case 'horizontal': + case 'vertical': + // Valid values + break; + default: + throw Error( + 'An invalid "layout" prop has been specified. ' + + 'Value should be either "horizontal" or "vertical". ' + + `"${layout}" was specified.` + ); + } + + if (children == null) { + throw Error( + 'An invalid "children" prop has been specified. ' + + 'Value should be a React component. ' + + `"${children === null ? 'null' : typeof children}" was specified.` + ); + } + + if (isHorizontal && typeof width !== 'number') { + throw Error( + 'An invalid "width" prop has been specified. ' + + 'Horizontal lists must specify a number for width. ' + + `"${width === null ? 'null' : typeof width}" was specified.` + ); + } else if (!isHorizontal && typeof height !== 'number') { + throw Error( + 'An invalid "height" prop has been specified. ' + + 'Vertical lists must specify a number for height. ' + + `"${height === null ? 'null' : typeof height}" was specified.` + ); + } + } +}; diff --git a/vendor/react-window/src/domHelpers.js b/vendor/react-window/src/domHelpers.js new file mode 100644 index 0000000000000..830aba68d79aa --- /dev/null +++ b/vendor/react-window/src/domHelpers.js @@ -0,0 +1,22 @@ +// @flow + +let size: number = -1; + +// This utility copied from "dom-helpers" package. +export function getScrollbarSize(recalculate?: boolean = false): number { + if (size === -1 || recalculate) { + const div = document.createElement('div'); + const style = div.style; + style.width = '50px'; + style.height = '50px'; + style.overflow = 'scroll'; + + ((document.body: any): HTMLBodyElement).appendChild(div); + + size = div.offsetWidth - div.clientWidth; + + ((document.body: any): HTMLBodyElement).removeChild(div); + } + + return size; +} diff --git a/vendor/react-window/src/index.js b/vendor/react-window/src/index.js new file mode 100644 index 0000000000000..dff39bf11968e --- /dev/null +++ b/vendor/react-window/src/index.js @@ -0,0 +1,9 @@ +// @flow + +export { default as VariableSizeGrid } from './VariableSizeGrid'; +export { default as VariableSizeList } from './VariableSizeList'; +export { default as FixedSizeGrid } from './FixedSizeGrid'; +export { default as FixedSizeList } from './FixedSizeList'; + +export { default as areEqual } from './areEqual'; +export { default as shouldComponentUpdate } from './shouldComponentUpdate'; diff --git a/vendor/react-window/src/shallowDiffers.js b/vendor/react-window/src/shallowDiffers.js new file mode 100644 index 0000000000000..c2395f6378f27 --- /dev/null +++ b/vendor/react-window/src/shallowDiffers.js @@ -0,0 +1,17 @@ +// @flow + +// Pulled from react-compat +// https://github.com/developit/preact-compat/blob/7c5de00e7c85e2ffd011bf3af02899b63f699d3a/src/index.js#L349 +export default function shallowDiffers(prev: Object, next: Object): boolean { + for (let attribute in prev) { + if (!(attribute in next)) { + return true; + } + } + for (let attribute in next) { + if (prev[attribute] !== next[attribute]) { + return true; + } + } + return false; +} diff --git a/vendor/react-window/src/shouldComponentUpdate.js b/vendor/react-window/src/shouldComponentUpdate.js new file mode 100644 index 0000000000000..6b891bd4d2e50 --- /dev/null +++ b/vendor/react-window/src/shouldComponentUpdate.js @@ -0,0 +1,16 @@ +// @flow + +import areEqual from './areEqual'; +import shallowDiffers from './shallowDiffers'; + +// Custom shouldComponentUpdate for class components. +// It knows to compare individual style props and ignore the wrapper object. +// See https://reactjs.org/docs/react-component.html#shouldcomponentupdate +export default function shouldComponentUpdate( + nextProps: Object, + nextState: Object +): boolean { + return ( + !areEqual(this.props, nextProps) || shallowDiffers(this.state, nextState) + ); +} diff --git a/vendor/react-window/src/timer.js b/vendor/react-window/src/timer.js new file mode 100644 index 0000000000000..aea75e5fd7c28 --- /dev/null +++ b/vendor/react-window/src/timer.js @@ -0,0 +1,37 @@ +// @flow + +// Animation frame based implementation of setTimeout. +// Inspired by Joe Lambert, https://gist.github.com/joelambert/1002116#file-requesttimeout-js + +const hasNativePerformanceNow = + typeof performance === 'object' && typeof performance.now === 'function'; + +const now = hasNativePerformanceNow + ? () => performance.now() + : () => Date.now(); + +export type TimeoutID = {| + id: AnimationFrameID, +|}; + +export function cancelTimeout(timeoutID: TimeoutID) { + cancelAnimationFrame(timeoutID.id); +} + +export function requestTimeout(callback: Function, delay: number): TimeoutID { + const start = now(); + + function tick() { + if (now() - start >= delay) { + callback.call(null); + } else { + timeoutID.id = requestAnimationFrame(tick); + } + } + + const timeoutID: TimeoutID = { + id: requestAnimationFrame(tick), + }; + + return timeoutID; +} diff --git a/yarn.lock b/yarn.lock index 71262041f7132..2304d556a7c5f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -758,7 +758,7 @@ "@babel/plugin-transform-react-jsx-self" "^7.0.0" "@babel/plugin-transform-react-jsx-source" "^7.0.0" -"@babel/runtime@7.3.1", "@babel/runtime@^7.0.0": +"@babel/runtime@7.3.1": version "7.3.1" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.3.1.tgz#574b03e8e8a9898eaf4a872a92ea20b7846f6f2a" integrity sha512-7jGW8ppV0ant637pIqAcFfQDDH1orEPGJb8aXfUozuCU3QqX7rX4DA8iwrbPrR1hcH0FTTHz47yQnk+bl5xHQA== @@ -8119,11 +8119,6 @@ mem@^4.0.0: mimic-fn "^1.0.0" p-is-promise "^2.0.0" -"memoize-one@>=3.1.1 <6": - version "5.0.4" - resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.0.4.tgz#005928aced5c43d890a4dfab18ca908b0ec92cbc" - integrity sha512-P0z5IeAH6qHHGkJIXWw0xC2HNEgkx/9uWWBQw64FJj3/ol14VYdfVGWWr0fXfjhhv3TKVIqUq65os6O4GUNksA== - memoize-one@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-3.1.1.tgz#ef609811e3bc28970eac2884eece64d167830d17" @@ -9754,14 +9749,6 @@ react-virtualized-auto-sizer@^1.0.2: resolved "https://registry.yarnpkg.com/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.2.tgz#a61dd4f756458bbf63bd895a92379f9b70f803bd" integrity sha512-MYXhTY1BZpdJFjUovvYHVBmkq79szK/k7V3MO+36gJkWGkrXKtyr4vCPtpphaTLRAdDNoYEYFZWE8LjN+PIHNg== -react-window@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.0.tgz#08196d1e0749fe60d0b29bb94ae682d37666e620" - integrity sha512-impIDEhm3+9/Y8ctiFaUI07a8Y/1jG0U4ONRUmYe7IsU+UQ2B0Ayyfbr+3R3nwAJpVGHx5pfI5SoDBt1o07YFw== - dependencies: - "@babel/runtime" "^7.0.0" - memoize-one ">=3.1.1 <6" - react@^0.0.0-50b50c26f: version "0.0.0-50b50c26f" resolved "https://registry.yarnpkg.com/react/-/react-0.0.0-50b50c26f.tgz#b782b579ce1f5d8bd696c5e45c744714ebecb111"