From cdf845486946654352fbf058a73de1c9df70a746 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 1 Mar 2019 12:09:59 +0100 Subject: [PATCH 01/20] Add RTL support for List component --- src/createListComponent.js | 7 +- website/src/App.js | 6 ++ .../src/code/FixedSizeListHorizontalRtl.js | 18 +++++ website/src/code/FixedSizeListVerticalRtl.js | 17 +++++ .../src/routes/examples/FixedSizeListRtl.js | 75 +++++++++++++++++++ 5 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 website/src/code/FixedSizeListHorizontalRtl.js create mode 100644 website/src/code/FixedSizeListVerticalRtl.js create mode 100644 website/src/routes/examples/FixedSizeListRtl.js diff --git a/src/createListComponent.js b/src/createListComponent.js index daa34625..7b59c364 100644 --- a/src/createListComponent.js +++ b/src/createListComponent.js @@ -396,6 +396,7 @@ export default function createListComponent({ itemStyleCache[index] = style = { position: 'absolute', left: direction === 'horizontal' ? offset : 0, + right: direction === 'horizontal' ? offset : 0, top: direction === 'vertical' ? offset : 0, height: direction === 'vertical' ? size : '100%', width: direction === 'horizontal' ? size : '100%', @@ -448,7 +449,7 @@ export default function createListComponent({ } _onScrollHorizontal = (event: ScrollEvent): void => { - const { scrollLeft } = event.currentTarget; + const { scrollLeft, scrollWidth, clientWidth } = event.currentTarget; this.setState(prevState => { if (prevState.scrollOffset === scrollLeft) { // Scroll position may have been updated by cDM/cDU, @@ -457,11 +458,13 @@ export default function createListComponent({ return null; } + const isRtl = this.props.style && this.props.style.direction === 'rtl'; + return { isScrolling: true, scrollDirection: prevState.scrollOffset < scrollLeft ? 'forward' : 'backward', - scrollOffset: scrollLeft, + scrollOffset: isRtl ? scrollWidth - clientWidth - scrollLeft : scrollLeft, scrollUpdateWasRequested: false, }; }, this._resetIsScrollingDebounced); diff --git a/website/src/App.js b/website/src/App.js index d24571cc..8b91285d 100644 --- a/website/src/App.js +++ b/website/src/App.js @@ -14,6 +14,7 @@ import FixedSizeGridApi from './routes/api/FixedSizeGrid'; import FixedSizeListApi from './routes/api/FixedSizeList'; import FixedSizeGridExample from './routes/examples/FixedSizeGrid'; import FixedSizeListExample from './routes/examples/FixedSizeList'; +import FixedSizeListExampleRtl from './routes/examples/FixedSizeListRtl'; import ListWithScrollingIndicatorExample from './routes/examples/ListWithScrollingIndicator'; import ScrollToItemExample from './routes/examples/ScrollToItem'; import MemoizedListItemsExample from './routes/examples/MemoizedListItemsExample'; @@ -95,6 +96,11 @@ const EXAMPLE_ROUTES = [ title: 'Memoized List items', component: MemoizedListItemsExample, }, + { + path: '/examples/list/fixed-size-rtl', + title: 'Fixed Size List RTL', + component: FixedSizeListExampleRtl, + }, ]; const COMPONENTS_ROUTES = [ diff --git a/website/src/code/FixedSizeListHorizontalRtl.js b/website/src/code/FixedSizeListHorizontalRtl.js new file mode 100644 index 00000000..12132df1 --- /dev/null +++ b/website/src/code/FixedSizeListHorizontalRtl.js @@ -0,0 +1,18 @@ +import { FixedSizeList as List } from 'react-window'; + +const Column = ({ index, style }) => ( +
Column {index}
+); + +const Example = () => ( + + {Column} + +); diff --git a/website/src/code/FixedSizeListVerticalRtl.js b/website/src/code/FixedSizeListVerticalRtl.js new file mode 100644 index 00000000..8b83e529 --- /dev/null +++ b/website/src/code/FixedSizeListVerticalRtl.js @@ -0,0 +1,17 @@ +import { FixedSizeList as List } from 'react-window'; + +const Row = ({ index, style }) => ( +
Row {index}
+); + +const Example = () => ( + + {Row} + +); diff --git a/website/src/routes/examples/FixedSizeListRtl.js b/website/src/routes/examples/FixedSizeListRtl.js new file mode 100644 index 00000000..eb79071d --- /dev/null +++ b/website/src/routes/examples/FixedSizeListRtl.js @@ -0,0 +1,75 @@ +import React, { PureComponent } from 'react'; +import { FixedSizeList } from 'react-window'; +import CodeBlock from '../../components/CodeBlock'; +import ProfiledExample from '../../components/ProfiledExample'; + +import CODE_HORIZONTAL from '../../code/FixedSizeListHorizontalRtl.js'; +import CODE_VERTICAL from '../../code/FixedSizeListVerticalRtl.js'; + +import styles from './shared.module.css'; + +class Item extends PureComponent { + render() { + const { data, index, style } = this.props; + + return ( +
+ {data} {index} +
+ ); + } +} + +export default function() { + return ( +
+

Basic List

+
+ + + {Item} + + +
+ +
+
+
+ + + {Item} + + +
+ +
+
+
+ ); +} From ebdb13141b6ae8dfdedced24a36ff65d933b02e9 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 1 Mar 2019 12:58:09 +0100 Subject: [PATCH 02/20] Fix lint error --- src/createListComponent.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/createListComponent.js b/src/createListComponent.js index 7b59c364..8acb6e7a 100644 --- a/src/createListComponent.js +++ b/src/createListComponent.js @@ -464,7 +464,9 @@ export default function createListComponent({ isScrolling: true, scrollDirection: prevState.scrollOffset < scrollLeft ? 'forward' : 'backward', - scrollOffset: isRtl ? scrollWidth - clientWidth - scrollLeft : scrollLeft, + scrollOffset: isRtl + ? scrollWidth - clientWidth - scrollLeft + : scrollLeft, scrollUpdateWasRequested: false, }; }, this._resetIsScrollingDebounced); From 97bd7e0c89d0f59b76eca22d6c958742c9e12f38 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Sat, 2 Mar 2019 11:12:34 -0800 Subject: [PATCH 03/20] Grid scrollToItem() takes scrollbar size into consideration --- src/FixedSizeGrid.js | 10 +++++-- src/VariableSizeGrid.js | 20 +++++++++---- src/__tests__/FixedSizeGrid.js | 50 ++++++++++++++++++++++++++++++- src/__tests__/VariableSizeGrid.js | 48 ++++++++++++++++++++++++++++- src/createGridComponent.js | 11 +++++-- src/domHelpers.js | 23 ++++++++++++++ 6 files changed, 149 insertions(+), 13 deletions(-) create mode 100644 src/domHelpers.js diff --git a/src/FixedSizeGrid.js b/src/FixedSizeGrid.js index f446acb8..6224f71d 100644 --- a/src/FixedSizeGrid.js +++ b/src/FixedSizeGrid.js @@ -27,7 +27,9 @@ const FixedSizeGrid = createGridComponent({ { columnCount, columnWidth, width }: Props, 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, 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) ); diff --git a/src/VariableSizeGrid.js b/src/VariableSizeGrid.js index be5a3c9d..061b3064 100644 --- a/src/VariableSizeGrid.js +++ b/src/VariableSizeGrid.js @@ -233,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); @@ -249,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': @@ -324,7 +328,8 @@ const VariableSizeGrid = createGridComponent({ index: number, align: ScrollToAlign, scrollOffset: number, - instanceProps: InstanceProps + instanceProps: InstanceProps, + scrollbarSize: number ): number => getOffsetForIndexAndAlignment( 'column', @@ -332,7 +337,8 @@ const VariableSizeGrid = createGridComponent({ index, align, scrollOffset, - instanceProps + instanceProps, + scrollbarSize ), getOffsetForRowAndAlignment: ( @@ -340,7 +346,8 @@ const VariableSizeGrid = createGridComponent({ index: number, align: ScrollToAlign, scrollOffset: number, - instanceProps: InstanceProps + instanceProps: InstanceProps, + scrollbarSize: number ): number => getOffsetForIndexAndAlignment( 'row', @@ -348,7 +355,8 @@ const VariableSizeGrid = createGridComponent({ index, align, scrollOffset, - instanceProps + instanceProps, + scrollbarSize ), getRowOffset: ( diff --git a/src/__tests__/FixedSizeGrid.js b/src/__tests__/FixedSizeGrid.js index 14e0de1f..83754f76 100644 --- a/src/__tests__/FixedSizeGrid.js +++ b/src/__tests__/FixedSizeGrid.js @@ -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 }) => ( @@ -512,6 +516,50 @@ 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( + + ); + + 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', + }); + }); }); // onItemsRendered is pretty well covered by other snapshot tests diff --git a/src/__tests__/VariableSizeGrid.js b/src/__tests__/VariableSizeGrid.js index fb3d76a8..653d025d 100644 --- a/src/__tests__/VariableSizeGrid.js +++ b/src/__tests__/VariableSizeGrid.js @@ -3,6 +3,7 @@ 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; @@ -13,7 +14,12 @@ const simulateScroll = (instance, { scrollLeft, scrollTop }) => { 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. @@ -34,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 }) => (
{JSON.stringify(rest, null, 2)}
)); @@ -255,6 +264,43 @@ 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( + + ); + + 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', + }); + }); }); describe('resetAfterIndex method', () => { diff --git a/src/createGridComponent.js b/src/createGridComponent.js index 4e8fbb82..a2e98d30 100644 --- a/src/createGridComponent.js +++ b/src/createGridComponent.js @@ -3,6 +3,7 @@ import memoizeOne from 'memoize-one'; import { createElement, PureComponent } from 'react'; import { cancelTimeout, requestTimeout } from './timer'; +import { getScrollbarSize } from './domHelpers'; import type { TimeoutID } from './timer'; @@ -101,7 +102,8 @@ type GetOffsetForItemAndAlignment = ( index: number, align: ScrollToAlign, scrollOffset: number, - instanceProps: any + instanceProps: any, + scrollbarSize: number ) => number; type GetStartIndexForOffset = ( props: Props, @@ -233,6 +235,7 @@ export default function createGridComponent({ rowIndex: number, }): void { const { scrollLeft, scrollTop } = this.state; + const scrollbarSize = getScrollbarSize(); this.scrollTo({ scrollLeft: getOffsetForColumnAndAlignment( @@ -240,14 +243,16 @@ export default function createGridComponent({ columnIndex, align, scrollLeft, - this._instanceProps + this._instanceProps, + scrollbarSize ), scrollTop: getOffsetForRowAndAlignment( this.props, rowIndex, align, scrollTop, - this._instanceProps + this._instanceProps, + scrollbarSize ), }); } diff --git a/src/domHelpers.js b/src/domHelpers.js new file mode 100644 index 00000000..0f3c1925 --- /dev/null +++ b/src/domHelpers.js @@ -0,0 +1,23 @@ +// @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'); + div.style.position = 'absolute'; + div.style.top = '-9999px'; + div.style.width = '50px'; + div.style.height = '50px'; + div.style.overflow = 'scroll'; + + ((document.body: any): HTMLBodyElement).appendChild(div); + + size = div.offsetWidth - div.clientWidth; + + ((document.body: any): HTMLBodyElement).removeChild(div); + } + + return size; +} From c747a2989c88f6480f311e7e7ded5e73569e916d Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Sat, 2 Mar 2019 21:41:45 -0800 Subject: [PATCH 04/20] Only count scrollbar size in scrollToItem() when scrolling is required --- src/__tests__/FixedSizeGrid.js | 59 +++++++++++++++++++++++++++++++ src/__tests__/VariableSizeGrid.js | 47 ++++++++++++++++++++++++ src/createGridComponent.js | 20 +++++++++-- 3 files changed, 124 insertions(+), 2 deletions(-) diff --git a/src/__tests__/FixedSizeGrid.js b/src/__tests__/FixedSizeGrid.js index 83754f76..c55c8d93 100644 --- a/src/__tests__/FixedSizeGrid.js +++ b/src/__tests__/FixedSizeGrid.js @@ -560,6 +560,65 @@ describe('FixedSizeGrid', () => { 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( + + ); + + 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( + + ); + + 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 diff --git a/src/__tests__/VariableSizeGrid.js b/src/__tests__/VariableSizeGrid.js index 653d025d..de665b13 100644 --- a/src/__tests__/VariableSizeGrid.js +++ b/src/__tests__/VariableSizeGrid.js @@ -301,6 +301,53 @@ describe('VariableSizeGrid', () => { 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( + + ); + + 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( + + ); + + 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', () => { diff --git a/src/createGridComponent.js b/src/createGridComponent.js index a2e98d30..039eede4 100644 --- a/src/createGridComponent.js +++ b/src/createGridComponent.js @@ -234,9 +234,25 @@ 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( this.props, @@ -244,7 +260,7 @@ export default function createGridComponent({ align, scrollLeft, this._instanceProps, - scrollbarSize + verticalScrollbarSize ), scrollTop: getOffsetForRowAndAlignment( this.props, @@ -252,7 +268,7 @@ export default function createGridComponent({ align, scrollTop, this._instanceProps, - scrollbarSize + horizontalScrollbarSize ), }); } From a42c2fba9649b9323d9ca19bf1b4e2ef37be895b Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Sun, 3 Mar 2019 12:54:31 -0800 Subject: [PATCH 05/20] Prettier --- src/createGridComponent.js | 6 ++++-- website/src/routes/examples/FixedSizeListRtl.js | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/createGridComponent.js b/src/createGridComponent.js index 039eede4..f71eea27 100644 --- a/src/createGridComponent.js +++ b/src/createGridComponent.js @@ -250,8 +250,10 @@ export default function createGridComponent({ // 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; + const horizontalScrollbarSize = + estimatedTotalWidth > width ? scrollbarSize : 0; + const verticalScrollbarSize = + estimatedTotalHeight > height ? scrollbarSize : 0; this.scrollTo({ scrollLeft: getOffsetForColumnAndAlignment( diff --git a/website/src/routes/examples/FixedSizeListRtl.js b/website/src/routes/examples/FixedSizeListRtl.js index eb79071d..3a495321 100644 --- a/website/src/routes/examples/FixedSizeListRtl.js +++ b/website/src/routes/examples/FixedSizeListRtl.js @@ -39,7 +39,7 @@ export default function() { itemData="صف" itemSize={35} width={300} - style={{ direction: "rtl" }} + style={{ direction: 'rtl' }} > {Item} @@ -61,7 +61,7 @@ export default function() { itemData="عمود" itemSize={100} width={300} - style={{ direction: "rtl" }} + style={{ direction: 'rtl' }} > {Item} From 112f102f3ae53d96c9603feb1c06575dfa9b9479 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Sun, 3 Mar 2019 13:57:21 -0800 Subject: [PATCH 06/20] Renamed FixedSizeListRtl to RTLLayout --- website/src/App.js | 6 ++-- .../src/code/FixedSizeListHorizontalRtl.js | 4 +-- website/src/code/FixedSizeListVerticalRtl.js | 17 ----------- .../{FixedSizeListRtl.js => RTLLayout.js} | 30 +++---------------- 4 files changed, 9 insertions(+), 48 deletions(-) delete mode 100644 website/src/code/FixedSizeListVerticalRtl.js rename website/src/routes/examples/{FixedSizeListRtl.js => RTLLayout.js} (61%) diff --git a/website/src/App.js b/website/src/App.js index 8b91285d..3a7ff7fb 100644 --- a/website/src/App.js +++ b/website/src/App.js @@ -14,7 +14,7 @@ import FixedSizeGridApi from './routes/api/FixedSizeGrid'; import FixedSizeListApi from './routes/api/FixedSizeList'; import FixedSizeGridExample from './routes/examples/FixedSizeGrid'; import FixedSizeListExample from './routes/examples/FixedSizeList'; -import FixedSizeListExampleRtl from './routes/examples/FixedSizeListRtl'; +import RTLLayoutExample from './routes/examples/RTLLayout'; import ListWithScrollingIndicatorExample from './routes/examples/ListWithScrollingIndicator'; import ScrollToItemExample from './routes/examples/ScrollToItem'; import MemoizedListItemsExample from './routes/examples/MemoizedListItemsExample'; @@ -98,8 +98,8 @@ const EXAMPLE_ROUTES = [ }, { path: '/examples/list/fixed-size-rtl', - title: 'Fixed Size List RTL', - component: FixedSizeListExampleRtl, + title: 'RTL layout', + component: RTLLayoutExample, }, ]; diff --git a/website/src/code/FixedSizeListHorizontalRtl.js b/website/src/code/FixedSizeListHorizontalRtl.js index 12132df1..e1073aa3 100644 --- a/website/src/code/FixedSizeListHorizontalRtl.js +++ b/website/src/code/FixedSizeListHorizontalRtl.js @@ -6,12 +6,12 @@ const Column = ({ index, style }) => ( const Example = () => ( {Column} diff --git a/website/src/code/FixedSizeListVerticalRtl.js b/website/src/code/FixedSizeListVerticalRtl.js deleted file mode 100644 index 8b83e529..00000000 --- a/website/src/code/FixedSizeListVerticalRtl.js +++ /dev/null @@ -1,17 +0,0 @@ -import { FixedSizeList as List } from 'react-window'; - -const Row = ({ index, style }) => ( -
Row {index}
-); - -const Example = () => ( - - {Row} - -); diff --git a/website/src/routes/examples/FixedSizeListRtl.js b/website/src/routes/examples/RTLLayout.js similarity index 61% rename from website/src/routes/examples/FixedSizeListRtl.js rename to website/src/routes/examples/RTLLayout.js index 3a495321..4891b73e 100644 --- a/website/src/routes/examples/FixedSizeListRtl.js +++ b/website/src/routes/examples/RTLLayout.js @@ -4,20 +4,19 @@ import CodeBlock from '../../components/CodeBlock'; import ProfiledExample from '../../components/ProfiledExample'; import CODE_HORIZONTAL from '../../code/FixedSizeListHorizontalRtl.js'; -import CODE_VERTICAL from '../../code/FixedSizeListVerticalRtl.js'; import styles from './shared.module.css'; class Item extends PureComponent { render() { - const { data, index, style } = this.props; + const { index, style } = this.props; return (
- {data} {index} + عمود {index}
); } @@ -27,27 +26,6 @@ export default function() { return (

Basic List

-
- - - {Item} - - -
- -
-
From 98c7ea733a969db78b7a5edb503ff330a7ebdcac Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Sun, 3 Mar 2019 14:20:28 -0800 Subject: [PATCH 07/20] Deprecate list direction=horizontal|vertical in favor of direction=ltr|rtl and separate layout=horizontal|vertical --- src/FixedSizeList.js | 12 +- src/VariableSizeList.js | 12 +- src/__tests__/FixedSizeGrid.js | 8 +- src/__tests__/FixedSizeList.js | 73 +++++++++-- src/createListComponent.js | 115 ++++++++++++------ .../fixed-size-list-horizontal/index.js | 2 +- .../variable-size-list-horizontal/index.js | 2 +- website/src/code/FixedSizeListHorizontal.js | 2 +- .../src/code/FixedSizeListHorizontalRtl.js | 2 +- .../src/code/VariableSizeListHorizontal.js | 2 +- website/src/routes/api/FixedSizeList.js | 30 +++-- website/src/routes/examples/FixedSizeList.js | 2 +- website/src/routes/examples/RTLLayout.js | 1 - .../src/routes/examples/VariableSizeList.js | 2 +- 14 files changed, 187 insertions(+), 78 deletions(-) diff --git a/src/FixedSizeList.js b/src/FixedSizeList.js index 7491d91d..498a8c9b 100644 --- a/src/FixedSizeList.js +++ b/src/FixedSizeList.js @@ -15,12 +15,14 @@ const FixedSizeList = createListComponent({ ((itemSize: any): number) * itemCount, getOffsetForIndexAndAlignment: ( - { direction, height, itemCount, itemSize, width }: Props, + { direction, height, itemCount, itemSize, layout, width }: Props, 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, + { direction, height, itemCount, itemSize, layout, width }: Props, startIndex: number, scrollOffset: number ): number => { + // TODO Deprecate direction "horizontal" + const isHorizontal = direction === 'horizontal' || layout === 'horizontal'; const offset = startIndex * ((itemSize: any): number); - const size = (((direction === 'horizontal' ? width : height): any): number); + const size = (((isHorizontal ? width : height): any): number); return Math.max( 0, Math.min( diff --git a/src/VariableSizeList.js b/src/VariableSizeList.js index 75188c2c..806beac1 100644 --- a/src/VariableSizeList.js +++ b/src/VariableSizeList.js @@ -185,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, @@ -234,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; diff --git a/src/__tests__/FixedSizeGrid.js b/src/__tests__/FixedSizeGrid.js index c55c8d93..ea6fd06b 100644 --- a/src/__tests__/FixedSizeGrid.js +++ b/src/__tests__/FixedSizeGrid.js @@ -924,7 +924,7 @@ describe('FixedSizeGrid', () => { it('should fail if a string height is provided', () => { expect(() => ReactTestRenderer.create( - + ) ).toThrow( 'An invalid "height" prop has been specified. ' + @@ -936,11 +936,7 @@ describe('FixedSizeGrid', () => { it('should fail if a string width is provided', () => { expect(() => ReactTestRenderer.create( - + ) ).toThrow( 'An invalid "width" prop has been specified. ' + diff --git a/src/__tests__/FixedSizeList.js b/src/__tests__/FixedSizeList.js index d71b9f39..6e1eb1db 100644 --- a/src/__tests__/FixedSizeList.js +++ b/src/__tests__/FixedSizeList.js @@ -58,13 +58,32 @@ describe('FixedSizeList', () => { it('should render a list of columns', () => { ReactTestRenderer.create( - + ); expect(itemRenderer).toHaveBeenCalledTimes(5); expect(onItemsRendered.mock.calls).toMatchSnapshot(); }); + it('should re-render items if layout changes', () => { + const rendered = ReactTestRenderer.create( + + ); + expect(itemRenderer).toHaveBeenCalled(); + itemRenderer.mockClear(); + + // Re-rendering should not affect pure sCU children: + rendered.update(); + expect(itemRenderer).not.toHaveBeenCalled(); + + // Re-rendering with new layout should re-render children: + rendered.update(); + 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( ); @@ -75,7 +94,7 @@ describe('FixedSizeList', () => { rendered.update(); expect(itemRenderer).not.toHaveBeenCalled(); - // Re-rendering with new direction should re-render children: + // Re-rendering with new layout should re-render children: rendered.update(); expect(itemRenderer).toHaveBeenCalled(); }); @@ -97,7 +116,7 @@ describe('FixedSizeList', () => { ReactDOM.render( , document.createElement('div') @@ -597,6 +616,32 @@ describe('FixedSizeList', () => { 'Please use the innerElementType and outerElementType props instead.' ); }); + + it('should warn if legacy direction "horizontal" value is used', () => { + spyOn(console, 'warn'); + ReactDOM.render( + , + document.createElement('div') + ); + 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.' + ); + }); + + it('should warn if legacy direction "vertical" value is used', () => { + spyOn(console, 'warn'); + ReactDOM.render( + , + document.createElement('div') + ); + 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.' + ); + }); }); describe('itemData', () => { @@ -664,6 +709,18 @@ describe('FixedSizeList', () => { ); }); + it('should fail if an invalid layout is provided', () => { + expect(() => + ReactTestRenderer.create( + + ) + ).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 +728,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 +736,7 @@ describe('FixedSizeList', () => { it('should fail if a string height is provided for a vertical list', () => { expect(() => ReactTestRenderer.create( - + ) ).toThrow( 'An invalid "height" prop has been specified. ' + @@ -691,11 +748,7 @@ describe('FixedSizeList', () => { it('should fail if a string width is provided for a horizontal list', () => { expect(() => ReactTestRenderer.create( - + ) ).toThrow( 'An invalid "width" prop has been specified. ' + diff --git a/src/createListComponent.js b/src/createListComponent.js index 8acb6e7a..6db9cb83 100644 --- a/src/createListComponent.js +++ b/src/createListComponent.js @@ -9,7 +9,8 @@ import type { TimeoutID } from './timer'; export type ScrollToAlign = 'auto' | 'center' | 'start' | 'end'; type itemSize = number | ((index: number) => number); -type Direction = 'horizontal' | 'vertical'; +type Direction = 'ltr' | 'rtl' | 'horizontal' | 'vertical'; +type Layout = 'horizontal' | 'vertical'; type RenderComponentProps = {| data: T, @@ -49,6 +50,7 @@ export type Props = {| itemData: T, itemKey?: (index: number, data: T) => any, itemSize: itemSize, + layout: Layout, onItemsRendered?: onItemsRenderedCallback, onScroll?: onScrollCallback, outerRef?: any, @@ -130,8 +132,9 @@ export default function createListComponent({ _resetIsScrollingTimeoutId: TimeoutID | null = null; static defaultProps = { - direction: 'vertical', + direction: 'ltr', itemData: undefined, + layout: 'vertical', overscanCount: 2, useIsScrolling: false, }; @@ -151,15 +154,9 @@ export default function createListComponent({ // eslint-disable-next-line no-useless-constructor constructor(props: Props) { super(props); - } - static getDerivedStateFromProps( - props: Props, - state: State - ): $Shape | null { validateSharedProps(props); validateProps(props); - return null; } scrollTo(scrollOffset: number): void { @@ -188,10 +185,11 @@ export default function createListComponent({ } componentDidMount() { - const { initialScrollOffset, direction } = this.props; + const { direction, initialScrollOffset, layout } = this.props; if (typeof initialScrollOffset === 'number' && this._outerRef !== null) { - if (direction === 'horizontal') { + // TODO Deprecate direction "horizontal" + if (direction === 'horizontal' || layout === 'horizontal') { ((this ._outerRef: any): HTMLDivElement).scrollLeft = initialScrollOffset; } else { @@ -204,11 +202,12 @@ export default function createListComponent({ } componentDidUpdate() { - const { direction } = this.props; + const { direction, layout } = this.props; const { scrollOffset, scrollUpdateWasRequested } = this.state; if (scrollUpdateWasRequested && this._outerRef !== null) { - if (direction === 'horizontal') { + // TODO Deprecate direction "horizontal" + if (direction === 'horizontal' || layout === 'horizontal') { ((this._outerRef: any): HTMLDivElement).scrollLeft = scrollOffset; } else { ((this._outerRef: any): HTMLDivElement).scrollTop = scrollOffset; @@ -236,6 +235,7 @@ export default function createListComponent({ itemCount, itemData, itemKey = defaultItemKey, + layout, outerElementType, outerTagName, style, @@ -244,10 +244,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 [startIndex, stopIndex] = this._getRangeToRender(); @@ -286,6 +289,7 @@ export default function createListComponent({ overflow: 'auto', WebkitOverflowScrolling: 'touch', willChange: 'transform', + direction: direction === 'rtl' ? 'rtl' : 'ltr', ...style, }, }, @@ -293,9 +297,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%', }, }) ); @@ -379,10 +383,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 ); @@ -393,21 +398,25 @@ 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, - right: direction === 'horizontal' ? offset : 0, - top: direction === 'vertical' ? offset : 0, - height: direction === 'vertical' ? size : '100%', - width: direction === 'horizontal' ? size : '100%', + left: isHorizontal ? offset : 0, + right: isHorizontal ? offset : 0, + top: !isHorizontal ? offset : 0, + height: !isHorizontal ? size : '100%', + width: isHorizontal ? size : '100%', }; } return style; }; - _getItemStyleCache: (_: any, __: any) => ItemStyleCache; - _getItemStyleCache = memoizeOne((_: any, __: any) => ({})); + _getItemStyleCache: (_: any, __: any, ___: any) => ItemStyleCache; + _getItemStyleCache = memoizeOne((_: any, __: any, ___: any) => ({})); _getRangeToRender(): [number, number, number, number] { const { itemCount, overscanCount } = this.props; @@ -458,15 +467,16 @@ export default function createListComponent({ return null; } - const isRtl = this.props.style && this.props.style.direction === 'rtl'; + const { direction } = this.props; return { isScrolling: true, scrollDirection: prevState.scrollOffset < scrollLeft ? 'forward' : 'backward', - scrollOffset: isRtl - ? scrollWidth - clientWidth - scrollLeft - : scrollLeft, + scrollOffset: + direction === 'rtl' + ? scrollWidth - clientWidth - scrollLeft + : scrollLeft, scrollUpdateWasRequested: false, }; }, this._resetIsScrollingDebounced); @@ -541,6 +551,7 @@ const validateSharedProps = ({ children, direction, height, + layout, innerTagName, outerTagName, width, @@ -553,12 +564,40 @@ const validateSharedProps = ({ ); } - 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': + 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) { @@ -569,13 +608,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. ' + diff --git a/website/sandboxes/fixed-size-list-horizontal/index.js b/website/sandboxes/fixed-size-list-horizontal/index.js index bbf45a4e..4c5ae27e 100644 --- a/website/sandboxes/fixed-size-list-horizontal/index.js +++ b/website/sandboxes/fixed-size-list-horizontal/index.js @@ -13,10 +13,10 @@ const Column = ({ index, style }) => ( const Example = () => ( {Column} diff --git a/website/sandboxes/variable-size-list-horizontal/index.js b/website/sandboxes/variable-size-list-horizontal/index.js index 89f1d54f..cc69d7d1 100644 --- a/website/sandboxes/variable-size-list-horizontal/index.js +++ b/website/sandboxes/variable-size-list-horizontal/index.js @@ -21,10 +21,10 @@ const Column = ({ index, style }) => ( const Example = () => ( {Column} diff --git a/website/src/code/FixedSizeListHorizontal.js b/website/src/code/FixedSizeListHorizontal.js index 72cdefd1..654196df 100644 --- a/website/src/code/FixedSizeListHorizontal.js +++ b/website/src/code/FixedSizeListHorizontal.js @@ -6,10 +6,10 @@ const Column = ({ index, style }) => ( const Example = () => ( {Column} diff --git a/website/src/code/FixedSizeListHorizontalRtl.js b/website/src/code/FixedSizeListHorizontalRtl.js index e1073aa3..ad4f17e9 100644 --- a/website/src/code/FixedSizeListHorizontalRtl.js +++ b/website/src/code/FixedSizeListHorizontalRtl.js @@ -6,7 +6,7 @@ const Column = ({ index, style }) => ( const Example = () => ( ( const Example = () => ( {Column} diff --git a/website/src/routes/api/FixedSizeList.js b/website/src/routes/api/FixedSizeList.js index dea1aed3..2907d966 100644 --- a/website/src/routes/api/FixedSizeList.js +++ b/website/src/routes/api/FixedSizeList.js @@ -58,18 +58,14 @@ const PROPS = [ type: 'string', }, { - defaultValue: '"vertical"', + defaultValue: '"ltr"', description: ( -

Primary scroll direction of the list. Acceptable values are:

+

Determines the direction of text and horizontal scrolling.

    -
  • vertical (default) - Up/down scrolling.
  • -
  • horizontal - Left/right scrolling.
  • +
  • ltr (default)
  • +
  • rtl
-

- Note that lists may scroll in both directions (depending on CSS) but - content will only be windowed in the primary direction. -

), name: 'direction', @@ -207,6 +203,24 @@ const PROPS = [ name: 'itemSize', type: 'number', }, + { + defaultValue: '"vertical"', + description: ( + +

Layout/orientation of the list. Acceptable values are:

+
    +
  • vertical (default) - Up/down scrolling.
  • +
  • horizontal - Left/right scrolling.
  • +
+

+ Note that lists may scroll in both directions (depending on CSS) but + content will only be windowed in the layout direction specified. +

+
+ ), + name: 'layout', + type: 'string', + }, { description: ( diff --git a/website/src/routes/examples/FixedSizeList.js b/website/src/routes/examples/FixedSizeList.js index e3cf3403..f0ed7ecd 100644 --- a/website/src/routes/examples/FixedSizeList.js +++ b/website/src/routes/examples/FixedSizeList.js @@ -54,11 +54,11 @@ export default function() { > {Item} diff --git a/website/src/routes/examples/RTLLayout.js b/website/src/routes/examples/RTLLayout.js index 4891b73e..c07cf2fc 100644 --- a/website/src/routes/examples/RTLLayout.js +++ b/website/src/routes/examples/RTLLayout.js @@ -39,7 +39,6 @@ export default function() { itemSize={100} layout="horizontal" width={300} - style={{ direction: 'rtl' }} > {Item} diff --git a/website/src/routes/examples/VariableSizeList.js b/website/src/routes/examples/VariableSizeList.js index 51aff135..c5e4b9ef 100644 --- a/website/src/routes/examples/VariableSizeList.js +++ b/website/src/routes/examples/VariableSizeList.js @@ -58,11 +58,11 @@ export default function() { > columnSizes[index]} + layout="horizontal" width={300} > {Item} From ba279dcfff7abbc4469fc3d6b2a6b08f771ba951 Mon Sep 17 00:00:00 2001 From: John Gozde Date: Sun, 3 Mar 2019 17:07:46 -0700 Subject: [PATCH 08/20] Upgrade memoize-one to ^5.0.0 Breaking changes between 3.1.2 and 5.0.0 do not react-window, since they were around bundling (4.0) and the optional comparer function (5.0), which is not used in this project. --- package.json | 2 +- yarn.lock | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index b6aa7818..9209d7f0 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ }, "dependencies": { "@babel/runtime": "^7.0.0", - "memoize-one": "^3.1.1" + "memoize-one": "^5.0.0" }, "peerDependencies": { "react": "^15.0.0 || ^16.0.0", diff --git a/yarn.lock b/yarn.lock index f81bc3cb..5a163d57 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5596,9 +5596,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" From 7d613516a977cf53d36a46a63689575c289d75d8 Mon Sep 17 00:00:00 2001 From: John Gozde Date: Sun, 3 Mar 2019 17:13:15 -0700 Subject: [PATCH 09/20] Upgrade memoize-one in website --- website/sandboxes/memoized-list-items/package.json | 2 +- website/yarn.lock | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/website/sandboxes/memoized-list-items/package.json b/website/sandboxes/memoized-list-items/package.json index 1d2a045c..6110232c 100644 --- a/website/sandboxes/memoized-list-items/package.json +++ b/website/sandboxes/memoized-list-items/package.json @@ -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" diff --git a/website/yarn.lock b/website/yarn.lock index 2435987d..17735bfe 100644 --- a/website/yarn.lock +++ b/website/yarn.lock @@ -6161,9 +6161,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" From e5d32f3afde4b2f140341b3ab2c18b0fd23b6d90 Mon Sep 17 00:00:00 2001 From: John Gozde Date: Sun, 3 Mar 2019 17:46:14 -0700 Subject: [PATCH 10/20] Prettier --- src/createGridComponent.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/createGridComponent.js b/src/createGridComponent.js index 039eede4..f71eea27 100644 --- a/src/createGridComponent.js +++ b/src/createGridComponent.js @@ -250,8 +250,10 @@ export default function createGridComponent({ // 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; + const horizontalScrollbarSize = + estimatedTotalWidth > width ? scrollbarSize : 0; + const verticalScrollbarSize = + estimatedTotalHeight > height ? scrollbarSize : 0; this.scrollTo({ scrollLeft: getOffsetForColumnAndAlignment( From 38732dd326f9d07e35eecbffa1f7752bb689edf0 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Sun, 3 Mar 2019 17:48:56 -0800 Subject: [PATCH 11/20] Added 'direction' prop to grid (including docs and tests) --- src/__tests__/FixedSizeGrid.js | 74 +++++++++++- src/__tests__/FixedSizeList.js | 74 ++++++++++-- src/createGridComponent.js | 106 +++++++++++++----- src/createListComponent.js | 70 ++++++++---- website/src/code/FixedSizeGridRtl.js | 21 ++++ .../src/code/FixedSizeListHorizontalRtl.js | 2 +- website/src/routes/api/FixedSizeGrid.js | 25 +++++ website/src/routes/api/FixedSizeList.js | 11 ++ website/src/routes/examples/RTLLayout.js | 57 +++++++++- 9 files changed, 374 insertions(+), 66 deletions(-) create mode 100644 website/src/code/FixedSizeGridRtl.js diff --git a/src/__tests__/FixedSizeGrid.js b/src/__tests__/FixedSizeGrid.js index ea6fd06b..dbc4303d 100644 --- a/src/__tests__/FixedSizeGrid.js +++ b/src/__tests__/FixedSizeGrid.js @@ -176,6 +176,41 @@ describe('FixedSizeGrid', () => { }); }); + describe('direction', () => { + it('should set the appropriate CSS direction style', () => { + const renderer = ReactTestRenderer.create( + + ); + expect(renderer.toJSON().props.style.direction).toBe('ltr'); + renderer.update(); + expect(renderer.toJSON().props.style.direction).toBe('rtl'); + }); + + it('should position items correctly', () => { + const renderer = ReactTestRenderer.create( + + ); + + 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(); + + 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( @@ -252,7 +287,8 @@ describe('FixedSizeGrid', () => { describe('overscanCount', () => { it('should warn about deprecated overscanCount prop', () => { spyOn(console, 'warn'); - ReactTestRenderer.create( + + const renderer = ReactTestRenderer.create( ); expect(console.warn).toHaveBeenCalledTimes(1); @@ -260,10 +296,16 @@ describe('FixedSizeGrid', () => { 'The overscanCount prop has been deprecated. ' + 'Please use the overscanColumnsCount and overscanRowsCount props instead.' ); + + renderer.update(); + + // 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( { it('should use overscanRowsCount if both it and overscanCount are provided', () => { spyOn(console, 'warn'); + ReactTestRenderer.create( { it('should support deprecated overscanCount', () => { spyOn(console, 'warn'); + ReactTestRenderer.create( { it('should warn if legacy innerTagName or outerTagName props are used', () => { spyOn(console, 'warn'); - ReactDOM.render( + const renderer = ReactTestRenderer.create( , - 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( + + ); + + // But it should only warn once. + expect(console.warn).toHaveBeenCalledTimes(1); }); }); @@ -921,6 +975,18 @@ describe('FixedSizeGrid', () => { ); }); + it('should fail if an invalid direction is provided', () => { + expect(() => + ReactTestRenderer.create( + + ) + ).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( diff --git a/src/__tests__/FixedSizeList.js b/src/__tests__/FixedSizeList.js index 6e1eb1db..f591c3d0 100644 --- a/src/__tests__/FixedSizeList.js +++ b/src/__tests__/FixedSizeList.js @@ -221,6 +221,39 @@ describe('FixedSizeList', () => { }); }); + describe('direction', () => { + it('should set the appropriate CSS direction style', () => { + const renderer = ReactTestRenderer.create( + + ); + expect(renderer.toJSON().props.style.direction).toBe('ltr'); + renderer.update(); + expect(renderer.toJSON().props.style.direction).toBe('rtl'); + }); + + it('should position items correctly', () => { + const renderer = ReactTestRenderer.create( + + ); + + 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(); + + 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( @@ -602,45 +635,68 @@ describe('FixedSizeList', () => { it('should warn if legacy innerTagName or outerTagName props are used', () => { spyOn(console, 'warn'); - ReactDOM.render( + + const renderer = ReactTestRenderer.create( , - 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( + + ); + + // But it should only warn once. + expect(console.warn).toHaveBeenCalledTimes(1); }); it('should warn if legacy direction "horizontal" value is used', () => { spyOn(console, 'warn'); - ReactDOM.render( - , - document.createElement('div') + + const renderer = ReactTestRenderer.create( + ); 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( + + ); + + // But it should only warn once. + expect(console.warn).toHaveBeenCalledTimes(1); }); it('should warn if legacy direction "vertical" value is used', () => { spyOn(console, 'warn'); - ReactDOM.render( - , - document.createElement('div') + + const renderer = ReactTestRenderer.create( + ); 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(); + + // But it should only warn once. + expect(console.warn).toHaveBeenCalledTimes(1); }); }); diff --git a/src/createGridComponent.js b/src/createGridComponent.js index f71eea27..c9912563 100644 --- a/src/createGridComponent.js +++ b/src/createGridComponent.js @@ -7,6 +7,7 @@ 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); @@ -50,6 +51,7 @@ export type Props = {| className?: string, columnCount: number, columnWidth: itemSize, + direction: Direction, height: number, initialScrollLeft?: number, initialScrollTop?: number, @@ -78,6 +80,7 @@ export type Props = {| |}; type State = {| + instance: any, isScrolling: boolean, horizontalScrollDirection: ScrollDirection, scrollLeft: number, @@ -124,6 +127,15 @@ 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') { + devWarningsOverscanCount = new WeakSet(); + devWarningsTagName = new WeakSet(); +} + export default function createGridComponent({ getColumnOffset, getColumnStartIndexForOffset, @@ -163,11 +175,13 @@ export default function createGridComponent({ _outerRef: ?HTMLDivElement; static defaultProps = { + direction: 'ltr', itemData: undefined, useIsScrolling: false, }; state: State = { + instance: this, isScrolling: false, horizontalScrollDirection: 'forward', scrollLeft: @@ -193,7 +207,7 @@ export default function createGridComponent({ nextProps: Props, prevState: State ): $Shape | null { - validateSharedProps(nextProps); + validateSharedProps(nextProps, prevState); validateProps(nextProps); return null; } @@ -308,6 +322,7 @@ export default function createGridComponent({ children, className, columnCount, + direction, height, innerRef, innerElementType, @@ -379,6 +394,7 @@ export default function createGridComponent({ overflow: 'auto', WebkitOverflowScrolling: 'touch', willChange: 'transform', + direction, ...style, }, }, @@ -505,20 +521,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), @@ -528,8 +551,8 @@ export default function createGridComponent({ return style; }; - _getItemStyleCache: (_: any, __: any) => ItemStyleCache; - _getItemStyleCache = memoizeOne((_: any, __: any) => ({})); + _getItemStyleCache: (_: any, __: any, ___: any) => ItemStyleCache; + _getItemStyleCache = memoizeOne((_: any, __: any, ___: any) => ({})); _getHorizontalRangeToRender(): [number, number, number, number] { const { @@ -626,7 +649,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 && @@ -638,11 +666,16 @@ export default function createGridComponent({ return null; } + const { direction } = this.props; + return { isScrolling: true, horizontalScrollDirection: prevState.scrollLeft < scrollLeft ? 'forward' : 'backward', - scrollLeft, + scrollLeft: + direction === 'rtl' + ? scrollWidth - clientWidth - scrollLeft + : scrollLeft, scrollTop, verticalScrollDirection: prevState.scrollTop < scrollTop ? 'forward' : 'backward', @@ -690,27 +723,37 @@ export default function createGridComponent({ }; } -const validateSharedProps = ({ - children, - height, - innerTagName, - outerTagName, - overscanCount, - width, -}: Props): void => { +const validateSharedProps = ( + { + children, + direction, + height, + innerTagName, + outerTagName, + overscanCount, + width, + }: Props, + { 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: any): WeakSet).has(instance)) { + ((devWarningsOverscanCount: any): WeakSet).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: any): WeakSet).has(instance)) { + ((devWarningsTagName: any): WeakSet).add(instance); + console.warn( + 'The innerTagName and outerTagName props have been deprecated. ' + + 'Please use the innerElementType and outerElementType props instead.' + ); + } } if (children == null) { @@ -721,6 +764,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. ' + diff --git a/src/createListComponent.js b/src/createListComponent.js index 6db9cb83..d94837f8 100644 --- a/src/createListComponent.js +++ b/src/createListComponent.js @@ -9,6 +9,7 @@ import type { TimeoutID } from './timer'; export type ScrollToAlign = 'auto' | 'center' | 'start' | 'end'; type itemSize = number | ((index: number) => number); +// TODO Deprecate directions "horizontal" and "vertical" type Direction = 'ltr' | 'rtl' | 'horizontal' | 'vertical'; type Layout = 'horizontal' | 'vertical'; @@ -63,6 +64,7 @@ export type Props = {| |}; type State = {| + instance: any, isScrolling: boolean, scrollDirection: ScrollDirection, scrollOffset: number, @@ -105,6 +107,15 @@ const IS_SCROLLING_DEBOUNCE_INTERVAL = 150; const defaultItemKey = (index: number, data: any) => index; +// In DEV mode, this Set helps us only log a warning once per component instace. +// This avoids spamming the console every time a render happens. +let devWarningsDirection = null; +let devWarningsTagName = null; +if (process.env.NODE_ENV !== 'production') { + devWarningsDirection = new WeakSet(); + devWarningsTagName = new WeakSet(); +} + export default function createListComponent({ getItemOffset, getEstimatedTotalSize, @@ -140,6 +151,7 @@ export default function createListComponent({ }; state: State = { + instance: this, isScrolling: false, scrollDirection: 'forward', scrollOffset: @@ -154,9 +166,15 @@ export default function createListComponent({ // eslint-disable-next-line no-useless-constructor constructor(props: Props) { super(props); + } - validateSharedProps(props); - validateProps(props); + static getDerivedStateFromProps( + nextProps: Props, + prevState: State + ): $Shape | null { + validateSharedProps(nextProps, prevState); + validateProps(nextProps); + return null; } scrollTo(scrollOffset: number): void { @@ -404,8 +422,7 @@ export default function createListComponent({ itemStyleCache[index] = style = { position: 'absolute', - left: isHorizontal ? offset : 0, - right: isHorizontal ? offset : 0, + [direction === 'rtl' ? 'right' : 'left']: isHorizontal ? offset : 0, top: !isHorizontal ? offset : 0, height: !isHorizontal ? size : '100%', width: isHorizontal ? size : '100%', @@ -458,7 +475,7 @@ export default function createListComponent({ } _onScrollHorizontal = (event: ScrollEvent): void => { - const { scrollLeft, scrollWidth, clientWidth } = event.currentTarget; + const { clientWidth, scrollLeft, scrollWidth } = event.currentTarget; this.setState(prevState => { if (prevState.scrollOffset === scrollLeft) { // Scroll position may have been updated by cDM/cDU, @@ -547,21 +564,27 @@ 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, - layout, - innerTagName, - outerTagName, - width, -}: Props): void => { +const validateSharedProps = ( + { + children, + direction, + height, + layout, + innerTagName, + outerTagName, + width, + }: Props, + { 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: any): WeakSet).has(instance)) { + ((devWarningsTagName: any): WeakSet).add(instance); + console.warn( + 'The innerTagName and outerTagName props have been deprecated. ' + + 'Please use the innerElementType and outerElementType props instead.' + ); + } } // TODO Deprecate direction "horizontal" @@ -570,10 +593,13 @@ const validateSharedProps = ({ switch (direction) { case 'horizontal': case 'vertical': - console.warn( - 'The direction prop should be either "ltr" (default) or "rtl". ' + - 'Please use the layout prop to specify "vertical" (default) or "horizontal" orientation.' - ); + if (!((devWarningsDirection: any): WeakSet).has(instance)) { + ((devWarningsDirection: any): WeakSet).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': diff --git a/website/src/code/FixedSizeGridRtl.js b/website/src/code/FixedSizeGridRtl.js new file mode 100644 index 00000000..febca6bc --- /dev/null +++ b/website/src/code/FixedSizeGridRtl.js @@ -0,0 +1,21 @@ +import { FixedSizeGrid as Grid } from 'react-window'; + +const Cell = ({ columnIndex, rowIndex, style }) => ( +
+ بند {rowIndex},{columnIndex} +
+); + +const Example = () => ( + + {Cell} + +); \ No newline at end of file diff --git a/website/src/code/FixedSizeListHorizontalRtl.js b/website/src/code/FixedSizeListHorizontalRtl.js index ad4f17e9..b43071e0 100644 --- a/website/src/code/FixedSizeListHorizontalRtl.js +++ b/website/src/code/FixedSizeListHorizontalRtl.js @@ -1,7 +1,7 @@ import { FixedSizeList as List } from 'react-window'; const Column = ({ index, style }) => ( -
Column {index}
+
عمود {index}
); const Example = () => ( diff --git a/website/src/routes/api/FixedSizeGrid.js b/website/src/routes/api/FixedSizeGrid.js index fab2bb2c..c84555d5 100644 --- a/website/src/routes/api/FixedSizeGrid.js +++ b/website/src/routes/api/FixedSizeGrid.js @@ -74,6 +74,31 @@ const PROPS = [ name: 'columnWidth', type: 'number', }, + { + defaultValue: '"ltr"', + description: ( + +

Determines the direction of text and horizontal scrolling.

+
    +
  • ltr (default)
  • +
  • rtl
  • +
+

+ This property also automatically sets the{' '} + + CSS direction style + {' '} + for the grid component. +

+
+ ), + name: 'direction', + type: 'string', + }, { description: (

diff --git a/website/src/routes/api/FixedSizeList.js b/website/src/routes/api/FixedSizeList.js index 2907d966..70be84ca 100644 --- a/website/src/routes/api/FixedSizeList.js +++ b/website/src/routes/api/FixedSizeList.js @@ -66,6 +66,17 @@ const PROPS = [

  • ltr (default)
  • rtl
  • +

    + This property also automatically sets the{' '} + + CSS direction style + {' '} + for the list component. +

    ), name: 'direction', diff --git a/website/src/routes/examples/RTLLayout.js b/website/src/routes/examples/RTLLayout.js index c07cf2fc..26d533f0 100644 --- a/website/src/routes/examples/RTLLayout.js +++ b/website/src/routes/examples/RTLLayout.js @@ -1,12 +1,36 @@ import React, { PureComponent } from 'react'; -import { FixedSizeList } from 'react-window'; +import { FixedSizeGrid, FixedSizeList } from 'react-window'; import CodeBlock from '../../components/CodeBlock'; import ProfiledExample from '../../components/ProfiledExample'; -import CODE_HORIZONTAL from '../../code/FixedSizeListHorizontalRtl.js'; +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 ( +
    + بند {rowIndex}, {columnIndex} +
    + ); + } +} + class Item extends PureComponent { render() { const { index, style } = this.props; @@ -25,11 +49,11 @@ class Item extends PureComponent { export default function() { return (
    -

    Basic List

    +

    RTL List

    - + +
    +
    +

    RTL Grid

    +
    + + + {Cell} + + +
    +
    From 7130e03555dd05ef3289f4a154c6cc1366fff149 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 4 Mar 2019 08:08:23 -0800 Subject: [PATCH 12/20] Shrink domHelpers. Remove unnecessary style. Better minification. --- src/domHelpers.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/domHelpers.js b/src/domHelpers.js index 0f3c1925..830aba68 100644 --- a/src/domHelpers.js +++ b/src/domHelpers.js @@ -6,11 +6,10 @@ let size: number = -1; export function getScrollbarSize(recalculate?: boolean = false): number { if (size === -1 || recalculate) { const div = document.createElement('div'); - div.style.position = 'absolute'; - div.style.top = '-9999px'; - div.style.width = '50px'; - div.style.height = '50px'; - div.style.overflow = 'scroll'; + const style = div.style; + style.width = '50px'; + style.height = '50px'; + style.overflow = 'scroll'; ((document.body: any): HTMLBodyElement).appendChild(div); From a724ee19a8d09f06deb37a88f04e6c892fdf6726 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 4 Mar 2019 08:09:11 -0800 Subject: [PATCH 13/20] Upgrade Rollup. Add Terser plug-in. --- package.json | 9 +-- rollup.config.js | 13 ++++ yarn.lock | 181 +++++++++++++++++++++++++++++++++++------------ 3 files changed, 152 insertions(+), 51 deletions(-) diff --git a/package.json b/package.json index b6aa7818..0edbf35b 100644 --- a/package.json +++ b/package.json @@ -99,9 +99,10 @@ "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", + "rollup-plugin-terser": "^4.0.4" } } diff --git a/rollup.config.js b/rollup.config.js index 5d167d14..8a2ced3f 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,12 +1,23 @@ import babel from 'rollup-plugin-babel'; import commonjs from 'rollup-plugin-commonjs'; import nodeResolve from 'rollup-plugin-node-resolve'; +import { terser } from "rollup-plugin-terser"; import pkg from './package.json'; const input = './src/index.js'; const external = id => !id.startsWith('.') && !id.startsWith('/'); +const terserConfig = { + mangle: { + properties: { + regex: /^_/ + } + }, + output: { comments: true }, + toplevel: true, +}; + export default [ { input, @@ -22,6 +33,7 @@ export default [ }), nodeResolve(), commonjs(), + terser(terserConfig), ], }, @@ -39,6 +51,7 @@ export default [ }), nodeResolve(), commonjs(), + terser(terserConfig), ], }, ]; diff --git a/yarn.lock b/yarn.lock index f81bc3cb..d5c273e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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" @@ -2215,6 +2223,11 @@ commander@2.15.x, commander@^2.11.0, commander@^2.14.1, commander@^2.9.0, comman version "2.15.1" resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" +commander@~2.17.1: + version "2.17.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" + integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg== + commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" @@ -3293,14 +3306,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" @@ -5063,6 +5077,14 @@ jest-validate@^22.4.0, jest-validate@^22.4.3: leven "^2.1.0" pretty-format "^22.4.3" +jest-worker@^24.0.0: + version "24.0.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.0.0.tgz#3d3483b077bf04f412f47654a27bba7e947f8b6d" + integrity sha512-s64/OThpfQvoCeHG963MiEZOAAxu8kHsaL/rCMF7lpdzo7vgF0CtPml9hfguOMgykgH/eOm4jFP4ibfHLruytg== + dependencies: + merge-stream "^1.0.1" + supports-color "^6.1.0" + jest@20.0.4: version "20.0.4" resolved "https://registry.yarnpkg.com/jest/-/jest-20.0.4.tgz#3dd260c2989d6dad678b1e9cc4d91944f6d602ac" @@ -5543,11 +5565,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" @@ -5626,6 +5649,13 @@ merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" +merge-stream@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-1.0.1.tgz#4041202d508a342ba00174008df0c251b8c135e1" + integrity sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE= + dependencies: + readable-stream "^2.0.1" + merge@^1.1.3: version "1.2.0" resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da" @@ -5652,7 +5682,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 +6303,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 +7332,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 +7388,42 @@ 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" + resolve "^1.10.0" -rollup-pluginutils@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.0.1.tgz#7ec95b3573f6543a46a6461bd9a7c544525d0fc0" +rollup-plugin-terser@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-4.0.4.tgz#6f661ef284fa7c27963d242601691dc3d23f994e" + integrity sha512-wPANT5XKVJJ8RDUN0+wIr7UPd0lIXBo4UdJ59VmlPCtlFsE20AM+14pe+tk7YunCsWEiuzkDBY3QIkSCjtrPXg== dependencies: - estree-walker "^0.3.0" - micromatch "^2.3.11" + "@babel/code-frame" "^7.0.0" + jest-worker "^24.0.0" + serialize-javascript "^1.6.1" + terser "^3.14.1" rollup-pluginutils@^2.3.0: version "2.3.1" @@ -7385,12 +7432,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" @@ -7504,6 +7561,11 @@ send@0.16.2: range-parser "~1.2.0" statuses "~1.4.0" +serialize-javascript@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.6.1.tgz#4d1f697ec49429a847ca6f442a2a755126c4d879" + integrity sha512-A5MOagrPFga4YaKQSWHryl7AXvbQkEqpw4NNYMTNYUNV51bA8ABHgYFpqKx+YFFrw59xMV1qGH1R4AgoNIVgCw== + serve-index@^1.7.2: version "1.9.1" resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" @@ -7699,6 +7761,14 @@ source-map-support@^0.5.0: buffer-from "^1.0.0" source-map "^0.6.0" +source-map-support@~0.5.9: + version "0.5.10" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.10.tgz#2214080bc9d51832511ee2bab96e3c2f9353120c" + integrity sha512-YfQ3tQFTK/yzlGJuX8pTwa4tifQj4QS2Mj7UegOu8jAz59MqIiMGPXxQhVQiIMNzayuUSF/jEuVnfFF5JqybmQ== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + source-map-url@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" @@ -7717,6 +7787,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" @@ -7952,6 +8027,13 @@ supports-color@^5.3.0, supports-color@^5.4.0: dependencies: has-flag "^3.0.0" +supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + svgo@^0.7.0: version "0.7.2" resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5" @@ -8050,6 +8132,15 @@ term-size@^1.2.0: dependencies: execa "^0.7.0" +terser@^3.14.1: + version "3.16.1" + resolved "https://registry.yarnpkg.com/terser/-/terser-3.16.1.tgz#5b0dd4fa1ffd0b0b43c2493b2c364fd179160493" + integrity sha512-JDJjgleBROeek2iBcSNzOHLKsB/MdDf+E/BOAJ0Tk9r7p9/fVobfv7LMJ/g/k3v9SXdmjZnIlFd5nfn/Rt0Xow== + dependencies: + commander "~2.17.1" + source-map "~0.6.1" + source-map-support "~0.5.9" + test-exclude@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.2.1.tgz#dfa222f03480bca69207ca728b37d74b45f724fa" @@ -8423,10 +8514,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" From 9c24344f3ce695068596caf6510be73d8925d9f9 Mon Sep 17 00:00:00 2001 From: John Gozde Date: Mon, 4 Mar 2019 10:26:49 -0700 Subject: [PATCH 14/20] Use version range for memoize-one --- package.json | 2 +- yarn.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 9209d7f0..3e179151 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ }, "dependencies": { "@babel/runtime": "^7.0.0", - "memoize-one": "^5.0.0" + "memoize-one": ">=3.1.1 <6" }, "peerDependencies": { "react": "^15.0.0 || ^16.0.0", diff --git a/yarn.lock b/yarn.lock index 5a163d57..51dbddc9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5596,7 +5596,7 @@ mem@^1.1.0: dependencies: mimic-fn "^1.0.0" -memoize-one@^5.0.0: +"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== From 0f24e9a6886a2189134eef57b754365a5eb7239c Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 4 Mar 2019 09:46:54 -0800 Subject: [PATCH 15/20] Removed Rollup terser plug-in for now because it was bloating the bundle --- package.json | 3 +-- rollup.config.js | 14 +----------- yarn.lock | 59 ------------------------------------------------ 3 files changed, 2 insertions(+), 74 deletions(-) diff --git a/package.json b/package.json index 0edbf35b..6c0135aa 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,6 @@ "rollup": "^1.4.1", "rollup-plugin-babel": "^4.3.2", "rollup-plugin-commonjs": "^9.2.1", - "rollup-plugin-node-resolve": "^4.0.1", - "rollup-plugin-terser": "^4.0.4" + "rollup-plugin-node-resolve": "^4.0.1" } } diff --git a/rollup.config.js b/rollup.config.js index 8a2ced3f..c05d722a 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,23 +1,13 @@ import babel from 'rollup-plugin-babel'; import commonjs from 'rollup-plugin-commonjs'; import nodeResolve from 'rollup-plugin-node-resolve'; -import { terser } from "rollup-plugin-terser"; + import pkg from './package.json'; const input = './src/index.js'; const external = id => !id.startsWith('.') && !id.startsWith('/'); -const terserConfig = { - mangle: { - properties: { - regex: /^_/ - } - }, - output: { comments: true }, - toplevel: true, -}; - export default [ { input, @@ -33,7 +23,6 @@ export default [ }), nodeResolve(), commonjs(), - terser(terserConfig), ], }, @@ -51,7 +40,6 @@ export default [ }), nodeResolve(), commonjs(), - terser(terserConfig), ], }, ]; diff --git a/yarn.lock b/yarn.lock index d5c273e3..d636bf6e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2223,11 +2223,6 @@ commander@2.15.x, commander@^2.11.0, commander@^2.14.1, commander@^2.9.0, comman version "2.15.1" resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" -commander@~2.17.1: - version "2.17.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" - integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg== - commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" @@ -5077,14 +5072,6 @@ jest-validate@^22.4.0, jest-validate@^22.4.3: leven "^2.1.0" pretty-format "^22.4.3" -jest-worker@^24.0.0: - version "24.0.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.0.0.tgz#3d3483b077bf04f412f47654a27bba7e947f8b6d" - integrity sha512-s64/OThpfQvoCeHG963MiEZOAAxu8kHsaL/rCMF7lpdzo7vgF0CtPml9hfguOMgykgH/eOm4jFP4ibfHLruytg== - dependencies: - merge-stream "^1.0.1" - supports-color "^6.1.0" - jest@20.0.4: version "20.0.4" resolved "https://registry.yarnpkg.com/jest/-/jest-20.0.4.tgz#3dd260c2989d6dad678b1e9cc4d91944f6d602ac" @@ -5649,13 +5636,6 @@ merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" -merge-stream@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-1.0.1.tgz#4041202d508a342ba00174008df0c251b8c135e1" - integrity sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE= - dependencies: - readable-stream "^2.0.1" - merge@^1.1.3: version "1.2.0" resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da" @@ -7415,16 +7395,6 @@ rollup-plugin-node-resolve@^4.0.1: is-module "^1.0.0" resolve "^1.10.0" -rollup-plugin-terser@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-4.0.4.tgz#6f661ef284fa7c27963d242601691dc3d23f994e" - integrity sha512-wPANT5XKVJJ8RDUN0+wIr7UPd0lIXBo4UdJ59VmlPCtlFsE20AM+14pe+tk7YunCsWEiuzkDBY3QIkSCjtrPXg== - dependencies: - "@babel/code-frame" "^7.0.0" - jest-worker "^24.0.0" - serialize-javascript "^1.6.1" - terser "^3.14.1" - rollup-pluginutils@^2.3.0: version "2.3.1" resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.3.1.tgz#760d185ccc237dedc12d7ae48c6bcd127b4892d0" @@ -7561,11 +7531,6 @@ send@0.16.2: range-parser "~1.2.0" statuses "~1.4.0" -serialize-javascript@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.6.1.tgz#4d1f697ec49429a847ca6f442a2a755126c4d879" - integrity sha512-A5MOagrPFga4YaKQSWHryl7AXvbQkEqpw4NNYMTNYUNV51bA8ABHgYFpqKx+YFFrw59xMV1qGH1R4AgoNIVgCw== - serve-index@^1.7.2: version "1.9.1" resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" @@ -7761,14 +7726,6 @@ source-map-support@^0.5.0: buffer-from "^1.0.0" source-map "^0.6.0" -source-map-support@~0.5.9: - version "0.5.10" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.10.tgz#2214080bc9d51832511ee2bab96e3c2f9353120c" - integrity sha512-YfQ3tQFTK/yzlGJuX8pTwa4tifQj4QS2Mj7UegOu8jAz59MqIiMGPXxQhVQiIMNzayuUSF/jEuVnfFF5JqybmQ== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - source-map-url@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" @@ -8027,13 +7984,6 @@ supports-color@^5.3.0, supports-color@^5.4.0: dependencies: has-flag "^3.0.0" -supports-color@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" - integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== - dependencies: - has-flag "^3.0.0" - svgo@^0.7.0: version "0.7.2" resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5" @@ -8132,15 +8082,6 @@ term-size@^1.2.0: dependencies: execa "^0.7.0" -terser@^3.14.1: - version "3.16.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-3.16.1.tgz#5b0dd4fa1ffd0b0b43c2493b2c364fd179160493" - integrity sha512-JDJjgleBROeek2iBcSNzOHLKsB/MdDf+E/BOAJ0Tk9r7p9/fVobfv7LMJ/g/k3v9SXdmjZnIlFd5nfn/Rt0Xow== - dependencies: - commander "~2.17.1" - source-map "~0.6.1" - source-map-support "~0.5.9" - test-exclude@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.2.1.tgz#dfa222f03480bca69207ca728b37d74b45f724fa" From b2ea9ad1f4c9f7ba6b063a738001a700d749a9c5 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 4 Mar 2019 10:05:02 -0800 Subject: [PATCH 16/20] 1.5.2 -> 1.6.0 --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a85b2652..a0b33a99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ Changelog ------------ +### 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)) diff --git a/package.json b/package.json index 4d94641d..5f3ea730 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-window", - "version": "1.5.2", + "version": "1.6.0", "description": "React components for efficiently rendering large, scrollable lists and tabular data", "author": From fb159091fd3cbf62cdd9a2a440fd45cf69b02d06 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 4 Mar 2019 11:10:21 -0800 Subject: [PATCH 17/20] Bugfix to account for differences between Chrome and non-Chrome browsers with regard to RTL and "scroll" events. --- src/createGridComponent.js | 17 +++++++++++++---- src/createListComponent.js | 19 ++++++++++++++----- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/createGridComponent.js b/src/createGridComponent.js index c9912563..a4b9c631 100644 --- a/src/createGridComponent.js +++ b/src/createGridComponent.js @@ -668,14 +668,23 @@ export default function createGridComponent({ 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: - direction === 'rtl' - ? scrollWidth - clientWidth - scrollLeft - : scrollLeft, + scrollLeft: calculatedScrollLeft, scrollTop, verticalScrollDirection: prevState.scrollTop < scrollTop ? 'forward' : 'backward', diff --git a/src/createListComponent.js b/src/createListComponent.js index d94837f8..d2f78de2 100644 --- a/src/createListComponent.js +++ b/src/createListComponent.js @@ -307,7 +307,7 @@ export default function createListComponent({ overflow: 'auto', WebkitOverflowScrolling: 'touch', willChange: 'transform', - direction: direction === 'rtl' ? 'rtl' : 'ltr', + direction, ...style, }, }, @@ -486,14 +486,23 @@ export default function createListComponent({ 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: - direction === 'rtl' - ? scrollWidth - clientWidth - scrollLeft - : scrollLeft, + scrollOffset, scrollUpdateWasRequested: false, }; }, this._resetIsScrollingDebounced); From e0d0dce1af3185ae516a4e90eb668695d2d45580 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 4 Mar 2019 11:10:31 -0800 Subject: [PATCH 18/20] 1.6.0 -> 1.6.1 --- CHANGELOG.md | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0b33a99..c1d1be4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ Changelog ------------ +### 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) diff --git a/package.json b/package.json index 5f3ea730..7150ef23 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-window", - "version": "1.6.0", + "version": "1.6.1", "description": "React components for efficiently rendering large, scrollable lists and tabular data", "author": From dcb9c55c754b56deb3d2c13d1155cfe465ae6b05 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 4 Mar 2019 11:24:24 -0800 Subject: [PATCH 19/20] Bugfix for RTL when scrolling back towards the beginning (right) of the list --- src/createGridComponent.js | 2 +- src/createListComponent.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/createGridComponent.js b/src/createGridComponent.js index a4b9c631..fd3893e8 100644 --- a/src/createGridComponent.js +++ b/src/createGridComponent.js @@ -673,7 +673,7 @@ export default function createGridComponent({ // See https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollLeft let calculatedScrollLeft = scrollLeft; if (direction === 'rtl') { - if (scrollLeft < 0) { + if (scrollLeft <= 0) { calculatedScrollLeft = -scrollLeft; } else { calculatedScrollLeft = scrollWidth - clientWidth - scrollLeft; diff --git a/src/createListComponent.js b/src/createListComponent.js index d2f78de2..5756d44e 100644 --- a/src/createListComponent.js +++ b/src/createListComponent.js @@ -491,7 +491,7 @@ export default function createListComponent({ // See https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollLeft let scrollOffset = scrollLeft; if (direction === 'rtl') { - if (scrollLeft < 0) { + if (scrollLeft <= 0) { scrollOffset = -scrollOffset; } else { scrollOffset = scrollWidth - clientWidth - scrollLeft; From 7b5dd7c3484329ec61e5c053c187b1c41f219c1c Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 4 Mar 2019 11:24:56 -0800 Subject: [PATCH 20/20] 1.6.1 -> 1.6.2 --- CHANGELOG.md | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1d1be4a..05b52c13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ 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. diff --git a/package.json b/package.json index 7150ef23..12dbb1cb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-window", - "version": "1.6.1", + "version": "1.6.2", "description": "React components for efficiently rendering large, scrollable lists and tabular data", "author":