Fixed scroll-to methods for RTL lists and grids
This commit is contained in:
+34
-24
@@ -3,7 +3,7 @@
|
||||
import memoizeOne from 'memoize-one';
|
||||
import { createElement, PureComponent } from 'react';
|
||||
import { cancelTimeout, requestTimeout } from './timer';
|
||||
import { getScrollbarSize } from './domHelpers';
|
||||
import { getScrollbarSize, isRTLOffsetNegative } from './domHelpers';
|
||||
|
||||
import type { TimeoutID } from './timer';
|
||||
|
||||
@@ -86,7 +86,6 @@ type State = {|
|
||||
isScrolling: boolean,
|
||||
horizontalScrollDirection: ScrollDirection,
|
||||
scrollLeft: number,
|
||||
scrollLeftRTLNonStandard: boolean | null,
|
||||
scrollTop: number,
|
||||
scrollUpdateWasRequested: boolean,
|
||||
verticalScrollDirection: ScrollDirection,
|
||||
@@ -195,7 +194,6 @@ export default function createGridComponent({
|
||||
typeof this.props.initialScrollLeft === 'number'
|
||||
? this.props.initialScrollLeft
|
||||
: 0,
|
||||
scrollLeftRTLNonStandard: null,
|
||||
scrollTop:
|
||||
typeof this.props.initialScrollTop === 'number'
|
||||
? this.props.initialScrollTop
|
||||
@@ -326,21 +324,39 @@ export default function createGridComponent({
|
||||
|
||||
componentDidMount() {
|
||||
const { initialScrollLeft, initialScrollTop } = this.props;
|
||||
if (typeof initialScrollLeft === 'number' && this._outerRef != null) {
|
||||
((this._outerRef: any): HTMLDivElement).scrollLeft = initialScrollLeft;
|
||||
}
|
||||
if (typeof initialScrollTop === 'number' && this._outerRef != null) {
|
||||
((this._outerRef: any): HTMLDivElement).scrollTop = initialScrollTop;
|
||||
|
||||
if (this._outerRef != null) {
|
||||
const outerRef = ((this._outerRef: any): HTMLElement);
|
||||
if (typeof initialScrollLeft === 'number') {
|
||||
outerRef.scrollLeft = initialScrollLeft;
|
||||
}
|
||||
if (typeof initialScrollTop === 'number') {
|
||||
outerRef.scrollTop = initialScrollTop;
|
||||
}
|
||||
}
|
||||
|
||||
this._callPropsCallbacks();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const { direction } = this.props;
|
||||
const { scrollLeft, scrollTop, scrollUpdateWasRequested } = this.state;
|
||||
if (scrollUpdateWasRequested && this._outerRef !== null) {
|
||||
((this._outerRef: any): HTMLDivElement).scrollLeft = scrollLeft;
|
||||
((this._outerRef: any): HTMLDivElement).scrollTop = scrollTop;
|
||||
|
||||
if (scrollUpdateWasRequested && this._outerRef != null) {
|
||||
const outerRef = ((this._outerRef: any): HTMLElement);
|
||||
if (direction === 'rtl') {
|
||||
const isNegative = isRTLOffsetNegative();
|
||||
if (isNegative) {
|
||||
outerRef.scrollLeft = -scrollLeft;
|
||||
} else {
|
||||
const { clientWidth, scrollWidth } = outerRef;
|
||||
outerRef.scrollLeft = scrollWidth - clientWidth - scrollLeft;
|
||||
}
|
||||
} else {
|
||||
outerRef.scrollLeft = Math.max(0, scrollLeft);
|
||||
}
|
||||
|
||||
outerRef.scrollTop = Math.max(0, scrollTop);
|
||||
}
|
||||
|
||||
this._callPropsCallbacks();
|
||||
@@ -707,22 +723,17 @@ export default function createGridComponent({
|
||||
|
||||
const { direction } = this.props;
|
||||
|
||||
let scrollLeftRTLNonStandard = prevState.scrollLeftRTLNonStandard;
|
||||
|
||||
// TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements.
|
||||
// Chrome does not seem to adhere; its scrollLeft values are positive (measured relative to the left).
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollLeft
|
||||
let calculatedScrollLeft = scrollLeft;
|
||||
if (direction === 'rtl') {
|
||||
// TRICKY It's important that we only set this value once; iOS elastic bounce can calse false positives.
|
||||
if (scrollLeftRTLNonStandard === null && scrollLeft !== 0) {
|
||||
scrollLeftRTLNonStandard = scrollLeft > 0;
|
||||
}
|
||||
const isNegative = isRTLOffsetNegative();
|
||||
|
||||
if (scrollLeftRTLNonStandard) {
|
||||
calculatedScrollLeft = scrollWidth - clientWidth - scrollLeft;
|
||||
} else {
|
||||
// TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements.
|
||||
// Chrome does not seem to adhere; its scrollLeft values are positive (measured relative to the left).
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollLeft
|
||||
if (isNegative) {
|
||||
calculatedScrollLeft = -scrollLeft;
|
||||
} else {
|
||||
calculatedScrollLeft = scrollWidth - clientWidth - scrollLeft;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -741,7 +752,6 @@ export default function createGridComponent({
|
||||
horizontalScrollDirection:
|
||||
prevState.scrollLeft < scrollLeft ? 'forward' : 'backward',
|
||||
scrollLeft: calculatedScrollLeft,
|
||||
scrollLeftRTLNonStandard,
|
||||
scrollTop: calculatedScrollTop,
|
||||
verticalScrollDirection:
|
||||
prevState.scrollTop < scrollTop ? 'forward' : 'backward',
|
||||
|
||||
+26
-23
@@ -3,6 +3,7 @@
|
||||
import memoizeOne from 'memoize-one';
|
||||
import { createElement, PureComponent } from 'react';
|
||||
import { cancelTimeout, requestTimeout } from './timer';
|
||||
import { isRTLOffsetNegative } from './domHelpers';
|
||||
|
||||
import type { TimeoutID } from './timer';
|
||||
|
||||
@@ -68,7 +69,6 @@ type State = {|
|
||||
isScrolling: boolean,
|
||||
scrollDirection: ScrollDirection,
|
||||
scrollOffset: number,
|
||||
scrollOffsetRTLNonStandard: boolean | null,
|
||||
scrollUpdateWasRequested: boolean,
|
||||
|};
|
||||
|
||||
@@ -161,7 +161,6 @@ export default function createListComponent({
|
||||
typeof this.props.initialScrollOffset === 'number'
|
||||
? this.props.initialScrollOffset
|
||||
: 0,
|
||||
scrollOffsetRTLNonStandard: null,
|
||||
scrollUpdateWasRequested: false,
|
||||
};
|
||||
|
||||
@@ -217,14 +216,13 @@ export default function createListComponent({
|
||||
componentDidMount() {
|
||||
const { direction, initialScrollOffset, layout } = this.props;
|
||||
|
||||
if (typeof initialScrollOffset === 'number' && this._outerRef !== null) {
|
||||
if (typeof initialScrollOffset === 'number' && this._outerRef != null) {
|
||||
const outerRef = ((this._outerRef: any): HTMLElement);
|
||||
// TODO Deprecate direction "horizontal"
|
||||
if (direction === 'horizontal' || layout === 'horizontal') {
|
||||
((this
|
||||
._outerRef: any): HTMLDivElement).scrollLeft = initialScrollOffset;
|
||||
outerRef.scrollLeft = initialScrollOffset;
|
||||
} else {
|
||||
((this
|
||||
._outerRef: any): HTMLDivElement).scrollTop = initialScrollOffset;
|
||||
outerRef.scrollTop = initialScrollOffset;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,12 +233,23 @@ export default function createListComponent({
|
||||
const { direction, layout } = this.props;
|
||||
const { scrollOffset, scrollUpdateWasRequested } = this.state;
|
||||
|
||||
if (scrollUpdateWasRequested && this._outerRef !== null) {
|
||||
if (scrollUpdateWasRequested && this._outerRef != null) {
|
||||
const outerRef = ((this._outerRef: any): HTMLElement);
|
||||
// TODO Deprecate direction "horizontal"
|
||||
if (direction === 'horizontal' || layout === 'horizontal') {
|
||||
((this._outerRef: any): HTMLDivElement).scrollLeft = scrollOffset;
|
||||
if (direction === 'rtl') {
|
||||
const isNegative = isRTLOffsetNegative();
|
||||
if (isNegative) {
|
||||
outerRef.scrollLeft = -scrollOffset;
|
||||
} else {
|
||||
const { clientWidth, scrollWidth } = outerRef;
|
||||
outerRef.scrollLeft = scrollWidth - clientWidth - scrollOffset;
|
||||
}
|
||||
} else {
|
||||
outerRef.scrollLeft = scrollOffset;
|
||||
}
|
||||
} else {
|
||||
((this._outerRef: any): HTMLDivElement).scrollTop = scrollOffset;
|
||||
outerRef.scrollTop = scrollOffset;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -498,22 +507,17 @@ export default function createListComponent({
|
||||
|
||||
const { direction } = this.props;
|
||||
|
||||
let scrollOffsetRTLNonStandard = prevState.scrollOffsetRTLNonStandard;
|
||||
|
||||
// TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements.
|
||||
// Chrome does not seem to adhere; its scrollLeft values are positive (measured relative to the left).
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollLeft
|
||||
let scrollOffset = scrollLeft;
|
||||
if (direction === 'rtl') {
|
||||
// TRICKY It's important that we only set this value once; iOS elastic bounce can calse false positives.
|
||||
if (scrollOffsetRTLNonStandard === null && scrollLeft !== 0) {
|
||||
scrollOffsetRTLNonStandard = scrollLeft > 0;
|
||||
}
|
||||
const isNegative = isRTLOffsetNegative();
|
||||
|
||||
if (scrollOffsetRTLNonStandard) {
|
||||
scrollOffset = scrollWidth - clientWidth - scrollLeft;
|
||||
// TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements.
|
||||
// Chrome does not seem to adhere; its scrollLeft values are positive (measured relative to the left).
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollLeft
|
||||
if (isNegative) {
|
||||
scrollOffset = -scrollLeft;
|
||||
} else {
|
||||
scrollOffset = -scrollOffset;
|
||||
scrollOffset = scrollWidth - clientWidth - scrollLeft;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -528,7 +532,6 @@ export default function createListComponent({
|
||||
scrollDirection:
|
||||
prevState.scrollOffset < scrollLeft ? 'forward' : 'backward',
|
||||
scrollOffset,
|
||||
scrollOffsetRTLNonStandard,
|
||||
scrollUpdateWasRequested: false,
|
||||
};
|
||||
}, this._resetIsScrollingDebounced);
|
||||
|
||||
@@ -20,3 +20,40 @@ export function getScrollbarSize(recalculate?: boolean = false): number {
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
let cachedRTLResult: boolean | null = null;
|
||||
|
||||
// TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements.
|
||||
// Chrome does not seem to adhere; its scrollLeft values are positive (measured relative to the left).
|
||||
// Safari's elastic bounce makes detecting this even more complicated wrt potential false positives.
|
||||
// The safest way to check this is to intentionally set a negative offset,
|
||||
// and then verify that the subsequent "scroll" event matches the negative offset.
|
||||
// If it does not match, then we can assume a non-standard RTL scroll implementation.
|
||||
export function isRTLOffsetNegative(recalculate?: boolean = false): boolean {
|
||||
if (cachedRTLResult === null || recalculate) {
|
||||
const outerDiv = document.createElement('div');
|
||||
const outerStyle = outerDiv.style;
|
||||
outerStyle.width = '50px';
|
||||
outerStyle.height = '50px';
|
||||
outerStyle.overflow = 'scroll';
|
||||
outerStyle.direction = 'rtl';
|
||||
|
||||
const innerDiv = document.createElement('div');
|
||||
const innerStyle = innerDiv.style;
|
||||
innerStyle.width = '100px';
|
||||
innerStyle.height = '100px';
|
||||
|
||||
outerDiv.appendChild(innerDiv);
|
||||
|
||||
((document.body: any): HTMLBodyElement).appendChild(outerDiv);
|
||||
|
||||
outerDiv.scrollLeft = -10;
|
||||
cachedRTLResult = outerDiv.scrollLeft === -10;
|
||||
|
||||
((document.body: any): HTMLBodyElement).removeChild(outerDiv);
|
||||
|
||||
return cachedRTLResult;
|
||||
}
|
||||
|
||||
return cachedRTLResult;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import React, { PureComponent, useRef } from 'react';
|
||||
import { FixedSizeGrid, FixedSizeList } from 'react-window';
|
||||
import CodeBlock from '../../components/CodeBlock';
|
||||
import ProfiledExample from '../../components/ProfiledExample';
|
||||
@@ -47,6 +47,22 @@ class Item extends PureComponent {
|
||||
}
|
||||
|
||||
export default function() {
|
||||
const gridRef = useRef();
|
||||
const listRef = useRef();
|
||||
|
||||
const scrollGridTo500 = () => gridRef.current.scrollTo({ scrollLeft: 500 });
|
||||
const scrollGridTo1500 = () => gridRef.current.scrollTo({ scrollLeft: 1500 });
|
||||
const scrollGridToItem8Auto = () =>
|
||||
gridRef.current.scrollToItem({ columnIndex: 8 });
|
||||
const scrollGridToItem12Center = () =>
|
||||
gridRef.current.scrollToItem({ columnIndex: 12, align: 'center' });
|
||||
|
||||
const scrollListTo500 = () => listRef.current.scrollTo(500);
|
||||
const scrollListTo1500 = () => listRef.current.scrollTo(1500);
|
||||
const scrollListToItem8Auto = () => listRef.current.scrollToItem(8);
|
||||
const scrollListToItem12Center = () =>
|
||||
listRef.current.scrollToItem(12, 'center');
|
||||
|
||||
return (
|
||||
<div className={styles.ExampleWrapper}>
|
||||
<h1 className={styles.ExampleHeader}>RTL List</h1>
|
||||
@@ -55,13 +71,32 @@ export default function() {
|
||||
className={styles.ExampleDemo}
|
||||
sandbox="fixed-size-list-horizontal-rtl"
|
||||
>
|
||||
<button
|
||||
className={styles.ExampleButton}
|
||||
onClick={scrollListToItem8Auto}
|
||||
>
|
||||
Scroll to item 8 (align: auto)
|
||||
</button>
|
||||
<button
|
||||
className={styles.ExampleButton}
|
||||
onClick={scrollListToItem12Center}
|
||||
>
|
||||
Scroll to item 12 (align: center)
|
||||
</button>
|
||||
<button className={styles.ExampleButton} onClick={scrollListTo500}>
|
||||
Scroll to 500
|
||||
</button>
|
||||
<button className={styles.ExampleButton} onClick={scrollListTo1500}>
|
||||
Scroll to 1500
|
||||
</button>
|
||||
<FixedSizeList
|
||||
className={styles.List}
|
||||
direction="rtl"
|
||||
height={75}
|
||||
itemCount={1000}
|
||||
itemCount={20}
|
||||
itemSize={100}
|
||||
layout="horizontal"
|
||||
ref={listRef}
|
||||
width={300}
|
||||
>
|
||||
{Item}
|
||||
@@ -77,13 +112,32 @@ export default function() {
|
||||
className={styles.ExampleDemo}
|
||||
sandbox="fixed-size-grid-rtl"
|
||||
>
|
||||
<button
|
||||
className={styles.ExampleButton}
|
||||
onClick={scrollGridToItem8Auto}
|
||||
>
|
||||
Scroll to column 8 (align: auto)
|
||||
</button>
|
||||
<button
|
||||
className={styles.ExampleButton}
|
||||
onClick={scrollGridToItem12Center}
|
||||
>
|
||||
Scroll to column 12 (align: center)
|
||||
</button>
|
||||
<button className={styles.ExampleButton} onClick={scrollGridTo500}>
|
||||
Scroll to 500
|
||||
</button>
|
||||
<button className={styles.ExampleButton} onClick={scrollGridTo1500}>
|
||||
Scroll to 1500
|
||||
</button>
|
||||
<FixedSizeGrid
|
||||
className={styles.Grid}
|
||||
columnCount={1000}
|
||||
columnCount={20}
|
||||
columnWidth={100}
|
||||
direction="rtl"
|
||||
height={150}
|
||||
rowCount={1000}
|
||||
ref={gridRef}
|
||||
rowCount={20}
|
||||
rowHeight={35}
|
||||
width={300}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user