This commit is contained in:
21
node_modules/react-window/LICENSE.md
generated
vendored
Normal file
21
node_modules/react-window/LICENSE.md
generated
vendored
Normal file
@@ -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.
|
||||
171
node_modules/react-window/README.md
generated
vendored
Normal file
171
node_modules/react-window/README.md
generated
vendored
Normal file
@@ -0,0 +1,171 @@
|
||||
# react-window
|
||||
|
||||
> React components for efficiently rendering large lists and tabular data
|
||||
|
||||
### If you like this project, 🎉 [become a sponsor](https://github.com/sponsors/bvaughn/) or ☕ [buy me a coffee](http://givebrian.coffee/)
|
||||
|
||||
---
|
||||
|
||||
React window works by only rendering *part* of a large data set (just enough to fill the viewport). This helps address some common performance bottlenecks:
|
||||
1. It reduces the amount of work (and time) required to render the initial view and to process updates.
|
||||
2. It reduces the memory footprint by avoiding over-allocation of DOM nodes.
|
||||
|
||||
### Sponsors
|
||||
|
||||
The following wonderful companies have sponsored react-window:
|
||||
|
||||
<a href="https://opencollective.com/react-window/sponsor/0/website" target="_blank"><img src="https://opencollective.com/react-window/sponsor/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/sponsor/1/website" target="_blank"><img src="https://opencollective.com/react-window/sponsor/1/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/sponsor/2/website" target="_blank"><img src="https://opencollective.com/react-window/sponsor/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/sponsor/3/website" target="_blank"><img src="https://opencollective.com/react-window/sponsor/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/sponsor/4/website" target="_blank"><img src="https://opencollective.com/react-window/sponsor/4/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/sponsor/5/website" target="_blank"><img src="https://opencollective.com/react-window/sponsor/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/sponsor/6/website" target="_blank"><img src="https://opencollective.com/react-window/sponsor/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/sponsor/7/website" target="_blank"><img src="https://opencollective.com/react-window/sponsor/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/sponsor/8/website" target="_blank"><img src="https://opencollective.com/react-window/sponsor/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/sponsor/9/website" target="_blank"><img src="https://opencollective.com/react-window/sponsor/9/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/sponsor/10/website" target="_blank"><img src="https://opencollective.com/react-window/sponsor/10/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/sponsor/11/website" target="_blank"><img src="https://opencollective.com/react-window/sponsor/11/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/sponsor/12/website" target="_blank"><img src="https://opencollective.com/react-window/sponsor/12/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/sponsor/13/website" target="_blank"><img src="https://opencollective.com/react-window/sponsor/13/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/sponsor/14/website" target="_blank"><img src="https://opencollective.com/react-window/sponsor/14/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/sponsor/15/website" target="_blank"><img src="https://opencollective.com/react-window/sponsor/15/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/sponsor/16/website" target="_blank"><img src="https://opencollective.com/react-window/sponsor/16/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/sponsor/17/website" target="_blank"><img src="https://opencollective.com/react-window/sponsor/17/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/sponsor/18/website" target="_blank"><img src="https://opencollective.com/react-window/sponsor/18/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/sponsor/19/website" target="_blank"><img src="https://opencollective.com/react-window/sponsor/19/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/sponsor/20/website" target="_blank"><img src="https://opencollective.com/react-window/sponsor/20/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/sponsor/21/website" target="_blank"><img src="https://opencollective.com/react-window/sponsor/21/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/sponsor/22/website" target="_blank"><img src="https://opencollective.com/react-window/sponsor/22/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/sponsor/23/website" target="_blank"><img src="https://opencollective.com/react-window/sponsor/23/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/sponsor/24/website" target="_blank"><img src="https://opencollective.com/react-window/sponsor/24/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/sponsor/25/website" target="_blank"><img src="https://opencollective.com/react-window/sponsor/25/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/sponsor/26/website" target="_blank"><img src="https://opencollective.com/react-window/sponsor/26/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/sponsor/27/website" target="_blank"><img src="https://opencollective.com/react-window/sponsor/27/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/sponsor/28/website" target="_blank"><img src="https://opencollective.com/react-window/sponsor/28/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/sponsor/29/website" target="_blank"><img src="https://opencollective.com/react-window/sponsor/29/avatar.svg"></a>
|
||||
|
||||
[Learn more about becoming a sponsor!](https://opencollective.com/react-window#sponsor)
|
||||
|
||||
<a href="https://opencollective.com/react-window/backer/0/website" target="_blank"><img src="https://opencollective.com/react-window/backer/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/backer/1/website" target="_blank"><img src="https://opencollective.com/react-window/backer/1/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/backer/2/website" target="_blank"><img src="https://opencollective.com/react-window/backer/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/backer/3/website" target="_blank"><img src="https://opencollective.com/react-window/backer/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/backer/4/website" target="_blank"><img src="https://opencollective.com/react-window/backer/4/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/backer/5/website" target="_blank"><img src="https://opencollective.com/react-window/backer/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/backer/6/website" target="_blank"><img src="https://opencollective.com/react-window/backer/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/backer/7/website" target="_blank"><img src="https://opencollective.com/react-window/backer/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/backer/8/website" target="_blank"><img src="https://opencollective.com/react-window/backer/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/backer/9/website" target="_blank"><img src="https://opencollective.com/react-window/backer/9/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/backer/10/website" target="_blank"><img src="https://opencollective.com/react-window/backer/10/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/backer/11/website" target="_blank"><img src="https://opencollective.com/react-window/backer/11/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/backer/12/website" target="_blank"><img src="https://opencollective.com/react-window/backer/12/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/backer/13/website" target="_blank"><img src="https://opencollective.com/react-window/backer/13/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/backer/14/website" target="_blank"><img src="https://opencollective.com/react-window/backer/14/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/backer/15/website" target="_blank"><img src="https://opencollective.com/react-window/backer/15/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/backer/16/website" target="_blank"><img src="https://opencollective.com/react-window/backer/16/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/backer/17/website" target="_blank"><img src="https://opencollective.com/react-window/backer/17/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/backer/18/website" target="_blank"><img src="https://opencollective.com/react-window/backer/18/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/backer/19/website" target="_blank"><img src="https://opencollective.com/react-window/backer/19/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/backer/20/website" target="_blank"><img src="https://opencollective.com/react-window/backer/20/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/backer/21/website" target="_blank"><img src="https://opencollective.com/react-window/backer/21/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/backer/22/website" target="_blank"><img src="https://opencollective.com/react-window/backer/22/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/backer/23/website" target="_blank"><img src="https://opencollective.com/react-window/backer/23/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/backer/24/website" target="_blank"><img src="https://opencollective.com/react-window/backer/24/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/backer/25/website" target="_blank"><img src="https://opencollective.com/react-window/backer/25/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/backer/26/website" target="_blank"><img src="https://opencollective.com/react-window/backer/26/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/backer/27/website" target="_blank"><img src="https://opencollective.com/react-window/backer/27/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/backer/28/website" target="_blank"><img src="https://opencollective.com/react-window/backer/28/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-window/backer/29/website" target="_blank"><img src="https://opencollective.com/react-window/backer/29/avatar.svg"></a>
|
||||
|
||||
## 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).
|
||||
* [`react-vtree`](https://www.npmjs.com/package/react-vtree): Lightweight and flexible solution to render large tree structures (e.g., file system).
|
||||
|
||||
## 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**<sup>1</sup> 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)! 🙂
|
||||
|
||||
<sup>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.</sup>
|
||||
|
||||
### 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):
|
||||
|
||||
<img width="336" alt="screen shot 2019-03-07 at 7 29 08 pm" src="https://user-images.githubusercontent.com/29597/54005716-50f41880-410f-11e9-864f-a65bbdf49e07.png">
|
||||
|
||||
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...
|
||||
|
||||
<img src="https://user-images.githubusercontent.com/29597/54005352-eb535c80-410d-11e9-80b2-d3d02db1f599.gif" width="302" height="152">
|
||||
|
||||
...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!
|
||||
|
||||
<img width="257" alt="screen shot 2019-03-07 at 7 21 48 pm" src="https://user-images.githubusercontent.com/29597/54005433-45ecb880-410e-11e9-8721-420ff1a153da.png">
|
||||
|
||||
### 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):
|
||||
|
||||
<img width="368" alt="screen shot 2019-03-07 at 7 32 32 pm" src="https://user-images.githubusercontent.com/29597/54006733-653a1480-4113-11e9-907b-08ca5e27b3f9.png">
|
||||
|
||||
Here's a [Code Sandbox demo](https://codesandbox.io/s/5wqo7z2np4).
|
||||
|
||||
### Can I attach custom properties or event handlers?
|
||||
|
||||
Yes, using the `outerElementType` prop.
|
||||
|
||||
<img width="412" alt="Screen Shot 2019-03-12 at 8 58 09 AM" src="https://user-images.githubusercontent.com/29597/54215333-f9ee9a80-44a4-11e9-9142-34c67026d950.png">
|
||||
|
||||
Here's a [Code Sandbox demo](https://codesandbox.io/s/4zqx79nww0).
|
||||
|
||||
### Can I add padding to the top and bottom of a list?
|
||||
|
||||
Yes, although it requires a bit of inline styling.
|
||||
|
||||
<img width="418" alt="Screen Shot 2019-06-02 at 8 38 18 PM" src="https://user-images.githubusercontent.com/29597/58774454-65ad4480-8576-11e9-8889-07044fd41393.png">
|
||||
|
||||
Here's a [Code Sandbox demo](https://codesandbox.io/s/react-window-list-padding-dg0pq).
|
||||
|
||||
### Can I add gutter or padding between items?
|
||||
|
||||
Yes, although it requires a bit of inline styling.
|
||||
|
||||
<img width="416" alt="Screen Shot 2019-03-26 at 6 33 56 PM" src="https://user-images.githubusercontent.com/29597/55043972-c14ad700-4ff5-11e9-9caa-2e9f4d85f96c.png">
|
||||
|
||||
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)
|
||||
2
node_modules/react-window/dist/index-dev.umd.js
generated
vendored
Normal file
2
node_modules/react-window/dist/index-dev.umd.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
1
node_modules/react-window/dist/index-dev.umd.js.map
generated
vendored
Normal file
1
node_modules/react-window/dist/index-dev.umd.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
2
node_modules/react-window/dist/index-prod.umd.js
generated
vendored
Normal file
2
node_modules/react-window/dist/index-prod.umd.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
1
node_modules/react-window/dist/index-prod.umd.js.map
generated
vendored
Normal file
1
node_modules/react-window/dist/index-prod.umd.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
2087
node_modules/react-window/dist/index.cjs.js
generated
vendored
Normal file
2087
node_modules/react-window/dist/index.cjs.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
3
node_modules/react-window/dist/index.cjs.js.flow
generated
vendored
Normal file
3
node_modules/react-window/dist/index.cjs.js.flow
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
// @flow
|
||||
|
||||
export * from '../src';
|
||||
1
node_modules/react-window/dist/index.cjs.js.map
generated
vendored
Normal file
1
node_modules/react-window/dist/index.cjs.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
2076
node_modules/react-window/dist/index.esm.js
generated
vendored
Normal file
2076
node_modules/react-window/dist/index.esm.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
3
node_modules/react-window/dist/index.esm.js.flow
generated
vendored
Normal file
3
node_modules/react-window/dist/index.esm.js.flow
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
// @flow
|
||||
|
||||
export * from '../src';
|
||||
1
node_modules/react-window/dist/index.esm.js.map
generated
vendored
Normal file
1
node_modules/react-window/dist/index.esm.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
114
node_modules/react-window/package.json
generated
vendored
Normal file
114
node_modules/react-window/package.json
generated
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
{
|
||||
"name": "react-window",
|
||||
"version": "1.8.10",
|
||||
"description": "React components for efficiently rendering large, scrollable lists and tabular data",
|
||||
"author": "Brian Vaughn <brian.david.vaughn@gmail.com> (https://github.com/bvaughn/)",
|
||||
"contributors": [
|
||||
"Brian Vaughn <brian.david.vaughn@gmail.com> (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"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"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 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.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.111.0",
|
||||
"gh-pages": "^1.1.0",
|
||||
"lint-staged": "^7.0.5",
|
||||
"prettier": "^1.12.1",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-is": "^17.0.1",
|
||||
"react-scripts": "^1.1.1",
|
||||
"react-test-renderer": "^17.0.1",
|
||||
"rollup": "^1.4.1",
|
||||
"rollup-plugin-babel": "^4.3.2",
|
||||
"rollup-plugin-commonjs": "^9.2.1",
|
||||
"rollup-plugin-node-resolve": "^4.0.1",
|
||||
"rollup-plugin-replace": "^2.2.0",
|
||||
"rollup-plugin-terser": "^5.1.0"
|
||||
}
|
||||
}
|
||||
244
node_modules/react-window/src/FixedSizeGrid.js
generated
vendored
Normal file
244
node_modules/react-window/src/FixedSizeGrid.js
generated
vendored
Normal file
@@ -0,0 +1,244 @@
|
||||
// @flow
|
||||
|
||||
import createGridComponent from './createGridComponent';
|
||||
|
||||
import type { Props, ScrollToAlign } from './createGridComponent';
|
||||
|
||||
const FixedSizeGrid = createGridComponent({
|
||||
getColumnOffset: ({ columnWidth }: Props<any>, index: number): number =>
|
||||
index * ((columnWidth: any): number),
|
||||
|
||||
getColumnWidth: ({ columnWidth }: Props<any>, index: number): number =>
|
||||
((columnWidth: any): number),
|
||||
|
||||
getRowOffset: ({ rowHeight }: Props<any>, index: number): number =>
|
||||
index * ((rowHeight: any): number),
|
||||
|
||||
getRowHeight: ({ rowHeight }: Props<any>, index: number): number =>
|
||||
((rowHeight: any): number),
|
||||
|
||||
getEstimatedTotalHeight: ({ rowCount, rowHeight }: Props<any>) =>
|
||||
((rowHeight: any): number) * rowCount,
|
||||
|
||||
getEstimatedTotalWidth: ({ columnCount, columnWidth }: Props<any>) =>
|
||||
((columnWidth: any): number) * columnCount,
|
||||
|
||||
getOffsetForColumnAndAlignment: (
|
||||
{ columnCount, columnWidth, width }: Props<any>,
|
||||
columnIndex: number,
|
||||
align: ScrollToAlign,
|
||||
scrollLeft: number,
|
||||
instanceProps: typeof undefined,
|
||||
scrollbarSize: number
|
||||
): number => {
|
||||
const lastColumnOffset = Math.max(
|
||||
0,
|
||||
columnCount * ((columnWidth: any): number) - width
|
||||
);
|
||||
const maxOffset = Math.min(
|
||||
lastColumnOffset,
|
||||
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':
|
||||
// "Centered" offset is usually the average of the min and max.
|
||||
// But near the edges of the list, this doesn't hold true.
|
||||
const middleOffset = Math.round(
|
||||
minOffset + (maxOffset - minOffset) / 2
|
||||
);
|
||||
if (middleOffset < Math.ceil(width / 2)) {
|
||||
return 0; // near the beginning
|
||||
} else if (middleOffset > lastColumnOffset + Math.floor(width / 2)) {
|
||||
return lastColumnOffset; // near the end
|
||||
} else {
|
||||
return middleOffset;
|
||||
}
|
||||
case 'auto':
|
||||
default:
|
||||
if (scrollLeft >= minOffset && scrollLeft <= maxOffset) {
|
||||
return scrollLeft;
|
||||
} else if (minOffset > maxOffset) {
|
||||
// Because we only take into account the scrollbar size when calculating minOffset
|
||||
// this value can be larger than maxOffset when at the end of the list
|
||||
return minOffset;
|
||||
} else if (scrollLeft < minOffset) {
|
||||
return minOffset;
|
||||
} else {
|
||||
return maxOffset;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getOffsetForRowAndAlignment: (
|
||||
{ rowHeight, height, rowCount }: Props<any>,
|
||||
rowIndex: number,
|
||||
align: ScrollToAlign,
|
||||
scrollTop: number,
|
||||
instanceProps: typeof undefined,
|
||||
scrollbarSize: number
|
||||
): number => {
|
||||
const lastRowOffset = Math.max(
|
||||
0,
|
||||
rowCount * ((rowHeight: any): number) - height
|
||||
);
|
||||
const maxOffset = Math.min(
|
||||
lastRowOffset,
|
||||
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':
|
||||
// "Centered" offset is usually the average of the min and max.
|
||||
// But near the edges of the list, this doesn't hold true.
|
||||
const middleOffset = Math.round(
|
||||
minOffset + (maxOffset - minOffset) / 2
|
||||
);
|
||||
if (middleOffset < Math.ceil(height / 2)) {
|
||||
return 0; // near the beginning
|
||||
} else if (middleOffset > lastRowOffset + Math.floor(height / 2)) {
|
||||
return lastRowOffset; // near the end
|
||||
} else {
|
||||
return middleOffset;
|
||||
}
|
||||
case 'auto':
|
||||
default:
|
||||
if (scrollTop >= minOffset && scrollTop <= maxOffset) {
|
||||
return scrollTop;
|
||||
} else if (minOffset > maxOffset) {
|
||||
// Because we only take into account the scrollbar size when calculating minOffset
|
||||
// this value can be larger than maxOffset when at the end of the list
|
||||
return minOffset;
|
||||
} else if (scrollTop < minOffset) {
|
||||
return minOffset;
|
||||
} else {
|
||||
return maxOffset;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getColumnStartIndexForOffset: (
|
||||
{ columnWidth, columnCount }: Props<any>,
|
||||
scrollLeft: number
|
||||
): number =>
|
||||
Math.max(
|
||||
0,
|
||||
Math.min(
|
||||
columnCount - 1,
|
||||
Math.floor(scrollLeft / ((columnWidth: any): number))
|
||||
)
|
||||
),
|
||||
|
||||
getColumnStopIndexForStartIndex: (
|
||||
{ columnWidth, columnCount, width }: Props<any>,
|
||||
startIndex: number,
|
||||
scrollLeft: number
|
||||
): number => {
|
||||
const left = startIndex * ((columnWidth: any): number);
|
||||
const numVisibleColumns = Math.ceil(
|
||||
(width + scrollLeft - left) / ((columnWidth: any): number)
|
||||
);
|
||||
return Math.max(
|
||||
0,
|
||||
Math.min(
|
||||
columnCount - 1,
|
||||
startIndex + numVisibleColumns - 1 // -1 is because stop index is inclusive
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
getRowStartIndexForOffset: (
|
||||
{ rowHeight, rowCount }: Props<any>,
|
||||
scrollTop: number
|
||||
): number =>
|
||||
Math.max(
|
||||
0,
|
||||
Math.min(rowCount - 1, Math.floor(scrollTop / ((rowHeight: any): number)))
|
||||
),
|
||||
|
||||
getRowStopIndexForStartIndex: (
|
||||
{ rowHeight, rowCount, height }: Props<any>,
|
||||
startIndex: number,
|
||||
scrollTop: number
|
||||
): number => {
|
||||
const top = startIndex * ((rowHeight: any): number);
|
||||
const numVisibleRows = Math.ceil(
|
||||
(height + scrollTop - top) / ((rowHeight: any): number)
|
||||
);
|
||||
return Math.max(
|
||||
0,
|
||||
Math.min(
|
||||
rowCount - 1,
|
||||
startIndex + numVisibleRows - 1 // -1 is because stop index is inclusive
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
initInstanceProps(props: Props<any>): any {
|
||||
// Noop
|
||||
},
|
||||
|
||||
shouldResetStyleCacheOnItemSizeChange: true,
|
||||
|
||||
validateProps: ({ columnWidth, rowHeight }: Props<any>): 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;
|
||||
137
node_modules/react-window/src/FixedSizeList.js
generated
vendored
Normal file
137
node_modules/react-window/src/FixedSizeList.js
generated
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
// @flow
|
||||
|
||||
import createListComponent from './createListComponent';
|
||||
|
||||
import type { Props, ScrollToAlign } from './createListComponent';
|
||||
|
||||
type InstanceProps = any;
|
||||
|
||||
const FixedSizeList = createListComponent({
|
||||
getItemOffset: ({ itemSize }: Props<any>, index: number): number =>
|
||||
index * ((itemSize: any): number),
|
||||
|
||||
getItemSize: ({ itemSize }: Props<any>, index: number): number =>
|
||||
((itemSize: any): number),
|
||||
|
||||
getEstimatedTotalSize: ({ itemCount, itemSize }: Props<any>) =>
|
||||
((itemSize: any): number) * itemCount,
|
||||
|
||||
getOffsetForIndexAndAlignment: (
|
||||
{ direction, height, itemCount, itemSize, layout, width }: Props<any>,
|
||||
index: number,
|
||||
align: ScrollToAlign,
|
||||
scrollOffset: number,
|
||||
instanceProps: InstanceProps,
|
||||
scrollbarSize: number
|
||||
): number => {
|
||||
// TODO Deprecate direction "horizontal"
|
||||
const isHorizontal = direction === 'horizontal' || layout === 'horizontal';
|
||||
const size = (((isHorizontal ? width : height): any): number);
|
||||
const lastItemOffset = Math.max(
|
||||
0,
|
||||
itemCount * ((itemSize: any): number) - size
|
||||
);
|
||||
const maxOffset = Math.min(
|
||||
lastItemOffset,
|
||||
index * ((itemSize: any): number)
|
||||
);
|
||||
const minOffset = Math.max(
|
||||
0,
|
||||
index * ((itemSize: any): number) -
|
||||
size +
|
||||
((itemSize: any): number) +
|
||||
scrollbarSize
|
||||
);
|
||||
|
||||
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': {
|
||||
// "Centered" offset is usually the average of the min and max.
|
||||
// But near the edges of the list, this doesn't hold true.
|
||||
const middleOffset = Math.round(
|
||||
minOffset + (maxOffset - minOffset) / 2
|
||||
);
|
||||
if (middleOffset < Math.ceil(size / 2)) {
|
||||
return 0; // near the beginning
|
||||
} else if (middleOffset > lastItemOffset + Math.floor(size / 2)) {
|
||||
return lastItemOffset; // near the end
|
||||
} else {
|
||||
return middleOffset;
|
||||
}
|
||||
}
|
||||
case 'auto':
|
||||
default:
|
||||
if (scrollOffset >= minOffset && scrollOffset <= maxOffset) {
|
||||
return scrollOffset;
|
||||
} else if (scrollOffset < minOffset) {
|
||||
return minOffset;
|
||||
} else {
|
||||
return maxOffset;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getStartIndexForOffset: (
|
||||
{ itemCount, itemSize }: Props<any>,
|
||||
offset: number
|
||||
): number =>
|
||||
Math.max(
|
||||
0,
|
||||
Math.min(itemCount - 1, Math.floor(offset / ((itemSize: any): number)))
|
||||
),
|
||||
|
||||
getStopIndexForStartIndex: (
|
||||
{ direction, height, itemCount, itemSize, layout, width }: Props<any>,
|
||||
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);
|
||||
const numVisibleItems = Math.ceil(
|
||||
(size + scrollOffset - offset) / ((itemSize: any): number)
|
||||
);
|
||||
return Math.max(
|
||||
0,
|
||||
Math.min(
|
||||
itemCount - 1,
|
||||
startIndex + numVisibleItems - 1 // -1 is because stop index is inclusive
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
initInstanceProps(props: Props<any>): any {
|
||||
// Noop
|
||||
},
|
||||
|
||||
shouldResetStyleCacheOnItemSizeChange: true,
|
||||
|
||||
validateProps: ({ itemSize }: Props<any>): 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;
|
||||
507
node_modules/react-window/src/VariableSizeGrid.js
generated
vendored
Normal file
507
node_modules/react-window/src/VariableSizeGrid.js
generated
vendored
Normal file
@@ -0,0 +1,507 @@
|
||||
// @flow
|
||||
|
||||
import createGridComponent from './createGridComponent';
|
||||
|
||||
import type { Props, ScrollToAlign } from './createGridComponent';
|
||||
|
||||
const DEFAULT_ESTIMATED_ITEM_SIZE = 50;
|
||||
|
||||
type VariableSizeProps = {|
|
||||
estimatedColumnWidth: number,
|
||||
estimatedRowHeight: number,
|
||||
...Props<any>,
|
||||
|};
|
||||
|
||||
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<any>,
|
||||
{ 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<any>,
|
||||
{
|
||||
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<any>,
|
||||
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<any>,
|
||||
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<any>,
|
||||
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<any>,
|
||||
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<any>,
|
||||
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 (minOffset > maxOffset) {
|
||||
// Because we only take into account the scrollbar size when calculating minOffset
|
||||
// this value can be larger than maxOffset when at the end of the list
|
||||
return minOffset;
|
||||
} else if (scrollOffset < minOffset) {
|
||||
return minOffset;
|
||||
} else {
|
||||
return maxOffset;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const VariableSizeGrid = createGridComponent({
|
||||
getColumnOffset: (
|
||||
props: Props<any>,
|
||||
index: number,
|
||||
instanceProps: InstanceProps
|
||||
): number => getItemMetadata('column', props, index, instanceProps).offset,
|
||||
|
||||
getColumnStartIndexForOffset: (
|
||||
props: Props<any>,
|
||||
scrollLeft: number,
|
||||
instanceProps: InstanceProps
|
||||
): number => findNearestItem('column', props, instanceProps, scrollLeft),
|
||||
|
||||
getColumnStopIndexForStartIndex: (
|
||||
props: Props<any>,
|
||||
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<any>,
|
||||
index: number,
|
||||
instanceProps: InstanceProps
|
||||
): number => instanceProps.columnMetadataMap[index].size,
|
||||
|
||||
getEstimatedTotalHeight,
|
||||
getEstimatedTotalWidth,
|
||||
|
||||
getOffsetForColumnAndAlignment: (
|
||||
props: Props<any>,
|
||||
index: number,
|
||||
align: ScrollToAlign,
|
||||
scrollOffset: number,
|
||||
instanceProps: InstanceProps,
|
||||
scrollbarSize: number
|
||||
): number =>
|
||||
getOffsetForIndexAndAlignment(
|
||||
'column',
|
||||
props,
|
||||
index,
|
||||
align,
|
||||
scrollOffset,
|
||||
instanceProps,
|
||||
scrollbarSize
|
||||
),
|
||||
|
||||
getOffsetForRowAndAlignment: (
|
||||
props: Props<any>,
|
||||
index: number,
|
||||
align: ScrollToAlign,
|
||||
scrollOffset: number,
|
||||
instanceProps: InstanceProps,
|
||||
scrollbarSize: number
|
||||
): number =>
|
||||
getOffsetForIndexAndAlignment(
|
||||
'row',
|
||||
props,
|
||||
index,
|
||||
align,
|
||||
scrollOffset,
|
||||
instanceProps,
|
||||
scrollbarSize
|
||||
),
|
||||
|
||||
getRowOffset: (
|
||||
props: Props<any>,
|
||||
index: number,
|
||||
instanceProps: InstanceProps
|
||||
): number => getItemMetadata('row', props, index, instanceProps).offset,
|
||||
|
||||
getRowHeight: (
|
||||
props: Props<any>,
|
||||
index: number,
|
||||
instanceProps: InstanceProps
|
||||
): number => instanceProps.rowMetadataMap[index].size,
|
||||
|
||||
getRowStartIndexForOffset: (
|
||||
props: Props<any>,
|
||||
scrollTop: number,
|
||||
instanceProps: InstanceProps
|
||||
): number => findNearestItem('row', props, instanceProps, scrollTop),
|
||||
|
||||
getRowStopIndexForStartIndex: (
|
||||
props: Props<any>,
|
||||
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<any>, 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<any>): 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;
|
||||
317
node_modules/react-window/src/VariableSizeList.js
generated
vendored
Normal file
317
node_modules/react-window/src/VariableSizeList.js
generated
vendored
Normal file
@@ -0,0 +1,317 @@
|
||||
// @flow
|
||||
|
||||
import createListComponent from './createListComponent';
|
||||
|
||||
import type { Props, ScrollToAlign } from './createListComponent';
|
||||
|
||||
const DEFAULT_ESTIMATED_ITEM_SIZE = 50;
|
||||
|
||||
type VariableSizeProps = {|
|
||||
estimatedItemSize: number,
|
||||
...Props<any>,
|
||||
|};
|
||||
|
||||
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<any>,
|
||||
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<any>,
|
||||
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<any>,
|
||||
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<any>,
|
||||
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<any>,
|
||||
{ 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<any>,
|
||||
index: number,
|
||||
instanceProps: InstanceProps
|
||||
): number => getItemMetadata(props, index, instanceProps).offset,
|
||||
|
||||
getItemSize: (
|
||||
props: Props<any>,
|
||||
index: number,
|
||||
instanceProps: InstanceProps
|
||||
): number => instanceProps.itemMetadataMap[index].size,
|
||||
|
||||
getEstimatedTotalSize,
|
||||
|
||||
getOffsetForIndexAndAlignment: (
|
||||
props: Props<any>,
|
||||
index: number,
|
||||
align: ScrollToAlign,
|
||||
scrollOffset: number,
|
||||
instanceProps: InstanceProps,
|
||||
scrollbarSize: number
|
||||
): 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 + scrollbarSize
|
||||
);
|
||||
|
||||
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) {
|
||||
return minOffset;
|
||||
} else {
|
||||
return maxOffset;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getStartIndexForOffset: (
|
||||
props: Props<any>,
|
||||
offset: number,
|
||||
instanceProps: InstanceProps
|
||||
): number => findNearestItem(props, instanceProps, offset),
|
||||
|
||||
getStopIndexForStartIndex: (
|
||||
props: Props<any>,
|
||||
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<any>, 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<any>): 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;
|
||||
18
node_modules/react-window/src/areEqual.js
generated
vendored
Normal file
18
node_modules/react-window/src/areEqual.js
generated
vendored
Normal file
@@ -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)
|
||||
);
|
||||
}
|
||||
919
node_modules/react-window/src/createGridComponent.js
generated
vendored
Normal file
919
node_modules/react-window/src/createGridComponent.js
generated
vendored
Normal file
@@ -0,0 +1,919 @@
|
||||
// @flow
|
||||
|
||||
import memoizeOne from 'memoize-one';
|
||||
import { createElement, PureComponent } from 'react';
|
||||
import { cancelTimeout, requestTimeout } from './timer';
|
||||
import { getScrollbarSize, getRTLOffsetType } 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<T> = {|
|
||||
columnIndex: number,
|
||||
data: T,
|
||||
isScrolling?: boolean,
|
||||
rowIndex: number,
|
||||
style: Object,
|
||||
|};
|
||||
export type RenderComponent<T> = React$ComponentType<
|
||||
$Shape<RenderComponentProps<T>>
|
||||
>;
|
||||
|
||||
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<HTMLDivElement>;
|
||||
type ItemStyleCache = { [key: string]: Object };
|
||||
|
||||
type OuterProps = {|
|
||||
children: React$Node,
|
||||
className: string | void,
|
||||
onScroll: ScrollEvent => void,
|
||||
style: {
|
||||
[string]: mixed,
|
||||
},
|
||||
|};
|
||||
|
||||
type InnerProps = {|
|
||||
children: React$Node,
|
||||
style: {
|
||||
[string]: mixed,
|
||||
},
|
||||
|};
|
||||
|
||||
export type Props<T> = {|
|
||||
children: RenderComponent<T>,
|
||||
className?: string,
|
||||
columnCount: number,
|
||||
columnWidth: itemSize,
|
||||
direction: Direction,
|
||||
height: number,
|
||||
initialScrollLeft?: number,
|
||||
initialScrollTop?: number,
|
||||
innerRef?: any,
|
||||
innerElementType?: string | React$AbstractComponent<InnerProps, any>,
|
||||
innerTagName?: string, // deprecated
|
||||
itemData: T,
|
||||
itemKey?: (params: {|
|
||||
columnIndex: number,
|
||||
data: T,
|
||||
rowIndex: number,
|
||||
|}) => any,
|
||||
onItemsRendered?: OnItemsRenderedCallback,
|
||||
onScroll?: OnScrollCallback,
|
||||
outerRef?: any,
|
||||
outerElementType?: string | React$AbstractComponent<OuterProps, any>,
|
||||
outerTagName?: string, // deprecated
|
||||
overscanColumnCount?: number,
|
||||
overscanColumnsCount?: number, // deprecated
|
||||
overscanCount?: number, // deprecated
|
||||
overscanRowCount?: number,
|
||||
overscanRowsCount?: number, // deprecated
|
||||
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<any>,
|
||||
index: number,
|
||||
instanceProps: any
|
||||
) => number;
|
||||
type getItemSize = (
|
||||
props: Props<any>,
|
||||
index: number,
|
||||
instanceProps: any
|
||||
) => number;
|
||||
type getEstimatedTotalSize = (props: Props<any>, instanceProps: any) => number;
|
||||
type GetOffsetForItemAndAlignment = (
|
||||
props: Props<any>,
|
||||
index: number,
|
||||
align: ScrollToAlign,
|
||||
scrollOffset: number,
|
||||
instanceProps: any,
|
||||
scrollbarSize: number
|
||||
) => number;
|
||||
type GetStartIndexForOffset = (
|
||||
props: Props<any>,
|
||||
offset: number,
|
||||
instanceProps: any
|
||||
) => number;
|
||||
type GetStopIndexForStartIndex = (
|
||||
props: Props<any>,
|
||||
startIndex: number,
|
||||
scrollOffset: number,
|
||||
instanceProps: any
|
||||
) => number;
|
||||
type InitInstanceProps = (props: Props<any>, instance: any) => any;
|
||||
type ValidateProps = (props: Props<any>) => 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 devWarningsOverscanRowsColumnsCount = null;
|
||||
let devWarningsTagName = null;
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (typeof window !== 'undefined' && typeof window.WeakSet !== 'undefined') {
|
||||
devWarningsOverscanCount = new WeakSet();
|
||||
devWarningsOverscanRowsColumnsCount = 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<T> extends PureComponent<Props<T>, 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<T>) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(
|
||||
nextProps: Props<T>,
|
||||
prevState: State
|
||||
): $Shape<State> | 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 (this._outerRef != null) {
|
||||
const outerRef = ((this._outerRef: any): HTMLElement);
|
||||
if (typeof initialScrollLeft === 'number') {
|
||||
outerRef.scrollLeft = initialScrollLeft;
|
||||
}
|
||||
if (typeof initialScrollTop === 'number') {
|
||||
outerRef.scrollTop = initialScrollTop;
|
||||
}
|
||||
}
|
||||
|
||||
this._callPropsCallbacks();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const { direction } = this.props;
|
||||
const { scrollLeft, scrollTop, scrollUpdateWasRequested } = this.state;
|
||||
|
||||
if (scrollUpdateWasRequested && this._outerRef != null) {
|
||||
// TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements.
|
||||
// This is not the case for all browsers though (e.g. Chrome reports values as positive, measured relative to the left).
|
||||
// So we need to determine which browser behavior we're dealing with, and mimic it.
|
||||
const outerRef = ((this._outerRef: any): HTMLElement);
|
||||
if (direction === 'rtl') {
|
||||
switch (getRTLOffsetType()) {
|
||||
case 'negative':
|
||||
outerRef.scrollLeft = -scrollLeft;
|
||||
break;
|
||||
case 'positive-ascending':
|
||||
outerRef.scrollLeft = scrollLeft;
|
||||
break;
|
||||
default:
|
||||
const { clientWidth, scrollWidth } = outerRef;
|
||||
outerRef.scrollLeft = scrollWidth - clientWidth - scrollLeft;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
outerRef.scrollLeft = Math.max(0, scrollLeft);
|
||||
}
|
||||
|
||||
outerRef.scrollTop = Math.max(0, 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' : undefined,
|
||||
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 {
|
||||
const offset = getColumnOffset(
|
||||
this.props,
|
||||
columnIndex,
|
||||
this._instanceProps
|
||||
);
|
||||
const isRtl = direction === 'rtl';
|
||||
itemStyleCache[key] = style = {
|
||||
position: 'absolute',
|
||||
left: isRtl ? undefined : offset,
|
||||
right: isRtl ? offset : undefined,
|
||||
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,
|
||||
overscanColumnCount,
|
||||
overscanColumnsCount,
|
||||
overscanCount,
|
||||
rowCount,
|
||||
} = this.props;
|
||||
const { horizontalScrollDirection, isScrolling, scrollLeft } = this.state;
|
||||
|
||||
const overscanCountResolved: number =
|
||||
overscanColumnCount || 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,
|
||||
overscanRowCount,
|
||||
overscanRowsCount,
|
||||
rowCount,
|
||||
} = this.props;
|
||||
const { isScrolling, verticalScrollDirection, scrollTop } = this.state;
|
||||
|
||||
const overscanCountResolved: number =
|
||||
overscanRowCount || 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 {
|
||||
clientHeight,
|
||||
clientWidth,
|
||||
scrollLeft,
|
||||
scrollTop,
|
||||
scrollHeight,
|
||||
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;
|
||||
|
||||
// TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements.
|
||||
// This is not the case for all browsers though (e.g. Chrome reports values as positive, measured relative to the left).
|
||||
// It's also easier for this component if we convert offsets to the same format as they would be in for ltr.
|
||||
// So the simplest solution is to determine which browser behavior we're dealing with, and convert based on it.
|
||||
let calculatedScrollLeft = scrollLeft;
|
||||
if (direction === 'rtl') {
|
||||
switch (getRTLOffsetType()) {
|
||||
case 'negative':
|
||||
calculatedScrollLeft = -scrollLeft;
|
||||
break;
|
||||
case 'positive-descending':
|
||||
calculatedScrollLeft = scrollWidth - clientWidth - scrollLeft;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent Safari's elastic scrolling from causing visual shaking when scrolling past bounds.
|
||||
calculatedScrollLeft = Math.max(
|
||||
0,
|
||||
Math.min(calculatedScrollLeft, scrollWidth - clientWidth)
|
||||
);
|
||||
const calculatedScrollTop = Math.max(
|
||||
0,
|
||||
Math.min(scrollTop, scrollHeight - clientHeight)
|
||||
);
|
||||
|
||||
return {
|
||||
isScrolling: true,
|
||||
horizontalScrollDirection:
|
||||
prevState.scrollLeft < scrollLeft ? 'forward' : 'backward',
|
||||
scrollLeft: calculatedScrollLeft,
|
||||
scrollTop: calculatedScrollTop,
|
||||
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,
|
||||
overscanColumnsCount,
|
||||
overscanCount,
|
||||
overscanRowsCount,
|
||||
width,
|
||||
}: Props<any>,
|
||||
{ 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 overscanColumnCount and overscanRowCount props instead.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
typeof overscanColumnsCount === 'number' ||
|
||||
typeof overscanRowsCount === 'number'
|
||||
) {
|
||||
if (
|
||||
devWarningsOverscanRowsColumnsCount &&
|
||||
!devWarningsOverscanRowsColumnsCount.has(instance)
|
||||
) {
|
||||
devWarningsOverscanRowsColumnsCount.add(instance);
|
||||
console.warn(
|
||||
'The overscanColumnsCount and overscanRowsCount props have been deprecated. ' +
|
||||
'Please use the overscanColumnCount and overscanRowCount 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.`
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
745
node_modules/react-window/src/createListComponent.js
generated
vendored
Normal file
745
node_modules/react-window/src/createListComponent.js
generated
vendored
Normal file
@@ -0,0 +1,745 @@
|
||||
// @flow
|
||||
|
||||
import memoizeOne from 'memoize-one';
|
||||
import { createElement, PureComponent } from 'react';
|
||||
import { cancelTimeout, requestTimeout } from './timer';
|
||||
import { getScrollbarSize, getRTLOffsetType } from './domHelpers';
|
||||
|
||||
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<T> = {|
|
||||
data: T,
|
||||
index: number,
|
||||
isScrolling?: boolean,
|
||||
style: Object,
|
||||
|};
|
||||
type RenderComponent<T> = React$ComponentType<$Shape<RenderComponentProps<T>>>;
|
||||
|
||||
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<HTMLDivElement>;
|
||||
type ItemStyleCache = { [index: number]: Object };
|
||||
|
||||
type OuterProps = {|
|
||||
children: React$Node,
|
||||
className: string | void,
|
||||
onScroll: ScrollEvent => void,
|
||||
style: {
|
||||
[string]: mixed,
|
||||
},
|
||||
|};
|
||||
|
||||
type InnerProps = {|
|
||||
children: React$Node,
|
||||
style: {
|
||||
[string]: mixed,
|
||||
},
|
||||
|};
|
||||
|
||||
export type Props<T> = {|
|
||||
children: RenderComponent<T>,
|
||||
className?: string,
|
||||
direction: Direction,
|
||||
height: number | string,
|
||||
initialScrollOffset?: number,
|
||||
innerRef?: any,
|
||||
innerElementType?: string | React$AbstractComponent<InnerProps, any>,
|
||||
innerTagName?: string, // deprecated
|
||||
itemCount: number,
|
||||
itemData: T,
|
||||
itemKey?: (index: number, data: T) => any,
|
||||
itemSize: itemSize,
|
||||
layout: Layout,
|
||||
onItemsRendered?: onItemsRenderedCallback,
|
||||
onScroll?: onScrollCallback,
|
||||
outerRef?: any,
|
||||
outerElementType?: string | React$AbstractComponent<OuterProps, any>,
|
||||
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<any>,
|
||||
index: number,
|
||||
instanceProps: any
|
||||
) => number;
|
||||
type GetItemSize = (
|
||||
props: Props<any>,
|
||||
index: number,
|
||||
instanceProps: any
|
||||
) => number;
|
||||
type GetEstimatedTotalSize = (props: Props<any>, instanceProps: any) => number;
|
||||
type GetOffsetForIndexAndAlignment = (
|
||||
props: Props<any>,
|
||||
index: number,
|
||||
align: ScrollToAlign,
|
||||
scrollOffset: number,
|
||||
instanceProps: any
|
||||
) => number;
|
||||
type GetStartIndexForOffset = (
|
||||
props: Props<any>,
|
||||
offset: number,
|
||||
instanceProps: any
|
||||
) => number;
|
||||
type GetStopIndexForStartIndex = (
|
||||
props: Props<any>,
|
||||
startIndex: number,
|
||||
scrollOffset: number,
|
||||
instanceProps: any
|
||||
) => number;
|
||||
type InitInstanceProps = (props: Props<any>, instance: any) => any;
|
||||
type ValidateProps = (props: Props<any>) => 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<T> extends PureComponent<Props<T>, 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<T>) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(
|
||||
nextProps: Props<T>,
|
||||
prevState: State
|
||||
): $Shape<State> | 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, layout } = this.props;
|
||||
const { scrollOffset } = this.state;
|
||||
|
||||
index = Math.max(0, Math.min(index, itemCount - 1));
|
||||
|
||||
// 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.
|
||||
// This is an edge case for lists; normally they only scroll in the dominant direction.
|
||||
let scrollbarSize = 0;
|
||||
if (this._outerRef) {
|
||||
const outerRef = ((this._outerRef: any): HTMLElement);
|
||||
if (layout === 'vertical') {
|
||||
scrollbarSize =
|
||||
outerRef.scrollWidth > outerRef.clientWidth
|
||||
? getScrollbarSize()
|
||||
: 0;
|
||||
} else {
|
||||
scrollbarSize =
|
||||
outerRef.scrollHeight > outerRef.clientHeight
|
||||
? getScrollbarSize()
|
||||
: 0;
|
||||
}
|
||||
}
|
||||
|
||||
this.scrollTo(
|
||||
getOffsetForIndexAndAlignment(
|
||||
this.props,
|
||||
index,
|
||||
align,
|
||||
scrollOffset,
|
||||
this._instanceProps,
|
||||
scrollbarSize
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { direction, initialScrollOffset, layout } = this.props;
|
||||
|
||||
if (typeof initialScrollOffset === 'number' && this._outerRef != null) {
|
||||
const outerRef = ((this._outerRef: any): HTMLElement);
|
||||
// TODO Deprecate direction "horizontal"
|
||||
if (direction === 'horizontal' || layout === 'horizontal') {
|
||||
outerRef.scrollLeft = initialScrollOffset;
|
||||
} else {
|
||||
outerRef.scrollTop = initialScrollOffset;
|
||||
}
|
||||
}
|
||||
|
||||
this._callPropsCallbacks();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const { direction, layout } = this.props;
|
||||
const { scrollOffset, scrollUpdateWasRequested } = this.state;
|
||||
|
||||
if (scrollUpdateWasRequested && this._outerRef != null) {
|
||||
const outerRef = ((this._outerRef: any): HTMLElement);
|
||||
|
||||
// TODO Deprecate direction "horizontal"
|
||||
if (direction === 'horizontal' || layout === 'horizontal') {
|
||||
if (direction === 'rtl') {
|
||||
// TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements.
|
||||
// This is not the case for all browsers though (e.g. Chrome reports values as positive, measured relative to the left).
|
||||
// So we need to determine which browser behavior we're dealing with, and mimic it.
|
||||
switch (getRTLOffsetType()) {
|
||||
case 'negative':
|
||||
outerRef.scrollLeft = -scrollOffset;
|
||||
break;
|
||||
case 'positive-ascending':
|
||||
outerRef.scrollLeft = scrollOffset;
|
||||
break;
|
||||
default:
|
||||
const { clientWidth, scrollWidth } = outerRef;
|
||||
outerRef.scrollLeft = scrollWidth - clientWidth - scrollOffset;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
outerRef.scrollLeft = scrollOffset;
|
||||
}
|
||||
} else {
|
||||
outerRef.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' : undefined,
|
||||
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';
|
||||
|
||||
const isRtl = direction === 'rtl';
|
||||
const offsetHorizontal = isHorizontal ? offset : 0;
|
||||
itemStyleCache[index] = style = {
|
||||
position: 'absolute',
|
||||
left: isRtl ? undefined : offsetHorizontal,
|
||||
right: isRtl ? offsetHorizontal : undefined,
|
||||
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;
|
||||
|
||||
let scrollOffset = scrollLeft;
|
||||
if (direction === 'rtl') {
|
||||
// TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements.
|
||||
// This is not the case for all browsers though (e.g. Chrome reports values as positive, measured relative to the left).
|
||||
// It's also easier for this component if we convert offsets to the same format as they would be in for ltr.
|
||||
// So the simplest solution is to determine which browser behavior we're dealing with, and convert based on it.
|
||||
switch (getRTLOffsetType()) {
|
||||
case 'negative':
|
||||
scrollOffset = -scrollLeft;
|
||||
break;
|
||||
case 'positive-descending':
|
||||
scrollOffset = scrollWidth - clientWidth - scrollLeft;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent Safari's elastic scrolling from causing visual shaking when scrolling past bounds.
|
||||
scrollOffset = Math.max(
|
||||
0,
|
||||
Math.min(scrollOffset, scrollWidth - clientWidth)
|
||||
);
|
||||
|
||||
return {
|
||||
isScrolling: true,
|
||||
scrollDirection:
|
||||
prevState.scrollOffset < scrollOffset ? 'forward' : 'backward',
|
||||
scrollOffset,
|
||||
scrollUpdateWasRequested: false,
|
||||
};
|
||||
}, this._resetIsScrollingDebounced);
|
||||
};
|
||||
|
||||
_onScrollVertical = (event: ScrollEvent): void => {
|
||||
const { clientHeight, scrollHeight, 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;
|
||||
}
|
||||
|
||||
// Prevent Safari's elastic scrolling from causing visual shaking when scrolling past bounds.
|
||||
const scrollOffset = Math.max(
|
||||
0,
|
||||
Math.min(scrollTop, scrollHeight - clientHeight)
|
||||
);
|
||||
|
||||
return {
|
||||
isScrolling: true,
|
||||
scrollDirection:
|
||||
prevState.scrollOffset < scrollOffset ? 'forward' : 'backward',
|
||||
scrollOffset,
|
||||
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<any>,
|
||||
{ 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.`
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
72
node_modules/react-window/src/domHelpers.js
generated
vendored
Normal file
72
node_modules/react-window/src/domHelpers.js
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
// @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;
|
||||
}
|
||||
|
||||
export type RTLOffsetType =
|
||||
| 'negative'
|
||||
| 'positive-descending'
|
||||
| 'positive-ascending';
|
||||
|
||||
let cachedRTLResult: RTLOffsetType | null = null;
|
||||
|
||||
// TRICKY 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).
|
||||
// Safari's elastic bounce makes detecting this even more complicated wrt potential false positives.
|
||||
// The safest way to check this is to intentionally set a negative offset,
|
||||
// and then verify that the subsequent "scroll" event matches the negative offset.
|
||||
// If it does not match, then we can assume a non-standard RTL scroll implementation.
|
||||
export function getRTLOffsetType(recalculate?: boolean = false): RTLOffsetType {
|
||||
if (cachedRTLResult === null || recalculate) {
|
||||
const outerDiv = document.createElement('div');
|
||||
const outerStyle = outerDiv.style;
|
||||
outerStyle.width = '50px';
|
||||
outerStyle.height = '50px';
|
||||
outerStyle.overflow = 'scroll';
|
||||
outerStyle.direction = 'rtl';
|
||||
|
||||
const innerDiv = document.createElement('div');
|
||||
const innerStyle = innerDiv.style;
|
||||
innerStyle.width = '100px';
|
||||
innerStyle.height = '100px';
|
||||
|
||||
outerDiv.appendChild(innerDiv);
|
||||
|
||||
((document.body: any): HTMLBodyElement).appendChild(outerDiv);
|
||||
|
||||
if (outerDiv.scrollLeft > 0) {
|
||||
cachedRTLResult = 'positive-descending';
|
||||
} else {
|
||||
outerDiv.scrollLeft = 1;
|
||||
if (outerDiv.scrollLeft === 0) {
|
||||
cachedRTLResult = 'negative';
|
||||
} else {
|
||||
cachedRTLResult = 'positive-ascending';
|
||||
}
|
||||
}
|
||||
|
||||
((document.body: any): HTMLBodyElement).removeChild(outerDiv);
|
||||
|
||||
return cachedRTLResult;
|
||||
}
|
||||
|
||||
return cachedRTLResult;
|
||||
}
|
||||
9
node_modules/react-window/src/index.js
generated
vendored
Normal file
9
node_modules/react-window/src/index.js
generated
vendored
Normal file
@@ -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';
|
||||
17
node_modules/react-window/src/shallowDiffers.js
generated
vendored
Normal file
17
node_modules/react-window/src/shallowDiffers.js
generated
vendored
Normal file
@@ -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;
|
||||
}
|
||||
16
node_modules/react-window/src/shouldComponentUpdate.js
generated
vendored
Normal file
16
node_modules/react-window/src/shouldComponentUpdate.js
generated
vendored
Normal file
@@ -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)
|
||||
);
|
||||
}
|
||||
37
node_modules/react-window/src/timer.js
generated
vendored
Normal file
37
node_modules/react-window/src/timer.js
generated
vendored
Normal file
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user