From 2e91ec7ffb59f1aefbedf0143f2ed1b472a0099c Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 2 Dec 2016 19:36:31 -0800 Subject: [PATCH] Added to to handle case when header items change or resize. also better handles window resize events. --- CHANGELOG.md | 3 + docs/WindowScroller.md | 8 ++ package.json | 2 +- .../WindowScroller/WindowScroller.example.js | 48 ++++++++++-- source/WindowScroller/WindowScroller.js | 13 +++- source/WindowScroller/WindowScroller.test.js | 73 ++++++++++++++++++- 6 files changed, 133 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1144383b5..eae30ef75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ Changelog ------------ +##### 8.7.0 +Added `updatePosition` to `WindowScroller` to handle case when header items change or resize. `WindowScroller` also better handles window resize events. + ##### 8.6.1 Updated `CellSizeCache` interface for the better perfomance by removing `has` methods, reducing a double hashtable lookup to a single lookup. Special thanks to @arusakov for this contribution! diff --git a/docs/WindowScroller.md b/docs/WindowScroller.md index 923b0761d..08f0428eb 100644 --- a/docs/WindowScroller.md +++ b/docs/WindowScroller.md @@ -14,6 +14,14 @@ This may change with a future release but for the time being this HOC is should | onResize | Function | | Callback to be invoked on-resize; it is passed the following named parameters: `({ height: number })`. | | onScroll | Function | | Callback to be invoked on-scroll; it is passed the following named parameters: `({ scrollTop: number })`. | +### Public Methods + +##### updatePosition + +Recalculates scroll position from the top of page. + +This methoed is automatically triggered when the component mounts as well as when the browser resizes. It should be manually called if the page header (eg any items in the DOM "above" the `WindowScroller`) resizes or changes. + ### Examples ```javascript diff --git a/package.json b/package.json index 55978fb4a..b9f630e39 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "description": "React components for efficiently rendering large, scrollable lists and tabular data", "author": "Brian Vaughn ", "user": "bvaughn", - "version": "8.6.1", + "version": "8.7.0", "homepage": "https://github.com/bvaughn/react-virtualized", "main": "dist/commonjs/index.js", "module": "dist/es/index.js", diff --git a/source/WindowScroller/WindowScroller.example.js b/source/WindowScroller/WindowScroller.example.js index be6f9ecb8..7d167ecb8 100644 --- a/source/WindowScroller/WindowScroller.example.js +++ b/source/WindowScroller/WindowScroller.example.js @@ -17,11 +17,17 @@ export default class AutoSizerExample extends Component { constructor (props) { super(props) + this.state = { + showHeaderText: true + } + + this._hideHeader = this._hideHeader.bind(this) this._rowRenderer = this._rowRenderer.bind(this) } render () { const { list } = this.context + const { showHeaderText } = this.state return ( @@ -31,13 +37,27 @@ export default class AutoSizerExample extends Component { docsLink='https://github.com/bvaughn/react-virtualized/blob/master/docs/WindowScroller.md' /> - - This component decorates List, Table, or any other component - and manages the window scroll to scroll through the list - + {showHeaderText && ( + + This component decorates List, Table, or any other component + and manages the window scroll to scroll through the list + + )} + + {showHeaderText && ( + + + + )}
- + { + this._windowScroller = ref + }} + > {({ height, isScrolling, scrollTop }) => ( {({ width }) => ( @@ -45,9 +65,10 @@ export default class AutoSizerExample extends Component { autoHeight className={styles.List} height={height} + overscanRowCount={2} rowCount={list.size} rowHeight={30} - rowRenderer={({ index, key, style }) => this._rowRenderer({ index, isScrolling, key, style })} + rowRenderer={({ index, isVisible, key, style }) => this._rowRenderer({ index, isScrolling, isVisible, key, style })} scrollTop={scrollTop} width={width} /> @@ -64,11 +85,22 @@ export default class AutoSizerExample extends Component { return shallowCompare(this, nextProps, nextState) } - _rowRenderer ({ index, isScrolling, key, style }) { + _hideHeader () { + const { showHeaderText } = this.state + + this.setState({ + showHeaderText: !showHeaderText + }, () => { + this._windowScroller.updatePosition() + }) + } + + _rowRenderer ({ index, isScrolling, isVisible, key, style }) { const { list } = this.context const row = list.get(index) const className = cn(styles.row, { - [styles.rowScrolling]: isScrolling + [styles.rowScrolling]: isScrolling, + isVisible: isVisible }) return ( diff --git a/source/WindowScroller/WindowScroller.js b/source/WindowScroller/WindowScroller.js index 2cd894205..cfc77c9e9 100644 --- a/source/WindowScroller/WindowScroller.js +++ b/source/WindowScroller/WindowScroller.js @@ -43,14 +43,18 @@ export default class WindowScroller extends Component { this._enablePointerEventsAfterDelayCallback = this._enablePointerEventsAfterDelayCallback.bind(this) } - componentDidMount () { - const { height } = this.state - + updatePosition () { // Subtract documentElement top to handle edge-case where a user is navigating back (history) from an already-scrolled bage. // In this case the body's top position will be a negative number and this element's top will be increased (by that amount). this._positionFromTop = ReactDOM.findDOMNode(this).getBoundingClientRect().top - document.documentElement.getBoundingClientRect().top + } + + componentDidMount () { + const { height } = this.state + + this.updatePosition() if (height !== window.innerHeight) { this.setState({ @@ -59,6 +63,7 @@ export default class WindowScroller extends Component { } registerScrollListener(this) + window.addEventListener('resize', this._onResizeWindow, false) } @@ -92,6 +97,8 @@ export default class WindowScroller extends Component { _onResizeWindow (event) { const { onResize } = this.props + this.updatePosition() + const height = window.innerHeight || 0 this.setState({ height }) diff --git a/source/WindowScroller/WindowScroller.test.js b/source/WindowScroller/WindowScroller.test.js index b0c52969b..89941b22d 100644 --- a/source/WindowScroller/WindowScroller.test.js +++ b/source/WindowScroller/WindowScroller.test.js @@ -27,8 +27,11 @@ describe('WindowScroller', () => { document.dispatchEvent(new window.Event('resize', { bubbles: true })) } - function getMarkup (props = {}) { - return ( + function getMarkup ({ + headerElements, + ...props + } = {}) { + const windowScroller = ( {({ height, isScrolling, scrollTop }) => ( { )} ) + + if (headerElements) { + return ( +
+ {headerElements} + {windowScroller} +
+ ) + } else { + return windowScroller + } } // Starts updating scrollTop only when the top position is reached @@ -185,4 +199,59 @@ describe('WindowScroller', () => { window.innerHeight = 500 }) }) + + describe('updatePosition', () => { + it('should calculate the initial offset from the top of the page when mounted', () => { + let windowScroller + + render(getMarkup({ + headerElements:
, + ref: (ref) => { + windowScroller = ref + } + })) + + expect(windowScroller._positionFromTop > 100).toBeTruthy() + }) + + it('should recalculate the offset from the top when the window resizes', () => { + let windowScroller + + const rendered = render(getMarkup({ + headerElements: , + ref: (ref) => { + windowScroller = ref + } + })) + + expect(windowScroller._positionFromTop < 200).toBeTruthy() + + const header = findDOMNode(rendered).querySelector('#header') + header.style.height = '200px' + + simulateWindowResize({ height: 1000 }) + + expect(windowScroller._positionFromTop > 200).toBeTruthy() + }) + + it('should recalculate the offset from the top if called externally', () => { + let windowScroller + + const rendered = render(getMarkup({ + headerElements: , + ref: (ref) => { + windowScroller = ref + } + })) + + expect(windowScroller._positionFromTop < 200).toBeTruthy() + + const header = findDOMNode(rendered).querySelector('#header') + header.style.height = '200px' + + windowScroller.updatePosition() + + expect(windowScroller._positionFromTop > 200).toBeTruthy() + }) + }) })