Merge master

This commit is contained in:
Damian Pieczynski
2019-04-02 19:33:08 +02:00
parent ef4ab16401
commit e69da68967
41 changed files with 1239 additions and 244 deletions
+16
View File
@@ -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
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2018 Brian Vaughn
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+41 -2
View File
@@ -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 &lt;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
View File
@@ -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
View File
@@ -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
View File
@@ -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,
+8 -2
View File
@@ -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)
);
+8 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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. ' +
+135 -2
View File
@@ -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');
});
});
+42 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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. ' +
+22
View File
@@ -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}
+6
View File
@@ -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}
+21
View File
@@ -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>
);
+1 -1
View File
@@ -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}
+1 -1
View File
@@ -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();
+25
View File
@@ -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>
+31 -6
View File
@@ -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}
+1 -1
View File
@@ -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;
+99
View File
@@ -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>
);
}
+3 -6
View File
@@ -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}
+7
View File
@@ -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
View File
@@ -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"
+81 -52
View File
@@ -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"