Merge master
This commit is contained in:
@@ -1,6 +1,22 @@
|
||||
Changelog
|
||||
------------
|
||||
|
||||
### 1.6.2
|
||||
* 🐛 Bugfix for RTL when scrolling back towards the beginning (right) of the list.
|
||||
|
||||
### 1.6.1
|
||||
* 🐛 Bugfix to account for differences between Chrome and non-Chrome browsers with regard to RTL and "scroll" events.
|
||||
|
||||
### 1.6.0
|
||||
* 🎉 RTL support added for lists and grids. Special thanks to [davidgarsan](https://github.com/davidgarsan) for his support. - [#156](https://github.com/bvaughn/react-window/pull/156)
|
||||
* 🐛 Grid `scrollToItem` methods take scrollbar size into account when aligning items - [#153](https://github.com/bvaughn/react-window/issues/153)
|
||||
|
||||
### 1.5.2
|
||||
* 🐛 Edge case bug fix for `VariableSizeList` and `VariableSizeGrid` when the number of items decreases while a scroll is in progress. - ([iamsolankiamit](https://github.com/iamsolankiamit) - [#138](https://github.com/bvaughn/react-window/pull/138))
|
||||
|
||||
### 1.5.1
|
||||
* 🐛 Updated `getDerivedState` Flow annotations to address a warning in a newer version of Flow.
|
||||
|
||||
### 1.5.0
|
||||
* 🎉 Added advanced memoization helpers methods `areEqual` and `shouldComponentUpdate` for item renderers. - [#114](https://github.com/bvaughn/react-window/issues/114)
|
||||
|
||||
|
||||
+21
@@ -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.
|
||||
@@ -16,11 +16,16 @@ npm install --save react-window
|
||||
|
||||
## Usage
|
||||
|
||||
Learn more at [react-window.now.sh](https://react-window.now.sh/).
|
||||
Learn more at [react-window.now.sh](https://react-window.now.sh/):
|
||||
|
||||
## Related libraries
|
||||
|
||||
* [`react-virtualized-auto-sizer`](https://npmjs.com/package/react-virtualized-auto-sizer): HOC that grows to fit all of the available space and passes the width and height values to its child.
|
||||
* [`react-window-infinite-loader`](https://npmjs.com/package/react-window-infinite-loader): Helps break large data sets down into chunks that can be just-in-time loaded as they are scrolled into view. It can also be used to create infinite loading lists (e.g. Facebook or Twitter).
|
||||
|
||||
## Frequently asked questions
|
||||
|
||||
#### How is `react-window` different from `react-virtualized`?
|
||||
### 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).
|
||||
@@ -32,6 +37,40 @@ If `react-window` provides the functionality your project needs, I would strongl
|
||||
|
||||
<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).
|
||||
|
||||
## License
|
||||
|
||||
MIT © [bvaughn](https://github.com/bvaughn)
|
||||
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-window",
|
||||
"version": "1.6.0-alpha.1",
|
||||
"version": "1.7.0-alpha.1",
|
||||
"description":
|
||||
"React components for efficiently rendering large, scrollable lists and tabular data",
|
||||
"author":
|
||||
@@ -61,7 +61,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.0.0",
|
||||
"memoize-one": "^3.1.1"
|
||||
"memoize-one": ">=3.1.1 <6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^15.0.0 || ^16.0.0",
|
||||
@@ -99,9 +99,9 @@
|
||||
"react-dom": "^16.7.0",
|
||||
"react-scripts": "^1.1.1",
|
||||
"react-test-renderer": "^16.7.0",
|
||||
"rollup": "^0.65.0",
|
||||
"rollup-plugin-babel": "^4.0.2",
|
||||
"rollup-plugin-commonjs": "^8.2.1",
|
||||
"rollup-plugin-node-resolve": "^3.0.2"
|
||||
"rollup": "^1.4.1",
|
||||
"rollup-plugin-babel": "^4.3.2",
|
||||
"rollup-plugin-commonjs": "^9.2.1",
|
||||
"rollup-plugin-node-resolve": "^4.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import babel from 'rollup-plugin-babel';
|
||||
import commonjs from 'rollup-plugin-commonjs';
|
||||
import nodeResolve from 'rollup-plugin-node-resolve';
|
||||
|
||||
import pkg from './package.json';
|
||||
|
||||
const input = './src/index.js';
|
||||
|
||||
+21
-8
@@ -152,7 +152,7 @@ const DynamicSizeList = createListComponent({
|
||||
scrollOffset: number,
|
||||
instanceProps: InstanceProps
|
||||
): number => {
|
||||
const { direction, height, width } = props;
|
||||
const { direction, layout, height, width } = props;
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
const { lastMeasuredIndex } = instanceProps;
|
||||
@@ -164,7 +164,9 @@ const DynamicSizeList = createListComponent({
|
||||
}
|
||||
}
|
||||
|
||||
const size = (((direction === 'horizontal' ? width : height): any): number);
|
||||
const size = (((direction === 'horizontal' || layout === 'horizontal'
|
||||
? width
|
||||
: height): any): number);
|
||||
const itemMetadata = getItemMetadata(props, index, instanceProps);
|
||||
|
||||
// Get estimated total size after ItemMetadata is computed,
|
||||
@@ -225,9 +227,11 @@ const DynamicSizeList = createListComponent({
|
||||
scrollOffset: number,
|
||||
instanceProps: InstanceProps
|
||||
): number => {
|
||||
const { direction, height, itemCount, width } = props;
|
||||
const { direction, layout, height, itemCount, width } = props;
|
||||
|
||||
const size = (((direction === 'horizontal' ? width : height): any): number);
|
||||
const size = (((direction === 'horizontal' || layout === 'horizontal'
|
||||
? width
|
||||
: height): any): number);
|
||||
const itemMetadata = getItemMetadata(props, startIndex, instanceProps);
|
||||
const maxOffset = scrollOffset + size;
|
||||
|
||||
@@ -325,7 +329,7 @@ const DynamicSizeList = createListComponent({
|
||||
instance.forceUpdate();
|
||||
} else {
|
||||
const { scrollOffset } = instance.state;
|
||||
const { direction } = instance.props;
|
||||
const { direction, layout } = instance.props;
|
||||
|
||||
// Adjusting scroll offset directly interrupts smooth scrolling for some browsers (e.g. Firefox).
|
||||
// The relative scrollBy() method doesn't interrupt (or at least it won't as of Firefox v65).
|
||||
@@ -335,10 +339,17 @@ const DynamicSizeList = createListComponent({
|
||||
// $FlowFixMe Property scrollBy is missing in HTMLDivElement
|
||||
if (typeof element.scrollBy === 'function') {
|
||||
element.scrollBy(
|
||||
direction === 'horizontal' ? sizeDeltaForStateUpdate : 0,
|
||||
direction === 'horizontal' ? 0 : sizeDeltaForStateUpdate
|
||||
direction === 'horizontal' || layout === 'horizontal'
|
||||
? sizeDeltaForStateUpdate
|
||||
: 0,
|
||||
direction === 'horizontal' || layout === 'horizontal'
|
||||
? 0
|
||||
: sizeDeltaForStateUpdate
|
||||
);
|
||||
} else if (direction === 'horizontal') {
|
||||
} else if (
|
||||
direction === 'horizontal' ||
|
||||
layout === 'horizontal'
|
||||
) {
|
||||
element.scrollLeft = scrollOffset;
|
||||
} else {
|
||||
element.scrollTop = scrollOffset;
|
||||
@@ -418,6 +429,7 @@ const DynamicSizeList = createListComponent({
|
||||
const {
|
||||
children,
|
||||
direction,
|
||||
layout,
|
||||
itemCount,
|
||||
itemData,
|
||||
itemKey = defaultItemKey,
|
||||
@@ -451,6 +463,7 @@ const DynamicSizeList = createListComponent({
|
||||
items.push(
|
||||
createElement(ItemMeasurer, {
|
||||
direction,
|
||||
layout,
|
||||
handleNewMeasurements,
|
||||
index,
|
||||
item,
|
||||
|
||||
@@ -27,7 +27,9 @@ const FixedSizeGrid = createGridComponent({
|
||||
{ columnCount, columnWidth, width }: Props<any>,
|
||||
columnIndex: number,
|
||||
align: ScrollToAlign,
|
||||
scrollLeft: number
|
||||
scrollLeft: number,
|
||||
instanceProps: typeof undefined,
|
||||
scrollbarSize: number
|
||||
): number => {
|
||||
const maxOffset = Math.max(
|
||||
0,
|
||||
@@ -40,6 +42,7 @@ const FixedSizeGrid = createGridComponent({
|
||||
0,
|
||||
columnIndex * ((columnWidth: any): number) -
|
||||
width +
|
||||
scrollbarSize +
|
||||
((columnWidth: any): number)
|
||||
);
|
||||
|
||||
@@ -66,7 +69,9 @@ const FixedSizeGrid = createGridComponent({
|
||||
{ rowHeight, height, rowCount }: Props<any>,
|
||||
rowIndex: number,
|
||||
align: ScrollToAlign,
|
||||
scrollTop: number
|
||||
scrollTop: number,
|
||||
instanceProps: typeof undefined,
|
||||
scrollbarSize: number
|
||||
): number => {
|
||||
const maxOffset = Math.max(
|
||||
0,
|
||||
@@ -79,6 +84,7 @@ const FixedSizeGrid = createGridComponent({
|
||||
0,
|
||||
rowIndex * ((rowHeight: any): number) -
|
||||
height +
|
||||
scrollbarSize +
|
||||
((rowHeight: any): number)
|
||||
);
|
||||
|
||||
|
||||
@@ -15,12 +15,14 @@ const FixedSizeList = createListComponent({
|
||||
((itemSize: any): number) * itemCount,
|
||||
|
||||
getOffsetForIndexAndAlignment: (
|
||||
{ direction, height, itemCount, itemSize, width }: Props<any>,
|
||||
{ direction, height, itemCount, itemSize, layout, width }: Props<any>,
|
||||
index: number,
|
||||
align: ScrollToAlign,
|
||||
scrollOffset: number
|
||||
): number => {
|
||||
const size = (((direction === 'horizontal' ? width : height): any): number);
|
||||
// TODO Deprecate direction "horizontal"
|
||||
const isHorizontal = direction === 'horizontal' || layout === 'horizontal';
|
||||
const size = (((isHorizontal ? width : height): any): number);
|
||||
const maxOffset = Math.max(
|
||||
0,
|
||||
Math.min(
|
||||
@@ -62,12 +64,14 @@ const FixedSizeList = createListComponent({
|
||||
),
|
||||
|
||||
getStopIndexForStartIndex: (
|
||||
{ direction, height, itemCount, itemSize, width }: Props<any>,
|
||||
{ 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 = (((direction === 'horizontal' ? width : height): any): number);
|
||||
const size = (((isHorizontal ? width : height): any): number);
|
||||
return Math.max(
|
||||
0,
|
||||
Math.min(
|
||||
|
||||
+4
-2
@@ -3,7 +3,7 @@
|
||||
import { cloneElement, Component } from 'react';
|
||||
import { findDOMNode } from 'react-dom';
|
||||
|
||||
import type { Direction } from './createListComponent';
|
||||
import type { Direction, Layout } from './createListComponent';
|
||||
import type { HandleNewMeasurements } from './DynamicSizeList';
|
||||
|
||||
class DOMRectReadOnly {
|
||||
@@ -38,6 +38,7 @@ declare class ResizeObserver {
|
||||
|
||||
type ItemMeasurerProps = {|
|
||||
direction: Direction,
|
||||
layout: Layout,
|
||||
handleNewMeasurements: HandleNewMeasurements,
|
||||
index: number,
|
||||
item: React$Element<any>,
|
||||
@@ -107,6 +108,7 @@ export default class ItemMeasurer extends Component<ItemMeasurerProps, void> {
|
||||
_measureItem = (isCommitPhase: boolean) => {
|
||||
const {
|
||||
direction,
|
||||
layout,
|
||||
handleNewMeasurements,
|
||||
index,
|
||||
size: oldSize,
|
||||
@@ -121,7 +123,7 @@ export default class ItemMeasurer extends Component<ItemMeasurerProps, void> {
|
||||
node instanceof node.ownerDocument.defaultView.HTMLElement
|
||||
) {
|
||||
const newSize =
|
||||
direction === 'horizontal'
|
||||
direction === 'horizontal' || layout === 'horizontal'
|
||||
? Math.ceil(node.offsetWidth)
|
||||
: Math.ceil(node.offsetHeight);
|
||||
|
||||
|
||||
+26
-6
@@ -35,6 +35,12 @@ const getEstimatedTotalHeight = (
|
||||
) => {
|
||||
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;
|
||||
@@ -56,6 +62,12 @@ const getEstimatedTotalWidth = (
|
||||
) => {
|
||||
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;
|
||||
@@ -221,7 +233,8 @@ const getOffsetForIndexAndAlignment = (
|
||||
index: number,
|
||||
align: ScrollToAlign,
|
||||
scrollOffset: number,
|
||||
instanceProps: InstanceProps
|
||||
instanceProps: InstanceProps,
|
||||
scrollbarSize: number
|
||||
): number => {
|
||||
const size = itemType === 'column' ? props.width : props.height;
|
||||
const itemMetadata = getItemMetadata(itemType, props, index, instanceProps);
|
||||
@@ -237,7 +250,10 @@ const getOffsetForIndexAndAlignment = (
|
||||
0,
|
||||
Math.min(estimatedTotalSize - size, itemMetadata.offset)
|
||||
);
|
||||
const minOffset = Math.max(0, itemMetadata.offset - size + itemMetadata.size);
|
||||
const minOffset = Math.max(
|
||||
0,
|
||||
itemMetadata.offset - size + scrollbarSize + itemMetadata.size
|
||||
);
|
||||
|
||||
switch (align) {
|
||||
case 'start':
|
||||
@@ -312,7 +328,8 @@ const VariableSizeGrid = createGridComponent({
|
||||
index: number,
|
||||
align: ScrollToAlign,
|
||||
scrollOffset: number,
|
||||
instanceProps: InstanceProps
|
||||
instanceProps: InstanceProps,
|
||||
scrollbarSize: number
|
||||
): number =>
|
||||
getOffsetForIndexAndAlignment(
|
||||
'column',
|
||||
@@ -320,7 +337,8 @@ const VariableSizeGrid = createGridComponent({
|
||||
index,
|
||||
align,
|
||||
scrollOffset,
|
||||
instanceProps
|
||||
instanceProps,
|
||||
scrollbarSize
|
||||
),
|
||||
|
||||
getOffsetForRowAndAlignment: (
|
||||
@@ -328,7 +346,8 @@ const VariableSizeGrid = createGridComponent({
|
||||
index: number,
|
||||
align: ScrollToAlign,
|
||||
scrollOffset: number,
|
||||
instanceProps: InstanceProps
|
||||
instanceProps: InstanceProps,
|
||||
scrollbarSize: number
|
||||
): number =>
|
||||
getOffsetForIndexAndAlignment(
|
||||
'row',
|
||||
@@ -336,7 +355,8 @@ const VariableSizeGrid = createGridComponent({
|
||||
index,
|
||||
align,
|
||||
scrollOffset,
|
||||
instanceProps
|
||||
instanceProps,
|
||||
scrollbarSize
|
||||
),
|
||||
|
||||
getRowOffset: (
|
||||
|
||||
+14
-4
@@ -146,6 +146,12 @@ const getEstimatedTotalSize = (
|
||||
) => {
|
||||
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;
|
||||
@@ -179,9 +185,11 @@ const VariableSizeList = createListComponent({
|
||||
scrollOffset: number,
|
||||
instanceProps: InstanceProps
|
||||
): number => {
|
||||
const { direction, height, width } = props;
|
||||
const { direction, height, layout, width } = props;
|
||||
|
||||
const size = (((direction === 'horizontal' ? width : height): any): number);
|
||||
// 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,
|
||||
@@ -228,9 +236,11 @@ const VariableSizeList = createListComponent({
|
||||
scrollOffset: number,
|
||||
instanceProps: InstanceProps
|
||||
): number => {
|
||||
const { direction, height, itemCount, width } = props;
|
||||
const { direction, height, itemCount, layout, width } = props;
|
||||
|
||||
const size = (((direction === 'horizontal' ? width : height): any): number);
|
||||
// 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;
|
||||
|
||||
|
||||
+180
-11
@@ -3,6 +3,7 @@ import ReactDOM from 'react-dom';
|
||||
import ReactTestRenderer from 'react-test-renderer';
|
||||
import ReactTestUtils from 'react-dom/test-utils';
|
||||
import { FixedSizeGrid } from '..';
|
||||
import * as domHelpers from '../domHelpers';
|
||||
|
||||
const findScrollContainer = rendered => rendered.root.children[0].children[0];
|
||||
|
||||
@@ -13,7 +14,7 @@ const simulateScroll = (instance, { scrollLeft, scrollTop }) => {
|
||||
};
|
||||
|
||||
describe('FixedSizeGrid', () => {
|
||||
let itemRenderer, defaultProps, onItemsRendered;
|
||||
let defaultProps, getScrollbarSize, itemRenderer, onItemsRendered;
|
||||
|
||||
// Use PureComponent to test memoization.
|
||||
// Pass through to itemRenderer mock for easier test assertions.
|
||||
@@ -26,6 +27,9 @@ describe('FixedSizeGrid', () => {
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
// Mock the DOM helper util for testing purposes.
|
||||
getScrollbarSize = domHelpers.getScrollbarSize = jest.fn(() => 0);
|
||||
|
||||
onItemsRendered = jest.fn();
|
||||
|
||||
itemRenderer = jest.fn(({ style, ...rest }) => (
|
||||
@@ -172,6 +176,41 @@ describe('FixedSizeGrid', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('direction', () => {
|
||||
it('should set the appropriate CSS direction style', () => {
|
||||
const renderer = ReactTestRenderer.create(
|
||||
<FixedSizeGrid {...defaultProps} direction="ltr" />
|
||||
);
|
||||
expect(renderer.toJSON().props.style.direction).toBe('ltr');
|
||||
renderer.update(<FixedSizeGrid {...defaultProps} direction="rtl" />);
|
||||
expect(renderer.toJSON().props.style.direction).toBe('rtl');
|
||||
});
|
||||
|
||||
it('should position items correctly', () => {
|
||||
const renderer = ReactTestRenderer.create(
|
||||
<FixedSizeGrid {...defaultProps} direction="ltr" />
|
||||
);
|
||||
|
||||
let params = itemRenderer.mock.calls[0][0];
|
||||
expect(params.columnIndex).toBe(0);
|
||||
expect(params.rowIndex).toBe(0);
|
||||
let style = params.style;
|
||||
expect(style.left).toBe(0);
|
||||
expect(style.right).toBeUndefined();
|
||||
|
||||
itemRenderer.mockClear();
|
||||
|
||||
renderer.update(<FixedSizeGrid {...defaultProps} direction="rtl" />);
|
||||
|
||||
params = itemRenderer.mock.calls[0][0];
|
||||
expect(params.columnIndex).toBe(0);
|
||||
expect(params.rowIndex).toBe(0);
|
||||
style = params.style;
|
||||
expect(style.left).toBeUndefined();
|
||||
expect(style.right).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('overscanColumnsCount and overscanRowsCount', () => {
|
||||
it('should require a minimum of 1 overscan to support tabbing', () => {
|
||||
ReactTestRenderer.create(
|
||||
@@ -248,7 +287,8 @@ describe('FixedSizeGrid', () => {
|
||||
describe('overscanCount', () => {
|
||||
it('should warn about deprecated overscanCount prop', () => {
|
||||
spyOn(console, 'warn');
|
||||
ReactTestRenderer.create(
|
||||
|
||||
const renderer = ReactTestRenderer.create(
|
||||
<FixedSizeGrid {...defaultProps} overscanCount={1} />
|
||||
);
|
||||
expect(console.warn).toHaveBeenCalledTimes(1);
|
||||
@@ -256,10 +296,16 @@ describe('FixedSizeGrid', () => {
|
||||
'The overscanCount prop has been deprecated. ' +
|
||||
'Please use the overscanColumnsCount and overscanRowsCount props instead.'
|
||||
);
|
||||
|
||||
renderer.update(<FixedSizeGrid {...defaultProps} overscanCount={1} />);
|
||||
|
||||
// But it should only warn once.
|
||||
expect(console.warn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should use overscanColumnsCount if both it and overscanCount are provided', () => {
|
||||
spyOn(console, 'warn');
|
||||
|
||||
ReactTestRenderer.create(
|
||||
<FixedSizeGrid
|
||||
{...defaultProps}
|
||||
@@ -274,6 +320,7 @@ describe('FixedSizeGrid', () => {
|
||||
|
||||
it('should use overscanRowsCount if both it and overscanCount are provided', () => {
|
||||
spyOn(console, 'warn');
|
||||
|
||||
ReactTestRenderer.create(
|
||||
<FixedSizeGrid
|
||||
{...defaultProps}
|
||||
@@ -288,6 +335,7 @@ describe('FixedSizeGrid', () => {
|
||||
|
||||
it('should support deprecated overscanCount', () => {
|
||||
spyOn(console, 'warn');
|
||||
|
||||
ReactTestRenderer.create(
|
||||
<FixedSizeGrid
|
||||
{...defaultProps}
|
||||
@@ -512,6 +560,109 @@ describe('FixedSizeGrid', () => {
|
||||
instance.scrollToItem({ columnIndex: 15, rowIndex: 20 });
|
||||
expect(itemRenderer.mock.calls[0][0].isScrolling).toBe(false);
|
||||
});
|
||||
|
||||
it('should account for scrollbar size', () => {
|
||||
const onScroll = jest.fn();
|
||||
const rendered = ReactTestRenderer.create(
|
||||
<FixedSizeGrid
|
||||
{...defaultProps}
|
||||
columnWidth={100}
|
||||
height={150}
|
||||
rowHeight={25}
|
||||
width={300}
|
||||
onScroll={onScroll}
|
||||
/>
|
||||
);
|
||||
|
||||
onScroll.mockClear();
|
||||
rendered
|
||||
.getInstance()
|
||||
.scrollToItem({ columnIndex: 15, rowIndex: 10, align: 'end' });
|
||||
|
||||
// With hidden scrollbars (size === 0) we would expect...
|
||||
expect(onScroll).toHaveBeenCalledWith({
|
||||
horizontalScrollDirection: 'forward',
|
||||
scrollLeft: 1300,
|
||||
scrollTop: 125,
|
||||
scrollUpdateWasRequested: true,
|
||||
verticalScrollDirection: 'forward',
|
||||
});
|
||||
|
||||
getScrollbarSize.mockImplementation(() => 20);
|
||||
|
||||
onScroll.mockClear();
|
||||
rendered
|
||||
.getInstance()
|
||||
.scrollToItem({ columnIndex: 15, rowIndex: 10, align: 'end' });
|
||||
|
||||
// With scrollbars of size 20 we would expect those values ot increase by 20px
|
||||
expect(onScroll).toHaveBeenCalledWith({
|
||||
horizontalScrollDirection: 'forward',
|
||||
scrollLeft: 1320,
|
||||
scrollTop: 145,
|
||||
scrollUpdateWasRequested: true,
|
||||
verticalScrollDirection: 'forward',
|
||||
});
|
||||
});
|
||||
|
||||
it('should not account for scrollbar size when no scrollbar is visible for a particular direction', () => {
|
||||
getScrollbarSize.mockImplementation(() => 20);
|
||||
|
||||
const onScroll = jest.fn();
|
||||
const rendered = ReactTestRenderer.create(
|
||||
<FixedSizeGrid
|
||||
{...defaultProps}
|
||||
columnCount={2}
|
||||
columnWidth={100}
|
||||
height={150}
|
||||
rowHeight={25}
|
||||
width={300}
|
||||
onScroll={onScroll}
|
||||
/>
|
||||
);
|
||||
|
||||
onScroll.mockClear();
|
||||
rendered
|
||||
.getInstance()
|
||||
.scrollToItem({ columnIndex: 0, rowIndex: 10, align: 'end' });
|
||||
|
||||
// Since there aren't enough columns to require horizontal scrolling,
|
||||
// the additional 20px for the scrollbar should not be taken into consideration.
|
||||
expect(onScroll).toHaveBeenCalledWith({
|
||||
horizontalScrollDirection: 'backward',
|
||||
scrollLeft: 0,
|
||||
scrollTop: 125,
|
||||
scrollUpdateWasRequested: true,
|
||||
verticalScrollDirection: 'forward',
|
||||
});
|
||||
|
||||
rendered.update(
|
||||
<FixedSizeGrid
|
||||
{...defaultProps}
|
||||
columnWidth={100}
|
||||
height={150}
|
||||
rowCount={4}
|
||||
rowHeight={25}
|
||||
width={300}
|
||||
onScroll={onScroll}
|
||||
/>
|
||||
);
|
||||
|
||||
onScroll.mockClear();
|
||||
rendered
|
||||
.getInstance()
|
||||
.scrollToItem({ columnIndex: 15, rowIndex: 0, align: 'end' });
|
||||
|
||||
// Since there aren't enough rows to require vertical scrolling,
|
||||
// the additional 20px for the scrollbar should not be taken into consideration.
|
||||
expect(onScroll).toHaveBeenCalledWith({
|
||||
horizontalScrollDirection: 'forward',
|
||||
scrollLeft: 1300,
|
||||
scrollTop: 0,
|
||||
scrollUpdateWasRequested: true,
|
||||
verticalScrollDirection: 'backward',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// onItemsRendered is pretty well covered by other snapshot tests
|
||||
@@ -721,19 +872,29 @@ describe('FixedSizeGrid', () => {
|
||||
|
||||
it('should warn if legacy innerTagName or outerTagName props are used', () => {
|
||||
spyOn(console, 'warn');
|
||||
ReactDOM.render(
|
||||
const renderer = ReactTestRenderer.create(
|
||||
<FixedSizeGrid
|
||||
{...defaultProps}
|
||||
innerTagName="div"
|
||||
outerTagName="div"
|
||||
/>,
|
||||
document.createElement('div')
|
||||
/>
|
||||
);
|
||||
expect(console.warn).toHaveBeenCalledTimes(1);
|
||||
expect(console.warn).toHaveBeenLastCalledWith(
|
||||
'The innerTagName and outerTagName props have been deprecated. ' +
|
||||
'Please use the innerElementType and outerElementType props instead.'
|
||||
);
|
||||
|
||||
renderer.update(
|
||||
<FixedSizeGrid
|
||||
{...defaultProps}
|
||||
innerTagName="div"
|
||||
outerTagName="div"
|
||||
/>
|
||||
);
|
||||
|
||||
// But it should only warn once.
|
||||
expect(console.warn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -814,10 +975,22 @@ describe('FixedSizeGrid', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should fail if an invalid direction is provided', () => {
|
||||
expect(() =>
|
||||
ReactTestRenderer.create(
|
||||
<FixedSizeGrid {...defaultProps} direction={null} />
|
||||
)
|
||||
).toThrow(
|
||||
'An invalid "direction" prop has been specified. ' +
|
||||
'Value should be either "ltr" or "rtl". ' +
|
||||
'"null" was specified.'
|
||||
);
|
||||
});
|
||||
|
||||
it('should fail if a string height is provided', () => {
|
||||
expect(() =>
|
||||
ReactTestRenderer.create(
|
||||
<FixedSizeGrid {...defaultProps} direction="vertical" height="100%" />
|
||||
<FixedSizeGrid {...defaultProps} height="100%" />
|
||||
)
|
||||
).toThrow(
|
||||
'An invalid "height" prop has been specified. ' +
|
||||
@@ -829,11 +1002,7 @@ describe('FixedSizeGrid', () => {
|
||||
it('should fail if a string width is provided', () => {
|
||||
expect(() =>
|
||||
ReactTestRenderer.create(
|
||||
<FixedSizeGrid
|
||||
{...defaultProps}
|
||||
direction="horizontal"
|
||||
width="100%"
|
||||
/>
|
||||
<FixedSizeGrid {...defaultProps} width="100%" />
|
||||
)
|
||||
).toThrow(
|
||||
'An invalid "width" prop has been specified. ' +
|
||||
|
||||
+122
-13
@@ -58,13 +58,32 @@ describe('FixedSizeList', () => {
|
||||
|
||||
it('should render a list of columns', () => {
|
||||
ReactTestRenderer.create(
|
||||
<FixedSizeList {...defaultProps} direction="horizontal" />
|
||||
<FixedSizeList {...defaultProps} layout="horizontal" />
|
||||
);
|
||||
expect(itemRenderer).toHaveBeenCalledTimes(5);
|
||||
expect(onItemsRendered.mock.calls).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should re-render items if layout changes', () => {
|
||||
const rendered = ReactTestRenderer.create(
|
||||
<FixedSizeList {...defaultProps} layout="vertical" />
|
||||
);
|
||||
expect(itemRenderer).toHaveBeenCalled();
|
||||
itemRenderer.mockClear();
|
||||
|
||||
// Re-rendering should not affect pure sCU children:
|
||||
rendered.update(<FixedSizeList {...defaultProps} layout="vertical" />);
|
||||
expect(itemRenderer).not.toHaveBeenCalled();
|
||||
|
||||
// Re-rendering with new layout should re-render children:
|
||||
rendered.update(<FixedSizeList {...defaultProps} layout="horizontal" />);
|
||||
expect(itemRenderer).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// TODO Deprecate direction "horizontal"
|
||||
it('should re-render items if direction changes', () => {
|
||||
spyOn(console, 'warn'); // Ingore legacy prop warning
|
||||
|
||||
const rendered = ReactTestRenderer.create(
|
||||
<FixedSizeList {...defaultProps} direction="vertical" />
|
||||
);
|
||||
@@ -75,7 +94,7 @@ describe('FixedSizeList', () => {
|
||||
rendered.update(<FixedSizeList {...defaultProps} direction="vertical" />);
|
||||
expect(itemRenderer).not.toHaveBeenCalled();
|
||||
|
||||
// Re-rendering with new direction should re-render children:
|
||||
// Re-rendering with new layout should re-render children:
|
||||
rendered.update(<FixedSizeList {...defaultProps} direction="horizontal" />);
|
||||
expect(itemRenderer).toHaveBeenCalled();
|
||||
});
|
||||
@@ -97,7 +116,7 @@ describe('FixedSizeList', () => {
|
||||
ReactDOM.render(
|
||||
<FixedSizeList
|
||||
{...defaultProps}
|
||||
direction="horizontal"
|
||||
layout="horizontal"
|
||||
innerRef={innerRef}
|
||||
/>,
|
||||
document.createElement('div')
|
||||
@@ -202,6 +221,39 @@ describe('FixedSizeList', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('direction', () => {
|
||||
it('should set the appropriate CSS direction style', () => {
|
||||
const renderer = ReactTestRenderer.create(
|
||||
<FixedSizeList {...defaultProps} direction="ltr" />
|
||||
);
|
||||
expect(renderer.toJSON().props.style.direction).toBe('ltr');
|
||||
renderer.update(<FixedSizeList {...defaultProps} direction="rtl" />);
|
||||
expect(renderer.toJSON().props.style.direction).toBe('rtl');
|
||||
});
|
||||
|
||||
it('should position items correctly', () => {
|
||||
const renderer = ReactTestRenderer.create(
|
||||
<FixedSizeList {...defaultProps} direction="ltr" />
|
||||
);
|
||||
|
||||
let params = itemRenderer.mock.calls[0][0];
|
||||
expect(params.index).toBe(0);
|
||||
let style = params.style;
|
||||
expect(style.left).toBe(0);
|
||||
expect(style.right).toBeUndefined();
|
||||
|
||||
itemRenderer.mockClear();
|
||||
|
||||
renderer.update(<FixedSizeList {...defaultProps} direction="rtl" />);
|
||||
|
||||
params = itemRenderer.mock.calls[0][0];
|
||||
expect(params.index).toBe(0);
|
||||
style = params.style;
|
||||
expect(style.left).toBeUndefined();
|
||||
expect(style.right).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('overscanCount', () => {
|
||||
it('should require a minimum of 1 overscan to support tabbing', () => {
|
||||
ReactTestRenderer.create(
|
||||
@@ -583,19 +635,68 @@ describe('FixedSizeList', () => {
|
||||
|
||||
it('should warn if legacy innerTagName or outerTagName props are used', () => {
|
||||
spyOn(console, 'warn');
|
||||
ReactDOM.render(
|
||||
|
||||
const renderer = ReactTestRenderer.create(
|
||||
<FixedSizeList
|
||||
{...defaultProps}
|
||||
innerTagName="div"
|
||||
outerTagName="div"
|
||||
/>,
|
||||
document.createElement('div')
|
||||
/>
|
||||
);
|
||||
expect(console.warn).toHaveBeenCalledTimes(1);
|
||||
expect(console.warn).toHaveBeenLastCalledWith(
|
||||
'The innerTagName and outerTagName props have been deprecated. ' +
|
||||
'Please use the innerElementType and outerElementType props instead.'
|
||||
);
|
||||
|
||||
renderer.update(
|
||||
<FixedSizeList
|
||||
{...defaultProps}
|
||||
innerTagName="div"
|
||||
outerTagName="div"
|
||||
/>
|
||||
);
|
||||
|
||||
// But it should only warn once.
|
||||
expect(console.warn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should warn if legacy direction "horizontal" value is used', () => {
|
||||
spyOn(console, 'warn');
|
||||
|
||||
const renderer = ReactTestRenderer.create(
|
||||
<FixedSizeList {...defaultProps} direction="horizontal" />
|
||||
);
|
||||
expect(console.warn).toHaveBeenCalledTimes(1);
|
||||
expect(console.warn).toHaveBeenLastCalledWith(
|
||||
'The direction prop should be either "ltr" (default) or "rtl". ' +
|
||||
'Please use the layout prop to specify "vertical" (default) or "horizontal" orientation.'
|
||||
);
|
||||
|
||||
renderer.update(
|
||||
<FixedSizeList {...defaultProps} direction="horizontal" />
|
||||
);
|
||||
|
||||
// But it should only warn once.
|
||||
expect(console.warn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should warn if legacy direction "vertical" value is used', () => {
|
||||
spyOn(console, 'warn');
|
||||
|
||||
const renderer = ReactTestRenderer.create(
|
||||
<FixedSizeList {...defaultProps} direction="vertical" />
|
||||
);
|
||||
expect(console.warn).toHaveBeenCalledTimes(1);
|
||||
expect(console.warn).toHaveBeenLastCalledWith(
|
||||
'The direction prop should be either "ltr" (default) or "rtl". ' +
|
||||
'Please use the layout prop to specify "vertical" (default) or "horizontal" orientation.'
|
||||
);
|
||||
|
||||
renderer.update(<FixedSizeList {...defaultProps} direction="vertical" />);
|
||||
|
||||
// But it should only warn once.
|
||||
expect(console.warn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -664,6 +765,18 @@ describe('FixedSizeList', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should fail if an invalid layout is provided', () => {
|
||||
expect(() =>
|
||||
ReactTestRenderer.create(
|
||||
<FixedSizeList {...defaultProps} layout={null} />
|
||||
)
|
||||
).toThrow(
|
||||
'An invalid "layout" prop has been specified. ' +
|
||||
'Value should be either "horizontal" or "vertical". ' +
|
||||
'"null" was specified.'
|
||||
);
|
||||
});
|
||||
|
||||
it('should fail if an invalid direction is provided', () => {
|
||||
expect(() =>
|
||||
ReactTestRenderer.create(
|
||||
@@ -671,7 +784,7 @@ describe('FixedSizeList', () => {
|
||||
)
|
||||
).toThrow(
|
||||
'An invalid "direction" prop has been specified. ' +
|
||||
'Value should be either "horizontal" or "vertical". ' +
|
||||
'Value should be either "ltr" or "rtl". ' +
|
||||
'"null" was specified.'
|
||||
);
|
||||
});
|
||||
@@ -679,7 +792,7 @@ describe('FixedSizeList', () => {
|
||||
it('should fail if a string height is provided for a vertical list', () => {
|
||||
expect(() =>
|
||||
ReactTestRenderer.create(
|
||||
<FixedSizeList {...defaultProps} direction="vertical" height="100%" />
|
||||
<FixedSizeList {...defaultProps} layout="vertical" height="100%" />
|
||||
)
|
||||
).toThrow(
|
||||
'An invalid "height" prop has been specified. ' +
|
||||
@@ -691,11 +804,7 @@ describe('FixedSizeList', () => {
|
||||
it('should fail if a string width is provided for a horizontal list', () => {
|
||||
expect(() =>
|
||||
ReactTestRenderer.create(
|
||||
<FixedSizeList
|
||||
{...defaultProps}
|
||||
direction="horizontal"
|
||||
width="100%"
|
||||
/>
|
||||
<FixedSizeList {...defaultProps} layout="horizontal" width="100%" />
|
||||
)
|
||||
).toThrow(
|
||||
'An invalid "width" prop has been specified. ' +
|
||||
|
||||
@@ -1,11 +1,25 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import React, { createRef, PureComponent } from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import { Simulate } from 'react-dom/test-utils';
|
||||
import ReactTestRenderer from 'react-test-renderer';
|
||||
import { VariableSizeGrid } from '..';
|
||||
import * as domHelpers from '../domHelpers';
|
||||
|
||||
const simulateScroll = (instance, { scrollLeft, scrollTop }) => {
|
||||
instance._outerRef.scrollLeft = scrollLeft;
|
||||
instance._outerRef.scrollTop = scrollTop;
|
||||
Simulate.scroll(instance._outerRef);
|
||||
};
|
||||
|
||||
const findScrollContainer = rendered => rendered.root.children[0].children[0];
|
||||
|
||||
describe('VariableSizeGrid', () => {
|
||||
let columnWidth, defaultProps, itemRenderer, onItemsRendered, rowHeight;
|
||||
let columnWidth,
|
||||
defaultProps,
|
||||
getScrollbarSize,
|
||||
itemRenderer,
|
||||
onItemsRendered,
|
||||
rowHeight;
|
||||
|
||||
// Use PureComponent to test memoization.
|
||||
// Pass through to itemRenderer mock for easier test assertions.
|
||||
@@ -26,6 +40,9 @@ describe('VariableSizeGrid', () => {
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
// Mock the DOM helper util for testing purposes.
|
||||
getScrollbarSize = domHelpers.getScrollbarSize = jest.fn(() => 0);
|
||||
|
||||
itemRenderer = jest.fn(({ style, ...rest }) => (
|
||||
<div style={style}>{JSON.stringify(rest, null, 2)}</div>
|
||||
));
|
||||
@@ -247,6 +264,90 @@ describe('VariableSizeGrid', () => {
|
||||
.scrollToItem({ columnIndex: 9, rowIndex: 19, align: 'center' });
|
||||
expect(onItemsRendered.mock.calls).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should account for scrollbar size', () => {
|
||||
const onScroll = jest.fn();
|
||||
const rendered = ReactTestRenderer.create(
|
||||
<VariableSizeGrid {...defaultProps} onScroll={onScroll} />
|
||||
);
|
||||
|
||||
onScroll.mockClear();
|
||||
rendered
|
||||
.getInstance()
|
||||
.scrollToItem({ columnIndex: 15, rowIndex: 10, align: 'end' });
|
||||
|
||||
// With hidden scrollbars (size === 0) we would expect...
|
||||
expect(onScroll).toHaveBeenCalledWith({
|
||||
horizontalScrollDirection: 'forward',
|
||||
scrollLeft: 720,
|
||||
scrollTop: 230,
|
||||
scrollUpdateWasRequested: true,
|
||||
verticalScrollDirection: 'forward',
|
||||
});
|
||||
|
||||
getScrollbarSize.mockImplementation(() => 20);
|
||||
|
||||
onScroll.mockClear();
|
||||
rendered
|
||||
.getInstance()
|
||||
.scrollToItem({ columnIndex: 15, rowIndex: 10, align: 'end' });
|
||||
|
||||
// With scrollbars of size 20 we would expect those values ot increase by 20px
|
||||
expect(onScroll).toHaveBeenCalledWith({
|
||||
horizontalScrollDirection: 'forward',
|
||||
scrollLeft: 740,
|
||||
scrollTop: 250,
|
||||
scrollUpdateWasRequested: true,
|
||||
verticalScrollDirection: 'forward',
|
||||
});
|
||||
});
|
||||
|
||||
it('should not account for scrollbar size when no scrollbar is visible for a particular direction', () => {
|
||||
getScrollbarSize.mockImplementation(() => 20);
|
||||
|
||||
const onScroll = jest.fn();
|
||||
const rendered = ReactTestRenderer.create(
|
||||
<VariableSizeGrid
|
||||
{...defaultProps}
|
||||
columnCount={1}
|
||||
onScroll={onScroll}
|
||||
/>
|
||||
);
|
||||
|
||||
onScroll.mockClear();
|
||||
rendered
|
||||
.getInstance()
|
||||
.scrollToItem({ columnIndex: 0, rowIndex: 10, align: 'end' });
|
||||
|
||||
// Since there aren't enough columns to require horizontal scrolling,
|
||||
// the additional 20px for the scrollbar should not be taken into consideration.
|
||||
expect(onScroll).toHaveBeenCalledWith({
|
||||
horizontalScrollDirection: 'backward',
|
||||
scrollLeft: 0,
|
||||
scrollTop: 230,
|
||||
scrollUpdateWasRequested: true,
|
||||
verticalScrollDirection: 'forward',
|
||||
});
|
||||
|
||||
rendered.update(
|
||||
<VariableSizeGrid {...defaultProps} rowCount={1} onScroll={onScroll} />
|
||||
);
|
||||
|
||||
onScroll.mockClear();
|
||||
rendered
|
||||
.getInstance()
|
||||
.scrollToItem({ columnIndex: 15, rowIndex: 0, align: 'end' });
|
||||
|
||||
// Since there aren't enough rows to require vertical scrolling,
|
||||
// the additional 20px for the scrollbar should not be taken into consideration.
|
||||
expect(onScroll).toHaveBeenCalledWith({
|
||||
horizontalScrollDirection: 'forward',
|
||||
scrollLeft: 720,
|
||||
scrollTop: 0,
|
||||
scrollUpdateWasRequested: true,
|
||||
verticalScrollDirection: 'backward',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('resetAfterIndex method', () => {
|
||||
@@ -408,4 +509,36 @@ describe('VariableSizeGrid', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// https://github.com/bvaughn/react-window/pull/138
|
||||
it('should descrease scroll size when itemCount decreases', () => {
|
||||
const innerRef = createRef();
|
||||
const gridRef = createRef();
|
||||
|
||||
class Wrapper extends PureComponent {
|
||||
state = { columnCount: 100, rowCount: 200 };
|
||||
render() {
|
||||
return (
|
||||
<VariableSizeGrid
|
||||
{...defaultProps}
|
||||
columnCount={this.state.columnCount}
|
||||
innerRef={innerRef}
|
||||
ref={gridRef}
|
||||
rowCount={this.state.rowCount}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Use ReactDOM renderer so "scroll" events work correctly.
|
||||
const instance = render(<Wrapper />, document.createElement('div'));
|
||||
|
||||
// Simulate scrolling past several rows.
|
||||
simulateScroll(gridRef.current, { scrollLeft: 3000, scrollTop: 4000 });
|
||||
|
||||
// Decrease itemCount a lot and verify the scroll height is descreased as well.
|
||||
instance.setState({ columnCount: 2, rowCount: 4 });
|
||||
expect(innerRef.current.style.height).toEqual('106px');
|
||||
expect(innerRef.current.style.width).toEqual('101px');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,18 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import React, { createRef, PureComponent } from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import { Simulate } from 'react-dom/test-utils';
|
||||
import ReactTestRenderer from 'react-test-renderer';
|
||||
import { VariableSizeList } from '..';
|
||||
|
||||
const simulateScroll = (instance, scrollOffset, direction = 'vertical') => {
|
||||
if (direction === 'horizontal') {
|
||||
instance._outerRef.scrollLeft = scrollOffset;
|
||||
} else {
|
||||
instance._outerRef.scrollTop = scrollOffset;
|
||||
}
|
||||
Simulate.scroll(instance._outerRef);
|
||||
};
|
||||
|
||||
const findScrollContainer = rendered => rendered.root.children[0].children[0];
|
||||
|
||||
describe('VariableSizeList', () => {
|
||||
@@ -295,4 +306,34 @@ describe('VariableSizeList', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// https://github.com/bvaughn/react-window/pull/138
|
||||
it('should descrease scroll size when itemCount decreases', () => {
|
||||
const innerRef = createRef();
|
||||
const listRef = createRef();
|
||||
|
||||
class Wrapper extends PureComponent {
|
||||
state = { itemCount: 100 };
|
||||
render() {
|
||||
return (
|
||||
<VariableSizeList
|
||||
{...defaultProps}
|
||||
itemCount={this.state.itemCount}
|
||||
innerRef={innerRef}
|
||||
ref={listRef}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Use ReactDOM renderer so "scroll" events work correctly.
|
||||
const instance = render(<Wrapper />, document.createElement('div'));
|
||||
|
||||
// Simulate scrolling past several rows.
|
||||
simulateScroll(listRef.current, 3000);
|
||||
|
||||
// Decrease itemCount a lot and verify the scroll height is descreased as well.
|
||||
instance.setState({ itemCount: 3 });
|
||||
expect(innerRef.current.style.height).toEqual('78px');
|
||||
});
|
||||
});
|
||||
|
||||
+119
-32
@@ -3,9 +3,11 @@
|
||||
import memoizeOne from 'memoize-one';
|
||||
import { createElement, PureComponent } from 'react';
|
||||
import { cancelTimeout, requestTimeout } from './timer';
|
||||
import { getScrollbarSize } from './domHelpers';
|
||||
|
||||
import type { TimeoutID } from './timer';
|
||||
|
||||
type Direction = 'ltr' | 'rtl';
|
||||
export type ScrollToAlign = 'auto' | 'center' | 'start' | 'end';
|
||||
|
||||
type itemSize = number | ((index: number) => number);
|
||||
@@ -49,6 +51,7 @@ export type Props<T> = {|
|
||||
className?: string,
|
||||
columnCount: number,
|
||||
columnWidth: itemSize,
|
||||
direction: Direction,
|
||||
height: number,
|
||||
initialScrollLeft?: number,
|
||||
initialScrollTop?: number,
|
||||
@@ -77,6 +80,7 @@ export type Props<T> = {|
|
||||
|};
|
||||
|
||||
type State = {|
|
||||
instance: any,
|
||||
isScrolling: boolean,
|
||||
horizontalScrollDirection: ScrollDirection,
|
||||
scrollLeft: number,
|
||||
@@ -101,7 +105,8 @@ type GetOffsetForItemAndAlignment = (
|
||||
index: number,
|
||||
align: ScrollToAlign,
|
||||
scrollOffset: number,
|
||||
instanceProps: any
|
||||
instanceProps: any,
|
||||
scrollbarSize: number
|
||||
) => number;
|
||||
type GetStartIndexForOffset = (
|
||||
props: Props<any>,
|
||||
@@ -122,6 +127,17 @@ 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 instace.
|
||||
// This avoids spamming the console every time a render happens.
|
||||
let devWarningsOverscanCount = null;
|
||||
let devWarningsTagName = null;
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (typeof window.WeakSet !== 'undefined') {
|
||||
devWarningsOverscanCount = new WeakSet();
|
||||
devWarningsTagName = new WeakSet();
|
||||
}
|
||||
}
|
||||
|
||||
export default function createGridComponent({
|
||||
getColumnOffset,
|
||||
getColumnStartIndexForOffset,
|
||||
@@ -161,11 +177,13 @@ export default function createGridComponent({
|
||||
_outerRef: ?HTMLDivElement;
|
||||
|
||||
static defaultProps = {
|
||||
direction: 'ltr',
|
||||
itemData: undefined,
|
||||
useIsScrolling: false,
|
||||
};
|
||||
|
||||
state: State = {
|
||||
instance: this,
|
||||
isScrolling: false,
|
||||
horizontalScrollDirection: 'forward',
|
||||
scrollLeft:
|
||||
@@ -190,8 +208,8 @@ export default function createGridComponent({
|
||||
static getDerivedStateFromProps(
|
||||
nextProps: Props<T>,
|
||||
prevState: State
|
||||
): $Shape<State> {
|
||||
validateSharedProps(nextProps);
|
||||
): $Shape<State> | null {
|
||||
validateSharedProps(nextProps, prevState);
|
||||
validateProps(nextProps);
|
||||
return null;
|
||||
}
|
||||
@@ -232,7 +250,26 @@ export default function createGridComponent({
|
||||
columnIndex: number,
|
||||
rowIndex: number,
|
||||
}): void {
|
||||
const { height, width } = this.props;
|
||||
const { scrollLeft, scrollTop } = this.state;
|
||||
const scrollbarSize = getScrollbarSize();
|
||||
|
||||
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: getOffsetForColumnAndAlignment(
|
||||
@@ -240,14 +277,16 @@ export default function createGridComponent({
|
||||
columnIndex,
|
||||
align,
|
||||
scrollLeft,
|
||||
this._instanceProps
|
||||
this._instanceProps,
|
||||
verticalScrollbarSize
|
||||
),
|
||||
scrollTop: getOffsetForRowAndAlignment(
|
||||
this.props,
|
||||
rowIndex,
|
||||
align,
|
||||
scrollTop,
|
||||
this._instanceProps
|
||||
this._instanceProps,
|
||||
horizontalScrollbarSize
|
||||
),
|
||||
});
|
||||
}
|
||||
@@ -285,6 +324,7 @@ export default function createGridComponent({
|
||||
children,
|
||||
className,
|
||||
columnCount,
|
||||
direction,
|
||||
height,
|
||||
innerRef,
|
||||
innerElementType,
|
||||
@@ -356,6 +396,7 @@ export default function createGridComponent({
|
||||
overflow: 'auto',
|
||||
WebkitOverflowScrolling: 'touch',
|
||||
willChange: 'transform',
|
||||
direction,
|
||||
...style,
|
||||
},
|
||||
},
|
||||
@@ -482,20 +523,27 @@ export default function createGridComponent({
|
||||
// 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 key = `${rowIndex}:${columnIndex}`;
|
||||
const { columnWidth, direction, rowHeight } = this.props;
|
||||
|
||||
const itemStyleCache = this._getItemStyleCache(
|
||||
shouldResetStyleCacheOnItemSizeChange && this.props.columnWidth,
|
||||
shouldResetStyleCacheOnItemSizeChange && this.props.rowHeight
|
||||
shouldResetStyleCacheOnItemSizeChange && columnWidth,
|
||||
shouldResetStyleCacheOnItemSizeChange && direction,
|
||||
shouldResetStyleCacheOnItemSizeChange && rowHeight
|
||||
);
|
||||
|
||||
const key = `${rowIndex}:${columnIndex}`;
|
||||
|
||||
let style;
|
||||
if (itemStyleCache.hasOwnProperty(key)) {
|
||||
style = itemStyleCache[key];
|
||||
} else {
|
||||
itemStyleCache[key] = style = {
|
||||
position: 'absolute',
|
||||
left: getColumnOffset(this.props, columnIndex, this._instanceProps),
|
||||
[direction === 'rtl' ? 'right' : 'left']: getColumnOffset(
|
||||
this.props,
|
||||
columnIndex,
|
||||
this._instanceProps
|
||||
),
|
||||
top: getRowOffset(this.props, rowIndex, this._instanceProps),
|
||||
height: getRowHeight(this.props, rowIndex, this._instanceProps),
|
||||
width: getColumnWidth(this.props, columnIndex, this._instanceProps),
|
||||
@@ -505,11 +553,8 @@ export default function createGridComponent({
|
||||
return style;
|
||||
};
|
||||
|
||||
// TODO This memoized getter doesn't make much sense.
|
||||
// If all that's really needed is for the impl to be able to reset the cache,
|
||||
// Then we could expose a better API for that.
|
||||
_getItemStyleCache: (_: any, __: any) => ItemStyleCache;
|
||||
_getItemStyleCache = memoizeOne((_: any, __: any) => ({}));
|
||||
_getItemStyleCache: (_: any, __: any, ___: any) => ItemStyleCache;
|
||||
_getItemStyleCache = memoizeOne((_: any, __: any, ___: any) => ({}));
|
||||
|
||||
_getHorizontalRangeToRender(): [number, number, number, number] {
|
||||
const {
|
||||
@@ -606,7 +651,12 @@ export default function createGridComponent({
|
||||
}
|
||||
|
||||
_onScroll = (event: ScrollEvent): void => {
|
||||
const { scrollLeft, scrollTop } = event.currentTarget;
|
||||
const {
|
||||
clientWidth,
|
||||
scrollLeft,
|
||||
scrollTop,
|
||||
scrollWidth,
|
||||
} = event.currentTarget;
|
||||
this.setState(prevState => {
|
||||
if (
|
||||
prevState.scrollLeft === scrollLeft &&
|
||||
@@ -618,11 +668,25 @@ export default function createGridComponent({
|
||||
return null;
|
||||
}
|
||||
|
||||
const { direction } = this.props;
|
||||
|
||||
// HACK According to the spec, scrollLeft should be negative for RTL aligned elements.
|
||||
// Chrome does not seem to adhere; its scrolLeft values are positive (measured relative to the left).
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollLeft
|
||||
let calculatedScrollLeft = scrollLeft;
|
||||
if (direction === 'rtl') {
|
||||
if (scrollLeft <= 0) {
|
||||
calculatedScrollLeft = -scrollLeft;
|
||||
} else {
|
||||
calculatedScrollLeft = scrollWidth - clientWidth - scrollLeft;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isScrolling: true,
|
||||
horizontalScrollDirection:
|
||||
prevState.scrollLeft < scrollLeft ? 'forward' : 'backward',
|
||||
scrollLeft,
|
||||
scrollLeft: calculatedScrollLeft,
|
||||
scrollTop,
|
||||
verticalScrollDirection:
|
||||
prevState.scrollTop < scrollTop ? 'forward' : 'backward',
|
||||
@@ -670,27 +734,37 @@ export default function createGridComponent({
|
||||
};
|
||||
}
|
||||
|
||||
const validateSharedProps = ({
|
||||
children,
|
||||
height,
|
||||
innerTagName,
|
||||
outerTagName,
|
||||
overscanCount,
|
||||
width,
|
||||
}: Props<any>): void => {
|
||||
const validateSharedProps = (
|
||||
{
|
||||
children,
|
||||
direction,
|
||||
height,
|
||||
innerTagName,
|
||||
outerTagName,
|
||||
overscanCount,
|
||||
width,
|
||||
}: Props<any>,
|
||||
{ instance }: State
|
||||
): void => {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (typeof overscanCount === 'number') {
|
||||
console.warn(
|
||||
'The overscanCount prop has been deprecated. ' +
|
||||
'Please use the overscanColumnsCount and overscanRowsCount props instead.'
|
||||
);
|
||||
if (devWarningsOverscanCount && !devWarningsOverscanCount.has(instance)) {
|
||||
devWarningsOverscanCount.add(instance);
|
||||
console.warn(
|
||||
'The overscanCount prop has been deprecated. ' +
|
||||
'Please use the overscanColumnsCount and overscanRowsCount props instead.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (innerTagName != null || outerTagName != null) {
|
||||
console.warn(
|
||||
'The innerTagName and outerTagName props have been deprecated. ' +
|
||||
'Please use the innerElementType and outerElementType props instead.'
|
||||
);
|
||||
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) {
|
||||
@@ -701,6 +775,19 @@ const validateSharedProps = ({
|
||||
);
|
||||
}
|
||||
|
||||
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. ' +
|
||||
|
||||
+134
-53
@@ -9,7 +9,9 @@ import type { TimeoutID } from './timer';
|
||||
export type ScrollToAlign = 'auto' | 'center' | 'start' | 'end';
|
||||
|
||||
type itemSize = number | ((index: number) => number);
|
||||
export type Direction = 'horizontal' | 'vertical';
|
||||
// TODO Deprecate directions "horizontal" and "vertical"
|
||||
export type Direction = 'ltr' | 'rtl' | 'horizontal' | 'vertical';
|
||||
export type Layout = 'horizontal' | 'vertical';
|
||||
|
||||
export type RenderComponentProps<T> = {|
|
||||
data: T,
|
||||
@@ -49,6 +51,7 @@ export type Props<T> = {|
|
||||
itemData: T,
|
||||
itemKey?: (index: number, data: T) => any,
|
||||
itemSize: itemSize,
|
||||
layout: Layout,
|
||||
onItemsRendered?: onItemsRenderedCallback,
|
||||
onScroll?: onScrollCallback,
|
||||
outerRef?: any,
|
||||
@@ -61,6 +64,7 @@ export type Props<T> = {|
|
||||
|};
|
||||
|
||||
type State = {|
|
||||
instance: any,
|
||||
isScrolling: boolean,
|
||||
scrollDirection: ScrollDirection,
|
||||
scrollOffset: number,
|
||||
@@ -103,6 +107,17 @@ const IS_SCROLLING_DEBOUNCE_INTERVAL = 150;
|
||||
|
||||
export const defaultItemKey = (index: number, data: any) => index;
|
||||
|
||||
// In DEV mode, this Set helps us only log a warning once per component instace.
|
||||
// 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.WeakSet !== 'undefined') {
|
||||
devWarningsDirection = new WeakSet();
|
||||
devWarningsTagName = new WeakSet();
|
||||
}
|
||||
}
|
||||
|
||||
export default function createListComponent({
|
||||
getItemOffset,
|
||||
getEstimatedTotalSize,
|
||||
@@ -130,13 +145,15 @@ export default function createListComponent({
|
||||
_resetIsScrollingTimeoutId: TimeoutID | null = null;
|
||||
|
||||
static defaultProps = {
|
||||
direction: 'vertical',
|
||||
direction: 'ltr',
|
||||
itemData: undefined,
|
||||
layout: 'vertical',
|
||||
overscanCount: 2,
|
||||
useIsScrolling: false,
|
||||
};
|
||||
|
||||
state: State = {
|
||||
instance: this,
|
||||
isScrolling: false,
|
||||
scrollDirection: 'forward',
|
||||
scrollOffset:
|
||||
@@ -154,11 +171,11 @@ export default function createListComponent({
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(
|
||||
props: Props<T>,
|
||||
state: State
|
||||
): $Shape<State> {
|
||||
validateSharedProps(props);
|
||||
validateProps(props);
|
||||
nextProps: Props<T>,
|
||||
prevState: State
|
||||
): $Shape<State> | null {
|
||||
validateSharedProps(nextProps, prevState);
|
||||
validateProps(nextProps);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -188,14 +205,16 @@ export default function createListComponent({
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { initialScrollOffset, direction } = this.props;
|
||||
const { direction, initialScrollOffset, layout } = this.props;
|
||||
|
||||
if (typeof initialScrollOffset === 'number' && this._outerRef !== null) {
|
||||
const element = ((this._outerRef: any): HTMLDivElement);
|
||||
if (direction === 'horizontal') {
|
||||
element.scrollLeft = initialScrollOffset;
|
||||
// TODO Deprecate direction "horizontal"
|
||||
if (direction === 'horizontal' || layout === 'horizontal') {
|
||||
((this
|
||||
._outerRef: any): HTMLDivElement).scrollLeft = initialScrollOffset;
|
||||
} else {
|
||||
element.scrollTop = initialScrollOffset;
|
||||
((this
|
||||
._outerRef: any): HTMLDivElement).scrollTop = initialScrollOffset;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,15 +223,15 @@ export default function createListComponent({
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const { direction } = this.props;
|
||||
const { direction, layout } = this.props;
|
||||
const { scrollOffset, scrollUpdateWasRequested } = this.state;
|
||||
|
||||
if (scrollUpdateWasRequested && this._outerRef !== null) {
|
||||
const element = ((this._outerRef: any): HTMLDivElement);
|
||||
if (direction === 'horizontal') {
|
||||
element.scrollLeft = scrollOffset;
|
||||
// TODO Deprecate direction "horizontal"
|
||||
if (direction === 'horizontal' || layout === 'horizontal') {
|
||||
((this._outerRef: any): HTMLDivElement).scrollLeft = scrollOffset;
|
||||
} else {
|
||||
element.scrollTop = scrollOffset;
|
||||
((this._outerRef: any): HTMLDivElement).scrollTop = scrollOffset;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,6 +255,7 @@ export default function createListComponent({
|
||||
innerRef,
|
||||
innerElementType,
|
||||
innerTagName,
|
||||
layout,
|
||||
outerElementType,
|
||||
outerTagName,
|
||||
style,
|
||||
@@ -243,10 +263,13 @@ export default function createListComponent({
|
||||
} = this.props;
|
||||
const { isScrolling } = this.state;
|
||||
|
||||
const onScroll =
|
||||
direction === 'vertical'
|
||||
? this._onScrollVertical
|
||||
: this._onScrollHorizontal;
|
||||
// TODO Deprecate direction "horizontal"
|
||||
const isHorizontal =
|
||||
direction === 'horizontal' || layout === 'horizontal';
|
||||
|
||||
const onScroll = isHorizontal
|
||||
? this._onScrollHorizontal
|
||||
: this._onScrollVertical;
|
||||
|
||||
const items = this._renderItems();
|
||||
|
||||
@@ -270,6 +293,7 @@ export default function createListComponent({
|
||||
position: 'relative',
|
||||
WebkitOverflowScrolling: 'touch',
|
||||
willChange: 'transform',
|
||||
direction,
|
||||
...style,
|
||||
},
|
||||
},
|
||||
@@ -277,9 +301,9 @@ export default function createListComponent({
|
||||
children: items,
|
||||
ref: innerRef,
|
||||
style: {
|
||||
height: direction === 'horizontal' ? '100%' : estimatedTotalSize,
|
||||
height: isHorizontal ? '100%' : estimatedTotalSize,
|
||||
pointerEvents: isScrolling ? 'none' : '',
|
||||
width: direction === 'horizontal' ? estimatedTotalSize : '100%',
|
||||
width: isHorizontal ? estimatedTotalSize : '100%',
|
||||
},
|
||||
})
|
||||
);
|
||||
@@ -371,10 +395,11 @@ export default function createListComponent({
|
||||
// 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 } = this.props;
|
||||
const { direction, itemSize, layout } = this.props;
|
||||
|
||||
const itemStyleCache = this._getItemStyleCache(
|
||||
shouldResetStyleCacheOnItemSizeChange && itemSize,
|
||||
shouldResetStyleCacheOnItemSizeChange && layout,
|
||||
shouldResetStyleCacheOnItemSizeChange && direction
|
||||
);
|
||||
|
||||
@@ -385,12 +410,16 @@ export default function createListComponent({
|
||||
const offset = getItemOffset(this.props, index, this._instanceProps);
|
||||
const size = getItemSize(this.props, index, this._instanceProps);
|
||||
|
||||
// TODO Deprecate direction "horizontal"
|
||||
const isHorizontal =
|
||||
direction === 'horizontal' || layout === 'horizontal';
|
||||
|
||||
itemStyleCache[index] = style = {
|
||||
position: 'absolute',
|
||||
left: direction === 'horizontal' ? offset : 0,
|
||||
top: direction === 'vertical' ? offset : 0,
|
||||
height: direction === 'vertical' ? size : '100%',
|
||||
width: direction === 'horizontal' ? size : '100%',
|
||||
[direction === 'rtl' ? 'right' : 'left']: isHorizontal ? offset : 0,
|
||||
top: !isHorizontal ? offset : 0,
|
||||
height: !isHorizontal ? size : '100%',
|
||||
width: isHorizontal ? size : '100%',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -402,11 +431,11 @@ export default function createListComponent({
|
||||
// TODO This memoized getter doesn't make much sense.
|
||||
// If all that's really needed is for the impl to be able to reset the cache,
|
||||
// Then we could expose a better API for that.
|
||||
_getItemStyleCache: (_: any, __: any) => ItemStyleCache;
|
||||
_getItemStyleCache = memoizeOne((_, __) => {
|
||||
_getItemStyleCache: (_: any, __: any, ___: any) => ItemStyleCache;
|
||||
_getItemStyleCache = memoizeOne((_: any, __: any, ___: any) => {
|
||||
this._itemStyleCache = {};
|
||||
|
||||
return this._itemStyleCache;
|
||||
return this._itemStyleCache;
|
||||
});
|
||||
|
||||
_getRangeToRender(): [number, number, number, number] {
|
||||
@@ -478,7 +507,7 @@ export default function createListComponent({
|
||||
}
|
||||
|
||||
_onScrollHorizontal = (event: ScrollEvent): void => {
|
||||
const { scrollLeft } = event.currentTarget;
|
||||
const { clientWidth, scrollLeft, scrollWidth } = event.currentTarget;
|
||||
this.setState(prevState => {
|
||||
if (prevState.scrollOffset === scrollLeft) {
|
||||
// Scroll position may have been updated by cDM/cDU,
|
||||
@@ -487,11 +516,25 @@ export default function createListComponent({
|
||||
return null;
|
||||
}
|
||||
|
||||
const { direction } = this.props;
|
||||
|
||||
// HACK According to the spec, scrollLeft should be negative for RTL aligned elements.
|
||||
// Chrome does not seem to adhere; its scrolLeft values are positive (measured relative to the left).
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollLeft
|
||||
let scrollOffset = scrollLeft;
|
||||
if (direction === 'rtl') {
|
||||
if (scrollLeft <= 0) {
|
||||
scrollOffset = -scrollOffset;
|
||||
} else {
|
||||
scrollOffset = scrollWidth - clientWidth - scrollLeft;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isScrolling: true,
|
||||
scrollDirection:
|
||||
prevState.scrollOffset < scrollLeft ? 'forward' : 'backward',
|
||||
scrollOffset: scrollLeft,
|
||||
scrollOffset,
|
||||
scrollUpdateWasRequested: false,
|
||||
};
|
||||
}, this._resetIsScrollingDebounced);
|
||||
@@ -566,28 +609,66 @@ export default function createListComponent({
|
||||
// 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,
|
||||
innerTagName,
|
||||
outerTagName,
|
||||
width,
|
||||
}: Props<any>): void => {
|
||||
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) {
|
||||
console.warn(
|
||||
'The innerTagName and outerTagName props have been deprecated. ' +
|
||||
'Please use the innerElementType and outerElementType props instead.'
|
||||
);
|
||||
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 (direction !== 'horizontal' && direction !== 'vertical') {
|
||||
throw Error(
|
||||
'An invalid "direction" prop has been specified. ' +
|
||||
'Value should be either "horizontal" or "vertical". ' +
|
||||
`"${direction}" was specified.`
|
||||
);
|
||||
// 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) {
|
||||
@@ -598,13 +679,13 @@ const validateSharedProps = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (direction === 'horizontal' && typeof width !== 'number') {
|
||||
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 (direction === 'vertical' && typeof height !== 'number') {
|
||||
} else if (!isHorizontal && typeof height !== 'number') {
|
||||
throw Error(
|
||||
'An invalid "height" prop has been specified. ' +
|
||||
'Vertical lists must specify a number for height. ' +
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// @flow
|
||||
|
||||
let size: number = -1;
|
||||
|
||||
// This utility copied from "dom-helpers" package.
|
||||
export function getScrollbarSize(recalculate?: boolean = false): number {
|
||||
if (size === -1 || recalculate) {
|
||||
const div = document.createElement('div');
|
||||
const style = div.style;
|
||||
style.width = '50px';
|
||||
style.height = '50px';
|
||||
style.overflow = 'scroll';
|
||||
|
||||
((document.body: any): HTMLBodyElement).appendChild(div);
|
||||
|
||||
size = div.offsetWidth - div.clientWidth;
|
||||
|
||||
((document.body: any): HTMLBodyElement).removeChild(div);
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
@@ -26,7 +26,7 @@ const Example = () => (
|
||||
<Fragment>
|
||||
<List
|
||||
className="List"
|
||||
direction="horizontal"
|
||||
layout="horizontal"
|
||||
height={50}
|
||||
itemCount={items.length}
|
||||
itemData={items}
|
||||
|
||||
@@ -13,10 +13,10 @@ const Column = ({ index, style }) => (
|
||||
const Example = () => (
|
||||
<List
|
||||
className="List"
|
||||
direction="horizontal"
|
||||
height={75}
|
||||
itemCount={1000}
|
||||
itemSize={100}
|
||||
layout="horizontal"
|
||||
width={300}
|
||||
>
|
||||
{Column}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"description": "Demo of react-window with advanced memoization techniques",
|
||||
"main": "src/index.js",
|
||||
"dependencies": {
|
||||
"memoize-one": "^4",
|
||||
"memoize-one": "^5",
|
||||
"react": "^16.6",
|
||||
"react-dom": "^16.6",
|
||||
"react-window": "^1"
|
||||
|
||||
@@ -21,10 +21,10 @@ const Column = ({ index, style }) => (
|
||||
const Example = () => (
|
||||
<List
|
||||
className="List"
|
||||
direction="horizontal"
|
||||
height={75}
|
||||
itemCount={1000}
|
||||
itemSize={getItemSize}
|
||||
layout="horizontal"
|
||||
width={300}
|
||||
>
|
||||
{Column}
|
||||
|
||||
@@ -11,6 +11,7 @@ import FixedSizeGridApi from './routes/api/FixedSizeGrid';
|
||||
import FixedSizeGridExample from './routes/examples/FixedSizeGrid';
|
||||
import FixedSizeListApi from './routes/api/FixedSizeList';
|
||||
import FixedSizeListExample from './routes/examples/FixedSizeList';
|
||||
import RTLLayoutExample from './routes/examples/RTLLayout';
|
||||
import ListWithScrollingIndicatorExample from './routes/examples/ListWithScrollingIndicator';
|
||||
import MemoizedListItemsExample from './routes/examples/MemoizedListItemsExample';
|
||||
import ScrollToItemExample from './routes/examples/ScrollToItem';
|
||||
@@ -102,6 +103,11 @@ const EXAMPLE_ROUTES = [
|
||||
title: 'Memoized List items',
|
||||
component: MemoizedListItemsExample,
|
||||
},
|
||||
{
|
||||
path: '/examples/list/fixed-size-rtl',
|
||||
title: 'RTL layout',
|
||||
component: RTLLayoutExample,
|
||||
},
|
||||
];
|
||||
|
||||
const COMPONENTS_ROUTES = [
|
||||
|
||||
@@ -10,7 +10,7 @@ const Column = ({ index, style }) => (
|
||||
// Note that no itemSize is required for dyanmic lists!
|
||||
const Example = () => (
|
||||
<List
|
||||
direction="horizontal"
|
||||
layout="horizontal"
|
||||
height={50}
|
||||
itemCount={1000}
|
||||
width={300}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { FixedSizeGrid as Grid } from 'react-window';
|
||||
|
||||
const Cell = ({ columnIndex, rowIndex, style }) => (
|
||||
<div style={style}>
|
||||
بند {rowIndex},{columnIndex}
|
||||
</div>
|
||||
);
|
||||
|
||||
const Example = () => (
|
||||
<Grid
|
||||
columnCount={1000}
|
||||
columnWidth={100}
|
||||
direction="rtl"
|
||||
height={150}
|
||||
rowCount={1000}
|
||||
rowHeight={35}
|
||||
width={300}
|
||||
>
|
||||
{Cell}
|
||||
</Grid>
|
||||
);
|
||||
@@ -6,10 +6,10 @@ const Column = ({ index, style }) => (
|
||||
|
||||
const Example = () => (
|
||||
<List
|
||||
direction="horizontal"
|
||||
height={75}
|
||||
itemCount={1000}
|
||||
itemSize={100}
|
||||
layout="horizontal"
|
||||
width={300}
|
||||
>
|
||||
{Column}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { FixedSizeList as List } from 'react-window';
|
||||
|
||||
const Column = ({ index, style }) => (
|
||||
<div style={style}>عمود {index}</div>
|
||||
);
|
||||
|
||||
const Example = () => (
|
||||
<List
|
||||
direction="rtl"
|
||||
height={75}
|
||||
itemCount={1000}
|
||||
itemSize={100}
|
||||
layout="horizontal"
|
||||
width={300}
|
||||
>
|
||||
{Column}
|
||||
</List>
|
||||
);
|
||||
@@ -14,10 +14,10 @@ const Column = ({ index, style }) => (
|
||||
|
||||
const Example = () => (
|
||||
<List
|
||||
direction="horizontal"
|
||||
height={75}
|
||||
itemCount={1000}
|
||||
itemSize={getItemSize}
|
||||
layout="horizontal"
|
||||
width={300}
|
||||
>
|
||||
{Column}
|
||||
|
||||
@@ -13,7 +13,7 @@ type Props = {|
|
||||
style?: Object,
|
||||
|};
|
||||
|
||||
const isProfilingEnabled = window.location.hash.includes('profile=true');
|
||||
const isProfilingEnabled = window.location.hash.indexOf('profile=true') >= 0;
|
||||
|
||||
export default class ProfiledExample extends PureComponent<Props, void> {
|
||||
_averageTimeRef = createRef();
|
||||
|
||||
@@ -74,6 +74,31 @@ const PROPS = [
|
||||
name: 'columnWidth',
|
||||
type: 'number',
|
||||
},
|
||||
{
|
||||
defaultValue: '"ltr"',
|
||||
description: (
|
||||
<Fragment>
|
||||
<p>Determines the direction of text and horizontal scrolling.</p>
|
||||
<ul>
|
||||
<li>ltr (default)</li>
|
||||
<li>rtl</li>
|
||||
</ul>
|
||||
<p>
|
||||
This property also automatically sets the{' '}
|
||||
<a
|
||||
href="https://developer.mozilla.org/en-US/docs/Web/CSS/direction"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
CSS <code>direction</code> style
|
||||
</a>{' '}
|
||||
for the grid component.
|
||||
</p>
|
||||
</Fragment>
|
||||
),
|
||||
name: 'direction',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
description: (
|
||||
<p>
|
||||
|
||||
@@ -58,17 +58,24 @@ const PROPS = [
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
defaultValue: '"vertical"',
|
||||
defaultValue: '"ltr"',
|
||||
description: (
|
||||
<Fragment>
|
||||
<p>Primary scroll direction of the list. Acceptable values are:</p>
|
||||
<p>Determines the direction of text and horizontal scrolling.</p>
|
||||
<ul>
|
||||
<li>vertical (default) - Up/down scrolling.</li>
|
||||
<li>horizontal - Left/right scrolling.</li>
|
||||
<li>ltr (default)</li>
|
||||
<li>rtl</li>
|
||||
</ul>
|
||||
<p>
|
||||
Note that lists may scroll in both directions (depending on CSS) but
|
||||
content will only be windowed in the primary direction.
|
||||
This property also automatically sets the{' '}
|
||||
<a
|
||||
href="https://developer.mozilla.org/en-US/docs/Web/CSS/direction"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
CSS <code>direction</code> style
|
||||
</a>{' '}
|
||||
for the list component.
|
||||
</p>
|
||||
</Fragment>
|
||||
),
|
||||
@@ -207,6 +214,24 @@ const PROPS = [
|
||||
name: 'itemSize',
|
||||
type: 'number',
|
||||
},
|
||||
{
|
||||
defaultValue: '"vertical"',
|
||||
description: (
|
||||
<Fragment>
|
||||
<p>Layout/orientation of the list. Acceptable values are:</p>
|
||||
<ul>
|
||||
<li>vertical (default) - Up/down scrolling.</li>
|
||||
<li>horizontal - Left/right scrolling.</li>
|
||||
</ul>
|
||||
<p>
|
||||
Note that lists may scroll in both directions (depending on CSS) but
|
||||
content will only be windowed in the layout direction specified.
|
||||
</p>
|
||||
</Fragment>
|
||||
),
|
||||
name: 'layout',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
description: (
|
||||
<Fragment>
|
||||
|
||||
@@ -200,7 +200,7 @@ export default class DynamicSizeList extends PureComponent {
|
||||
</button>
|
||||
<List
|
||||
className={styles.List}
|
||||
direction="horizontal"
|
||||
layout="horizontal"
|
||||
height={50}
|
||||
itemCount={items.length}
|
||||
itemData={showText}
|
||||
|
||||
@@ -54,11 +54,11 @@ export default function() {
|
||||
>
|
||||
<FixedSizeList
|
||||
className={styles.List}
|
||||
direction="horizontal"
|
||||
height={75}
|
||||
itemCount={1000}
|
||||
itemData="Column"
|
||||
itemSize={100}
|
||||
layout="horizontal"
|
||||
width={300}
|
||||
>
|
||||
{Item}
|
||||
|
||||
@@ -3,20 +3,19 @@ import React, { PureComponent, memo } from 'react';
|
||||
import { FixedSizeList, areEqual } from 'react-window';
|
||||
import CodeBlock from '../../components/CodeBlock';
|
||||
import ProfiledExample from '../../components/ProfiledExample';
|
||||
import { fillArray } from '../../utils';
|
||||
|
||||
import CODE from '../../code/MemoizedListItems.js';
|
||||
|
||||
import styles from './shared.module.css';
|
||||
|
||||
const generateItems = numItems =>
|
||||
Array(numItems)
|
||||
.fill(true)
|
||||
.map(_ => ({
|
||||
isActive: false,
|
||||
label: Math.random()
|
||||
.toString(36)
|
||||
.substr(2),
|
||||
}));
|
||||
fillArray(numItems, () => ({
|
||||
isActive: false,
|
||||
label: Math.random()
|
||||
.toString(36)
|
||||
.substr(2),
|
||||
}));
|
||||
|
||||
const Row = memo(({ data, index, style }) => {
|
||||
const { items, toggleItemActive } = data;
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { FixedSizeGrid, FixedSizeList } from 'react-window';
|
||||
import CodeBlock from '../../components/CodeBlock';
|
||||
import ProfiledExample from '../../components/ProfiledExample';
|
||||
|
||||
import CODE_GRID from '../../code/FixedSizeGridRtl.js';
|
||||
import CODE_LIST from '../../code/FixedSizeListHorizontalRtl.js';
|
||||
|
||||
import styles from './shared.module.css';
|
||||
|
||||
class Cell extends PureComponent {
|
||||
render() {
|
||||
const { columnIndex, rowIndex, style } = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
columnIndex % 2
|
||||
? rowIndex % 2 === 0
|
||||
? styles.GridItemOdd
|
||||
: styles.GridItemEven
|
||||
: rowIndex % 2
|
||||
? styles.GridItemOdd
|
||||
: styles.GridItemEven
|
||||
}
|
||||
style={style}
|
||||
>
|
||||
بند {rowIndex}, {columnIndex}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Item extends PureComponent {
|
||||
render() {
|
||||
const { index, style } = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={index % 2 ? styles.ListItemOdd : styles.ListItemEven}
|
||||
style={style}
|
||||
>
|
||||
عمود {index}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default function() {
|
||||
return (
|
||||
<div className={styles.ExampleWrapper}>
|
||||
<h1 className={styles.ExampleHeader}>RTL List</h1>
|
||||
<div className={styles.Example}>
|
||||
<ProfiledExample
|
||||
className={styles.ExampleDemo}
|
||||
sandbox="fixed-size-list-horizontal-rtl"
|
||||
>
|
||||
<FixedSizeList
|
||||
className={styles.List}
|
||||
direction="rtl"
|
||||
height={75}
|
||||
itemCount={1000}
|
||||
itemSize={100}
|
||||
layout="horizontal"
|
||||
width={300}
|
||||
>
|
||||
{Item}
|
||||
</FixedSizeList>
|
||||
</ProfiledExample>
|
||||
<div className={styles.ExampleCode}>
|
||||
<CodeBlock value={CODE_LIST} />
|
||||
</div>
|
||||
</div>
|
||||
<h1 className={styles.ExampleHeader}>RTL Grid</h1>
|
||||
<div className={styles.Example}>
|
||||
<ProfiledExample
|
||||
className={styles.ExampleDemo}
|
||||
sandbox="fixed-size-grid-rtl"
|
||||
>
|
||||
<FixedSizeGrid
|
||||
className={styles.Grid}
|
||||
columnCount={1000}
|
||||
columnWidth={100}
|
||||
direction="rtl"
|
||||
height={150}
|
||||
rowCount={1000}
|
||||
rowHeight={35}
|
||||
width={300}
|
||||
>
|
||||
{Cell}
|
||||
</FixedSizeGrid>
|
||||
</ProfiledExample>
|
||||
<div className={styles.ExampleCode}>
|
||||
<CodeBlock value={CODE_GRID} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -4,18 +4,15 @@ import { unstable_trace as trace } from 'scheduler/tracing';
|
||||
|
||||
import CodeBlock from '../../components/CodeBlock';
|
||||
import ProfiledExample from '../../components/ProfiledExample';
|
||||
import { fillArray } from '../../utils';
|
||||
|
||||
import CODE_GRID from '../../code/ScrollToItemGrid.js';
|
||||
import CODE_LIST from '../../code/ScrollToItemList.js';
|
||||
|
||||
import styles from './shared.module.css';
|
||||
|
||||
const columnWidths = new Array(1000)
|
||||
.fill(true)
|
||||
.map(() => 75 + Math.round(Math.random() * 50));
|
||||
const rowHeights = new Array(1000)
|
||||
.fill(true)
|
||||
.map(() => 25 + Math.round(Math.random() * 50));
|
||||
const columnWidths = fillArray(1000, () => 75 + Math.round(Math.random() * 50));
|
||||
const rowHeights = fillArray(1000, () => 25 + Math.round(Math.random() * 50));
|
||||
|
||||
class ListItemRenderer extends PureComponent {
|
||||
render() {
|
||||
|
||||
@@ -2,17 +2,14 @@ import React, { PureComponent } from 'react';
|
||||
import { VariableSizeGrid } from 'react-window';
|
||||
import CodeBlock from '../../components/CodeBlock';
|
||||
import ProfiledExample from '../../components/ProfiledExample';
|
||||
import { fillArray } from '../../utils';
|
||||
|
||||
import CODE from '../../code/VariableSizeGrid.js';
|
||||
|
||||
import styles from './shared.module.css';
|
||||
|
||||
const columnWidths = new Array(1000)
|
||||
.fill(true)
|
||||
.map(() => 75 + Math.round(Math.random() * 50));
|
||||
const rowHeights = new Array(1000)
|
||||
.fill(true)
|
||||
.map(() => 25 + Math.round(Math.random() * 50));
|
||||
const columnWidths = fillArray(1000, () => 75 + Math.round(Math.random() * 50));
|
||||
const rowHeights = fillArray(1000, () => 25 + Math.round(Math.random() * 50));
|
||||
|
||||
class Cell extends PureComponent {
|
||||
render() {
|
||||
|
||||
@@ -2,18 +2,15 @@ import React, { PureComponent } from 'react';
|
||||
import { VariableSizeList } from 'react-window';
|
||||
import CodeBlock from '../../components/CodeBlock';
|
||||
import ProfiledExample from '../../components/ProfiledExample';
|
||||
import { fillArray } from '../../utils';
|
||||
|
||||
import CODE_HORIZONTAL from '../../code/VariableSizeListHorizontal.js';
|
||||
import CODE_VERTICAL from '../../code/VariableSizeListVertical.js';
|
||||
|
||||
import styles from './shared.module.css';
|
||||
|
||||
const columnSizes = new Array(1000)
|
||||
.fill(true)
|
||||
.map(() => 75 + Math.round(Math.random() * 50));
|
||||
const rowSizes = new Array(1000)
|
||||
.fill(true)
|
||||
.map(() => 25 + Math.round(Math.random() * 50));
|
||||
const columnSizes = fillArray(1000, () => 75 + Math.round(Math.random() * 50));
|
||||
const rowSizes = fillArray(1000, () => 25 + Math.round(Math.random() * 50));
|
||||
|
||||
class Item extends PureComponent {
|
||||
render() {
|
||||
@@ -61,11 +58,11 @@ export default function() {
|
||||
>
|
||||
<VariableSizeList
|
||||
className={styles.List}
|
||||
direction="horizontal"
|
||||
height={75}
|
||||
itemCount={1000}
|
||||
itemData="Column"
|
||||
itemSize={index => columnSizes[index]}
|
||||
layout="horizontal"
|
||||
width={300}
|
||||
>
|
||||
{Item}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export function fillArray(length, fillFunction): Array {
|
||||
const array = new Array(length);
|
||||
for (let i = 0; i < length; i++) {
|
||||
array[i] = fillFunction(i);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
+4
-3
@@ -6167,9 +6167,10 @@ mem@^1.1.0:
|
||||
dependencies:
|
||||
mimic-fn "^1.0.0"
|
||||
|
||||
memoize-one@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-3.1.1.tgz#ef609811e3bc28970eac2884eece64d167830d17"
|
||||
memoize-one@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.0.0.tgz#d55007dffefb8de7546659a1722a5d42e128286e"
|
||||
integrity sha512-7g0+ejkOaI9w5x6LvQwmj68kUj6rxROywPSCqmclG/HBacmFnZqhVscQ8kovkn9FBCNJmOz6SY42+jnvZzDWdw==
|
||||
|
||||
memory-fs@^0.4.0, memory-fs@~0.4.1:
|
||||
version "0.4.1"
|
||||
|
||||
@@ -586,10 +586,12 @@
|
||||
"@types/estree@0.0.39":
|
||||
version "0.0.39"
|
||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
|
||||
integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
|
||||
|
||||
"@types/node@*":
|
||||
version "10.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.1.2.tgz#1b928a0baa408fc8ae3ac012cc81375addc147c6"
|
||||
"@types/node@^11.9.5":
|
||||
version "11.10.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-11.10.4.tgz#3f5fc4f0f322805f009e00ab35a2ff3d6b778e42"
|
||||
integrity sha512-wa09itaLE8L705aXd8F80jnFpxz3Y1/KRHfKsYL2bPc0XF+wEWu8sR9n5bmeu8Ba1N9z2GRNzm/YdHcghLkLKg==
|
||||
|
||||
abab@^1.0.3, abab@^1.0.4:
|
||||
version "1.0.4"
|
||||
@@ -638,10 +640,15 @@ acorn@^4.0.3, acorn@^4.0.4:
|
||||
version "4.0.13"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787"
|
||||
|
||||
acorn@^5.0.0, acorn@^5.2.1, acorn@^5.3.0, acorn@^5.5.0:
|
||||
acorn@^5.0.0, acorn@^5.3.0, acorn@^5.5.0:
|
||||
version "5.5.3"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.5.3.tgz#f473dd47e0277a08e28e9bec5aeeb04751f0b8c9"
|
||||
|
||||
acorn@^6.1.1:
|
||||
version "6.1.1"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.1.1.tgz#7d25ae05bb8ad1f9b699108e1094ecd7884adc1f"
|
||||
integrity sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==
|
||||
|
||||
address@1.0.3, address@^1.0.1:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/address/-/address-1.0.3.tgz#b5f50631f8d6cec8bd20c963963afb55e06cbce9"
|
||||
@@ -1888,9 +1895,10 @@ builtin-modules@^1.0.0, builtin-modules@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
|
||||
|
||||
builtin-modules@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-2.0.0.tgz#60b7ef5ae6546bd7deefa74b08b62a43a232648e"
|
||||
builtin-modules@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.0.0.tgz#1e587d44b006620d90286cc7a9238bbc6129cab1"
|
||||
integrity sha512-hMIeU4K2ilbXV6Uv93ZZ0Avg/M91RaKXucQ+4me2Do1txxBDyDZWCBa5bJSLqoNTRpXTLwEzIk1KmloenDDjhg==
|
||||
|
||||
builtin-status-codes@^3.0.0:
|
||||
version "3.0.0"
|
||||
@@ -3293,14 +3301,15 @@ estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
|
||||
|
||||
estree-walker@^0.3.0:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.3.1.tgz#e6b1a51cf7292524e7237c312e5fe6660c1ce1aa"
|
||||
|
||||
estree-walker@^0.5.0, estree-walker@^0.5.2:
|
||||
estree-walker@^0.5.2:
|
||||
version "0.5.2"
|
||||
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.5.2.tgz#d3850be7529c9580d815600b53126515e146dd39"
|
||||
|
||||
estree-walker@^0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.0.tgz#5d865327c44a618dde5699f763891ae31f257dae"
|
||||
integrity sha512-peq1RfVAVzr3PU/jL31RaOjUKLoZJpObQWJJ+LgfcxDUifyLZ1RjPQZTl0pzj2uJ45b7A7XpyppXvxdEqzo4rw==
|
||||
|
||||
esutils@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
|
||||
@@ -5543,11 +5552,12 @@ macaddress@^0.2.8:
|
||||
version "0.2.8"
|
||||
resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12"
|
||||
|
||||
magic-string@^0.22.4:
|
||||
version "0.22.5"
|
||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.22.5.tgz#8e9cf5afddf44385c1da5bc2a6a0dbd10b03657e"
|
||||
magic-string@^0.25.1:
|
||||
version "0.25.2"
|
||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.2.tgz#139c3a729515ec55e96e69e82a11fe890a293ad9"
|
||||
integrity sha512-iLs9mPjh9IuTtRsqqhNGYcZXGei0Nh/A4xirrsqW7c+QhKVFL2vm7U09ru6cHRD22azaP/wMDgI+HCqbETMTtg==
|
||||
dependencies:
|
||||
vlq "^0.2.2"
|
||||
sourcemap-codec "^1.4.4"
|
||||
|
||||
make-dir@^1.0.0:
|
||||
version "1.2.0"
|
||||
@@ -5596,9 +5606,10 @@ mem@^1.1.0:
|
||||
dependencies:
|
||||
mimic-fn "^1.0.0"
|
||||
|
||||
memoize-one@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-3.1.1.tgz#ef609811e3bc28970eac2884eece64d167830d17"
|
||||
"memoize-one@>=3.1.1 <6":
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.0.0.tgz#d55007dffefb8de7546659a1722a5d42e128286e"
|
||||
integrity sha512-7g0+ejkOaI9w5x6LvQwmj68kUj6rxROywPSCqmclG/HBacmFnZqhVscQ8kovkn9FBCNJmOz6SY42+jnvZzDWdw==
|
||||
|
||||
memory-fs@^0.4.0, memory-fs@~0.4.1:
|
||||
version "0.4.1"
|
||||
@@ -5652,7 +5663,7 @@ micromatch@^2.1.5, micromatch@^2.3.11:
|
||||
parse-glob "^3.0.4"
|
||||
regex-cache "^0.4.2"
|
||||
|
||||
micromatch@^3.1.4, micromatch@^3.1.8:
|
||||
micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.8:
|
||||
version "3.1.10"
|
||||
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
|
||||
dependencies:
|
||||
@@ -6273,6 +6284,11 @@ path-parse@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1"
|
||||
|
||||
path-parse@^1.0.6:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
|
||||
integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
|
||||
|
||||
path-to-regexp@0.1.7:
|
||||
version "0.1.7"
|
||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
|
||||
@@ -7297,7 +7313,14 @@ resolve@1.6.0:
|
||||
dependencies:
|
||||
path-parse "^1.0.5"
|
||||
|
||||
resolve@^1.1.6, resolve@^1.3.2, resolve@^1.3.3, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.6.0:
|
||||
resolve@^1.10.0:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba"
|
||||
integrity sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==
|
||||
dependencies:
|
||||
path-parse "^1.0.6"
|
||||
|
||||
resolve@^1.3.2, resolve@^1.3.3, resolve@^1.5.0, resolve@^1.6.0:
|
||||
version "1.7.1"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3"
|
||||
dependencies:
|
||||
@@ -7346,37 +7369,32 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
|
||||
hash-base "^3.0.0"
|
||||
inherits "^2.0.1"
|
||||
|
||||
rollup-plugin-babel@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/rollup-plugin-babel/-/rollup-plugin-babel-4.0.2.tgz#c073eeb0cc246324e6f6feaedbb90093841a138c"
|
||||
rollup-plugin-babel@^4.3.2:
|
||||
version "4.3.2"
|
||||
resolved "https://registry.yarnpkg.com/rollup-plugin-babel/-/rollup-plugin-babel-4.3.2.tgz#8c0e1bd7aa9826e90769cf76895007098ffd1413"
|
||||
integrity sha512-KfnizE258L/4enADKX61ozfwGHoqYauvoofghFJBhFnpH9Sb9dNPpWg8QHOaAfVASUYV8w0mCx430i9z0LJoJg==
|
||||
dependencies:
|
||||
"@babel/helper-module-imports" "^7.0.0"
|
||||
rollup-pluginutils "^2.3.0"
|
||||
|
||||
rollup-plugin-commonjs@^8.2.1:
|
||||
version "8.4.1"
|
||||
resolved "https://registry.yarnpkg.com/rollup-plugin-commonjs/-/rollup-plugin-commonjs-8.4.1.tgz#5c9cea2b2c3de322f5fbccd147e07ed5e502d7a0"
|
||||
rollup-plugin-commonjs@^9.2.1:
|
||||
version "9.2.1"
|
||||
resolved "https://registry.yarnpkg.com/rollup-plugin-commonjs/-/rollup-plugin-commonjs-9.2.1.tgz#bb151ca8fa23600c7a03e25f9f0a45b1ee922dac"
|
||||
integrity sha512-X0A/Cp/t+zbONFinBhiTZrfuUaVwRIp4xsbKq/2ohA2CDULa/7ONSJTelqxon+Vds2R2t2qJTqJQucKUC8GKkw==
|
||||
dependencies:
|
||||
acorn "^5.2.1"
|
||||
estree-walker "^0.5.0"
|
||||
magic-string "^0.22.4"
|
||||
resolve "^1.4.0"
|
||||
rollup-pluginutils "^2.0.1"
|
||||
estree-walker "^0.5.2"
|
||||
magic-string "^0.25.1"
|
||||
resolve "^1.10.0"
|
||||
rollup-pluginutils "^2.3.3"
|
||||
|
||||
rollup-plugin-node-resolve@^3.0.2:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-3.3.0.tgz#c26d110a36812cbefa7ce117cadcd3439aa1c713"
|
||||
rollup-plugin-node-resolve@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-4.0.1.tgz#f95765d174e5daeef9ea6268566141f53aa9d422"
|
||||
integrity sha512-fSS7YDuCe0gYqKsr5OvxMloeZYUSgN43Ypi1WeRZzQcWtHgFayV5tUSPYpxuaioIIWaBXl6NrVk0T2/sKwueLg==
|
||||
dependencies:
|
||||
builtin-modules "^2.0.0"
|
||||
builtin-modules "^3.0.0"
|
||||
is-module "^1.0.0"
|
||||
resolve "^1.1.6"
|
||||
|
||||
rollup-pluginutils@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.0.1.tgz#7ec95b3573f6543a46a6461bd9a7c544525d0fc0"
|
||||
dependencies:
|
||||
estree-walker "^0.3.0"
|
||||
micromatch "^2.3.11"
|
||||
resolve "^1.10.0"
|
||||
|
||||
rollup-pluginutils@^2.3.0:
|
||||
version "2.3.1"
|
||||
@@ -7385,12 +7403,22 @@ rollup-pluginutils@^2.3.0:
|
||||
estree-walker "^0.5.2"
|
||||
micromatch "^2.3.11"
|
||||
|
||||
rollup@^0.65.0:
|
||||
version "0.65.0"
|
||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-0.65.0.tgz#280db1252169b68fc3043028346b337dde453fba"
|
||||
rollup-pluginutils@^2.3.3:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.4.1.tgz#de43ab54965bbf47843599a7f3adceb723de38db"
|
||||
integrity sha512-wesMQ9/172IJDIW/lYWm0vW0LiKe5Ekjws481R7z9WTRtmO59cqyM/2uUlxvf6yzm/fElFmHUobeQOYz46dZJw==
|
||||
dependencies:
|
||||
estree-walker "^0.6.0"
|
||||
micromatch "^3.1.10"
|
||||
|
||||
rollup@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.4.1.tgz#cc03ef6fb49dd72a878e3da0131c0a3696de14a7"
|
||||
integrity sha512-YWf5zeR6SWtqZmCnuYs4a+ZJetj8NT4yfBMPXekWHW4L3144jM+J2AWagQVejB0FwCqjEUP9l8o4hg1rPDfQlg==
|
||||
dependencies:
|
||||
"@types/estree" "0.0.39"
|
||||
"@types/node" "*"
|
||||
"@types/node" "^11.9.5"
|
||||
acorn "^6.1.1"
|
||||
|
||||
run-async@^2.2.0:
|
||||
version "2.3.0"
|
||||
@@ -7717,6 +7745,11 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
|
||||
sourcemap-codec@^1.4.4:
|
||||
version "1.4.4"
|
||||
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.4.tgz#c63ea927c029dd6bd9a2b7fa03b3fec02ad56e9f"
|
||||
integrity sha512-CYAPYdBu34781kLHkaW3m6b/uUSyMOC2R61gcYMWooeuaGtjof86ZA/8T+qVPPt7np1085CR9hmMGrySwEc8Xg==
|
||||
|
||||
spdx-correct@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.0.0.tgz#05a5b4d7153a195bc92c3c425b69f3b2a9524c82"
|
||||
@@ -8423,10 +8456,6 @@ verror@1.10.0:
|
||||
core-util-is "1.0.2"
|
||||
extsprintf "^1.2.0"
|
||||
|
||||
vlq@^0.2.2:
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.3.tgz#8f3e4328cf63b1540c0d67e1b2778386f8975b26"
|
||||
|
||||
vm-browserify@0.0.4:
|
||||
version "0.0.4"
|
||||
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73"
|
||||
|
||||
Reference in New Issue
Block a user