diff --git a/source/Masonry/Masonry.example.css b/source/Masonry/Masonry.example.css
index de61d1151..118ac5dc3 100644
--- a/source/Masonry/Masonry.example.css
+++ b/source/Masonry/Masonry.example.css
@@ -5,4 +5,14 @@
padding: 0.5rem;
background-color: #f7f7f7;
word-break: break-all;
+}
+
+.checkboxLabel {
+ margin-left: .5rem;
+}
+.checkboxLabel:first-of-type {
+ margin-left: 0;
+}
+.checkbox {
+ margin-right: 5px;
}
\ No newline at end of file
diff --git a/source/Masonry/Masonry.example.js b/source/Masonry/Masonry.example.js
index fb0d321ca..ee4bd8d64 100644
--- a/source/Masonry/Masonry.example.js
+++ b/source/Masonry/Masonry.example.js
@@ -5,6 +5,7 @@ import { ContentBox, ContentBoxHeader, ContentBoxParagraph } from '../demo/Conte
import { LabeledInput, InputRow } from '../demo/LabeledInput'
import { CellMeasurer, CellMeasurerCache } from '../CellMeasurer'
import AutoSizer from '../AutoSizer'
+import WindowScroller from '../WindowScroller'
import createCellPositioner from './createCellPositioner'
import Masonry from './Masonry'
import styles from './Masonry.example.css'
@@ -30,11 +31,13 @@ export default class GridExample extends PureComponent {
this.state = {
columnWidth: 200,
height: 300,
- gutterSize: 10
+ gutterSize: 10,
+ windowScrollerEnabled: false
}
this._cellRenderer = this._cellRenderer.bind(this)
this._onResize = this._onResize.bind(this)
+ this._renderAutoSizer = this._renderAutoSizer.bind(this)
this._renderMasonry = this._renderMasonry.bind(this)
this._setMasonryRef = this._setMasonryRef.bind(this)
}
@@ -43,9 +46,22 @@ export default class GridExample extends PureComponent {
const {
columnWidth,
height,
- gutterSize
+ gutterSize,
+ windowScrollerEnabled
} = this.state
+ let child
+
+ if (windowScrollerEnabled) {
+ child = (
+
+ {this._renderAutoSizer}
+
+ )
+ } else {
+ child = this._renderAutoSizer({ height })
+ }
+
return (
+
+
+
+
{
this._calculateColumnCount()
- this._initOrResetPositioner()
+ this._resetCellPositioner()
this._masonry.clearCellPositions()
})
}}
@@ -96,7 +132,7 @@ export default class GridExample extends PureComponent {
gutterSize: parseInt(event.target.value, 10) || 10
}, () => {
this._calculateColumnCount()
- this._initOrResetPositioner()
+ this._resetCellPositioner()
this._masonry.recomputeCellPositions()
})
}}
@@ -104,13 +140,7 @@ export default class GridExample extends PureComponent {
/>
-
- {this._renderMasonry}
-
+ {child}
)
}
@@ -159,55 +189,82 @@ export default class GridExample extends PureComponent {
)
}
- _initOrResetPositioner () {
- const {
- columnWidth,
- gutterSize
- } = this.state
-
+ _initCellPositioner () {
if (typeof this._cellPositioner === 'undefined') {
+ const {
+ columnWidth,
+ gutterSize
+ } = this.state
+
this._cellPositioner = createCellPositioner({
cellMeasurerCache: this._cache,
columnCount: this._columnCount,
columnWidth,
spacer: gutterSize
})
- } else {
- this._cellPositioner.reset({
- columnCount: this._columnCount,
- columnWidth,
- spacer: gutterSize
- })
}
}
- _onResize (prevProps, prevState) {
+ _onResize ({ height, width }) {
+ this._width = width
+
this._columnHeights = {}
this._calculateColumnCount()
- this._initOrResetPositioner()
+ this._resetCellPositioner()
this._masonry.recomputeCellPositions()
}
+ _renderAutoSizer ({ height, scrollTop }) {
+ this._height = height
+ this._scrollTop = scrollTop
+
+ return (
+
+ {this._renderMasonry}
+
+ )
+ }
+
_renderMasonry ({ width }) {
this._width = width
+
this._calculateColumnCount()
- this._initOrResetPositioner()
+ this._initCellPositioner()
- const { height } = this.state
+ const { height, windowScrollerEnabled } = this.state
return (
)
}
+ _resetCellPositioner () {
+ const {
+ columnWidth,
+ gutterSize
+ } = this.state
+
+ this._cellPositioner.reset({
+ columnCount: this._columnCount,
+ columnWidth,
+ spacer: gutterSize
+ })
+ }
+
_setMasonryRef (ref) {
this._masonry = ref
}
diff --git a/source/Masonry/Masonry.jest.js b/source/Masonry/Masonry.jest.js
index 3291ada0b..6bd5dd427 100644
--- a/source/Masonry/Masonry.jest.js
+++ b/source/Masonry/Masonry.jest.js
@@ -138,11 +138,20 @@ describe('Masonry', () => {
it('should measure additional cells on scroll when it runs out of measured cells', () => {
const cellMeasurerCache = createCellMeasurerCache()
- const rendered = findDOMNode(render(getMarkup({ cellMeasurerCache })))
+ const renderCallback = jest.fn().mockImplementation(index => index)
+ const cellRenderer = createCellRenderer(cellMeasurerCache, renderCallback)
+ const rendered = findDOMNode(render(getMarkup({ cellRenderer, cellMeasurerCache })))
expect(cellMeasurerCache.has(9)).toBe(false)
+
+ renderCallback.mockClear()
+
simulateScroll(rendered, 101)
expect(cellMeasurerCache.has(9)).toBe(true)
expect(cellMeasurerCache.has(10)).toBe(false)
+
+ // The first batch-measured cell in the new block should be the 10th one
+ // Verify that we measured the correct cell...
+ expect(renderCallback.mock.calls[0][0]).toBe(9)
})
it('should only render enough cells to fill the viewport plus overscanByPixels', () => {
@@ -155,19 +164,66 @@ describe('Masonry', () => {
simulateScroll(rendered, 1001)
assertVisibleCells(rendered, '27,29,30,31,32,33,34,35')
})
+
+ it('should still render correctly when autoHeight is true (eg WindowScroller)', () => {
+ // Share instances between renders to avoid resetting state in ways we don't intend
+ const cellMeasurerCache = createCellMeasurerCache()
+ const cellPositioner = createCellPositioner(cellMeasurerCache)
+
+ let rendered = findDOMNode(render(getMarkup({
+ autoHeight: true,
+ cellMeasurerCache,
+ cellPositioner
+ })))
+ assertVisibleCells(rendered, '0,1,2,3,4,5')
+ rendered = findDOMNode(render(getMarkup({
+ autoHeight: true,
+ cellMeasurerCache,
+ cellPositioner,
+ scrollTop: 51
+ })))
+ assertVisibleCells(rendered, '0,1,2,3,4,5,6')
+ rendered = findDOMNode(render(getMarkup({
+ autoHeight: true,
+ cellMeasurerCache,
+ cellPositioner,
+ scrollTop: 101
+ })))
+ assertVisibleCells(rendered, '0,2,3,4,5,6,7,8')
+ rendered = findDOMNode(render(getMarkup({
+ autoHeight: true,
+ cellMeasurerCache,
+ cellPositioner,
+ scrollTop: 1001
+ })))
+ assertVisibleCells(rendered, '27,29,30,31,32,33,34,35')
+ })
})
describe('recomputeCellPositions', () => {
it('should refresh all cell positions', () => {
+ // Share instances between renders to avoid resetting state in ways we don't intend
const cellMeasurerCache = createCellMeasurerCache()
- let rendered = findDOMNode(render(getMarkup({ cellMeasurerCache })))
- assertVisibleCells(rendered, '0,1,2,3,4,5')
- const component = render(getMarkup({
+ const cellPositioner = jest.fn().mockImplementation(
+ createCellPositioner(cellMeasurerCache)
+ )
+
+ let rendered = findDOMNode(render(getMarkup({
cellMeasurerCache,
- cellPositioner: index => ({
+ cellPositioner
+ })))
+ assertVisibleCells(rendered, '0,1,2,3,4,5')
+
+ cellPositioner.mockImplementation(
+ index => ({
left: 0,
top: index * CELL_SIZE_MULTIPLIER
})
+ )
+
+ const component = render(getMarkup({
+ cellMeasurerCache,
+ cellPositioner
}))
rendered = findDOMNode(component)
assertVisibleCells(rendered, '0,1,2,3,4,5')
diff --git a/source/Masonry/Masonry.js b/source/Masonry/Masonry.js
index 0c9afa775..9b4235c16 100644
--- a/source/Masonry/Masonry.js
+++ b/source/Masonry/Masonry.js
@@ -41,6 +41,7 @@ export default class Masonry extends PureComponent {
props: Props;
static defaultProps = {
+ autoHeight: false,
keyMapper: identity,
onCellsRendered: noop,
onScroll: noop,
@@ -109,8 +110,28 @@ export default class Masonry extends PureComponent {
this._invokeOnCellsRenderedCallback()
}
+ componentWillUnmount () {
+ if (this._debounceResetIsScrollingId) {
+ clearTimeout(this._debounceResetIsScrollingId)
+ }
+ }
+
+ componentWillReceiveProps (nextProps) {
+ const { scrollTop } = this.props
+
+ if (scrollTop !== nextProps.scrollTop) {
+ this._debounceResetIsScrolling()
+
+ this.setState({
+ isScrolling: true,
+ scrollTop: nextProps.scrollTop
+ })
+ }
+ }
+
render () {
const {
+ autoHeight,
cellCount,
cellMeasurerCache,
cellRenderer,
@@ -151,10 +172,10 @@ export default class Masonry extends PureComponent {
)
)
- for (let index = 0; index < batchSize; index++) {
+ for (let index = measuredCellCount; index < measuredCellCount + batchSize; index++) {
children.push(
cellRenderer({
- index: index + measuredCellCount,
+ index: index,
isScrolling,
key: keyMapper(index),
parent: this,
@@ -213,7 +234,7 @@ export default class Masonry extends PureComponent {
style={{
boxSizing: 'border-box',
direction: 'ltr',
- height,
+ height: autoHeight ? 'auto' : height,
overflowX: 'hidden',
overflowY: estimateTotalHeight < height ? 'hidden' : 'auto',
position: 'relative',
@@ -430,6 +451,7 @@ type Position = {
export type Positioner = (index: number) => Position;
type Props = {
+ autoHeight: boolean,
cellCount: number,
cellMeasurerCache: CellMeasurerCache,
cellPositioner: Positioner,