Deprecate list direction=horizontal|vertical in favor of direction=ltr|rtl and separate layout=horizontal|vertical

This commit is contained in:
Brian Vaughn
2019-03-03 14:20:28 -08:00
parent 112f102f3a
commit 98c7ea733a
14 changed files with 187 additions and 78 deletions
+8 -4
View File
@@ -15,12 +15,14 @@ const FixedSizeList = createListComponent({
((itemSize: any): number) * itemCount,
getOffsetForIndexAndAlignment: (
{ direction, height, itemCount, itemSize, width }: Props<any>,
{ direction, height, itemCount, itemSize, layout, width }: Props<any>,
index: number,
align: ScrollToAlign,
scrollOffset: number
): number => {
const size = (((direction === 'horizontal' ? width : height): any): number);
// TODO Deprecate direction "horizontal"
const isHorizontal = direction === 'horizontal' || layout === 'horizontal';
const size = (((isHorizontal ? width : height): any): number);
const maxOffset = Math.max(
0,
Math.min(
@@ -62,12 +64,14 @@ const FixedSizeList = createListComponent({
),
getStopIndexForStartIndex: (
{ direction, height, itemCount, itemSize, width }: Props<any>,
{ direction, height, itemCount, itemSize, layout, width }: Props<any>,
startIndex: number,
scrollOffset: number
): number => {
// TODO Deprecate direction "horizontal"
const isHorizontal = direction === 'horizontal' || layout === 'horizontal';
const offset = startIndex * ((itemSize: any): number);
const size = (((direction === 'horizontal' ? width : height): any): number);
const size = (((isHorizontal ? width : height): any): number);
return Math.max(
0,
Math.min(
+8 -4
View File
@@ -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;
+2 -6
View File
@@ -924,7 +924,7 @@ describe('FixedSizeGrid', () => {
it('should fail if a string height is provided', () => {
expect(() =>
ReactTestRenderer.create(
<FixedSizeGrid {...defaultProps} direction="vertical" height="100%" />
<FixedSizeGrid {...defaultProps} height="100%" />
)
).toThrow(
'An invalid "height" prop has been specified. ' +
@@ -936,11 +936,7 @@ describe('FixedSizeGrid', () => {
it('should fail if a string width is provided', () => {
expect(() =>
ReactTestRenderer.create(
<FixedSizeGrid
{...defaultProps}
direction="horizontal"
width="100%"
/>
<FixedSizeGrid {...defaultProps} width="100%" />
)
).toThrow(
'An invalid "width" prop has been specified. ' +
+63 -10
View File
@@ -58,13 +58,32 @@ describe('FixedSizeList', () => {
it('should render a list of columns', () => {
ReactTestRenderer.create(
<FixedSizeList {...defaultProps} direction="horizontal" />
<FixedSizeList {...defaultProps} layout="horizontal" />
);
expect(itemRenderer).toHaveBeenCalledTimes(5);
expect(onItemsRendered.mock.calls).toMatchSnapshot();
});
it('should re-render items if layout changes', () => {
const rendered = ReactTestRenderer.create(
<FixedSizeList {...defaultProps} layout="vertical" />
);
expect(itemRenderer).toHaveBeenCalled();
itemRenderer.mockClear();
// Re-rendering should not affect pure sCU children:
rendered.update(<FixedSizeList {...defaultProps} layout="vertical" />);
expect(itemRenderer).not.toHaveBeenCalled();
// Re-rendering with new layout should re-render children:
rendered.update(<FixedSizeList {...defaultProps} layout="horizontal" />);
expect(itemRenderer).toHaveBeenCalled();
});
// TODO Deprecate direction "horizontal"
it('should re-render items if direction changes', () => {
spyOn(console, 'warn'); // Ingore legacy prop warning
const rendered = ReactTestRenderer.create(
<FixedSizeList {...defaultProps} direction="vertical" />
);
@@ -75,7 +94,7 @@ describe('FixedSizeList', () => {
rendered.update(<FixedSizeList {...defaultProps} direction="vertical" />);
expect(itemRenderer).not.toHaveBeenCalled();
// Re-rendering with new direction should re-render children:
// Re-rendering with new layout should re-render children:
rendered.update(<FixedSizeList {...defaultProps} direction="horizontal" />);
expect(itemRenderer).toHaveBeenCalled();
});
@@ -97,7 +116,7 @@ describe('FixedSizeList', () => {
ReactDOM.render(
<FixedSizeList
{...defaultProps}
direction="horizontal"
layout="horizontal"
innerRef={innerRef}
/>,
document.createElement('div')
@@ -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(
<FixedSizeList {...defaultProps} direction="horizontal" />,
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(
<FixedSizeList {...defaultProps} direction="vertical" />,
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(
<FixedSizeList {...defaultProps} layout={null} />
)
).toThrow(
'An invalid "layout" prop has been specified. ' +
'Value should be either "horizontal" or "vertical". ' +
'"null" was specified.'
);
});
it('should fail if an invalid direction is provided', () => {
expect(() =>
ReactTestRenderer.create(
@@ -671,7 +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(
<FixedSizeList {...defaultProps} direction="vertical" height="100%" />
<FixedSizeList {...defaultProps} layout="vertical" height="100%" />
)
).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(
<FixedSizeList
{...defaultProps}
direction="horizontal"
width="100%"
/>
<FixedSizeList {...defaultProps} layout="horizontal" width="100%" />
)
).toThrow(
'An invalid "width" prop has been specified. ' +
+77 -38
View File
@@ -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<T> = {|
data: T,
@@ -49,6 +50,7 @@ export type Props<T> = {|
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<T>) {
super(props);
}
static getDerivedStateFromProps(
props: Props<T>,
state: State
): $Shape<State> | 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. ' +
@@ -13,10 +13,10 @@ const Column = ({ index, style }) => (
const Example = () => (
<List
className="List"
direction="horizontal"
height={75}
itemCount={1000}
itemSize={100}
layout="horizontal"
width={300}
>
{Column}
@@ -21,10 +21,10 @@ const Column = ({ index, style }) => (
const Example = () => (
<List
className="List"
direction="horizontal"
height={75}
itemCount={1000}
itemSize={getItemSize}
layout="horizontal"
width={300}
>
{Column}
+1 -1
View File
@@ -6,10 +6,10 @@ const Column = ({ index, style }) => (
const Example = () => (
<List
direction="horizontal"
height={75}
itemCount={1000}
itemSize={100}
layout="horizontal"
width={300}
>
{Column}
@@ -6,7 +6,7 @@ const Column = ({ index, style }) => (
const Example = () => (
<List
direction="ltr"
direction="rtl"
height={75}
itemCount={1000}
itemSize={100}
@@ -14,10 +14,10 @@ const Column = ({ index, style }) => (
const Example = () => (
<List
direction="horizontal"
height={75}
itemCount={1000}
itemSize={getItemSize}
layout="horizontal"
width={300}
>
{Column}
+22 -8
View File
@@ -58,18 +58,14 @@ const PROPS = [
type: 'string',
},
{
defaultValue: '"vertical"',
defaultValue: '"ltr"',
description: (
<Fragment>
<p>Primary scroll direction of the list. Acceptable values are:</p>
<p>Determines the direction of text and horizontal scrolling.</p>
<ul>
<li>vertical (default) - Up/down scrolling.</li>
<li>horizontal - Left/right scrolling.</li>
<li>ltr (default)</li>
<li>rtl</li>
</ul>
<p>
Note that lists may scroll in both directions (depending on CSS) but
content will only be windowed in the primary direction.
</p>
</Fragment>
),
name: 'direction',
@@ -207,6 +203,24 @@ const PROPS = [
name: 'itemSize',
type: 'number',
},
{
defaultValue: '"vertical"',
description: (
<Fragment>
<p>Layout/orientation of the list. Acceptable values are:</p>
<ul>
<li>vertical (default) - Up/down scrolling.</li>
<li>horizontal - Left/right scrolling.</li>
</ul>
<p>
Note that lists may scroll in both directions (depending on CSS) but
content will only be windowed in the layout direction specified.
</p>
</Fragment>
),
name: 'layout',
type: 'string',
},
{
description: (
<Fragment>
+1 -1
View File
@@ -54,11 +54,11 @@ export default function() {
>
<FixedSizeList
className={styles.List}
direction="horizontal"
height={75}
itemCount={1000}
itemData="Column"
itemSize={100}
layout="horizontal"
width={300}
>
{Item}
-1
View File
@@ -39,7 +39,6 @@ export default function() {
itemSize={100}
layout="horizontal"
width={300}
style={{ direction: 'rtl' }}
>
{Item}
</FixedSizeList>
@@ -58,11 +58,11 @@ export default function() {
>
<VariableSizeList
className={styles.List}
direction="horizontal"
height={75}
itemCount={1000}
itemData="Column"
itemSize={index => columnSizes[index]}
layout="horizontal"
width={300}
>
{Item}