Skip to content

Commit f14bd50

Browse files
committed
Added docs for ArrowKeyStepper. Added tests.
1 parent 3545cdb commit f14bd50

File tree

10 files changed

+229
-31
lines changed

10 files changed

+229
-31
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ Examples for each component can be seen in [the documentation](docs/README.md).
5757

5858
Here are some online demos of each component:
5959

60+
* [ArrowKeyStepper](https://bvaughn.github.io/react-virtualized/?component=ArrowKeyStepper)
6061
* [AutoSizer](https://bvaughn.github.io/react-virtualized/?component=AutoSizer)
6162
* [ColumnSizer](https://bvaughn.github.io/react-virtualized/?component=ColumnSizer)
6263
* [FlexTable](https://bvaughn.github.io/react-virtualized/?component=FlexTable)

docs/ArrowKeyStepper.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
ArrowKeyStepper
2+
---------------
3+
4+
High-order component that decorates another virtualized component and responds to arrow-key events by scrolling one row or column at a time.
5+
This provides a snap-to behavior rather than the default browser scrolling behavior.
6+
7+
Note that unlike the other HOCs in react-virtualized, the `ArrowKeyStepper` adds a `<div>` element around its children in order to attach a key-down event handler.
8+
9+
### Prop Types
10+
| Property | Type | Required? | Description |
11+
|:---|:---|:---:|:---|
12+
| children | Function || Function respondible for rendering children. This function should implement the following signature: `({ onKeyDown, onSectionRendered, scrollToColumn, scrollToRow }) => PropTypes.element` |
13+
| columnsCount | Number || Number of columns in grid; for `FlexTable` and `VirtualScroll` this property should always be `1`. |
14+
| rowsCount | Number || Number of rows in grid. |
15+
16+
### Children function
17+
18+
The child function is passed the following named parameters:
19+
20+
| Parameter | Type | Description |
21+
|:---|:---|:---:|
22+
| onKeyDown | Function | Key-down event handler to be attached to the DOM hierarchy. |
23+
| onSectionRendered | Function | Pass-through callback to be attached to child component; informs the key-stepper which range of cells are currently visible. |
24+
| scrollToColumn | Number | Specifies which column in the child component should be visible |
25+
| scrollToRow | Number | Specifies which row in the child component should be visible |
26+
27+
### Examples
28+
29+
You can decorate any virtualized component (eg. `FlexTable`, `Grid`, or `VirtualScroll`) with arrow-key snapping like so:
30+
31+
```javascript
32+
import React from 'react';
33+
import ReactDOM from 'react-dom';
34+
import { ArrowKeyStepper, Grid } from 'react-virtualized';
35+
import 'react-virtualized/styles.css'; // only needs to be imported once
36+
37+
ReactDOM.render(
38+
<ArrowKeyStepper
39+
columnsCount={columnsCount}
40+
rowsCount={rowsCount}
41+
>
42+
{({ onKeyDown, onSectionRendered, scrollToColumn, scrollToRow }) => (
43+
<div onKeyDown={onKeyDown}>
44+
<Grid
45+
columnsCount={columnsCount}
46+
onSectionRendered={onSectionRendered}
47+
rowsCount={rowsCount}
48+
scrollToColumn={scrollToColumn}
49+
scrollToRow={scrollToRow}
50+
{...otherGridProps}
51+
/>
52+
</div>
53+
)}
54+
</ArrowKeyStepper>,
55+
document.getElementById('example')
56+
);
57+
```

docs/AutoSizer.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ High-order component that automatically adjusts the width and height of a single
66
### Prop Types
77
| Property | Type | Required? | Description |
88
|:---|:---|:---:|:---|
9-
| children | PropTypes.Element || Function respondible for rendering children. This function should implement the following signature: `({ height, width }) => PropTypes.element` |
9+
| children | Function || Function respondible for rendering children. This function should implement the following signature: `({ height, width }) => PropTypes.element` |
1010
| disableHeight | Boolean | | If true the child's `height` property will not be managed |
1111
| disableWidth | Boolean | | If true the child's `width` property will not be managed |
1212
| onResize | Function | Callback to be invoked on-resize; it is passed the following named parameters: `({ height, width })` |

docs/ColumnSizer.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ High-order component that auto-calculates column-widths for `Grid` cells.
66
### Prop Types
77
| Property | Type | Required? | Description |
88
|:---|:---|:---:|:---|
9-
| children | PropTypes.Element || Function respondible for rendering a virtualized Grid. This function should implement the following signature: `({ adjustedWidth, getColumnWidth, registerChild }) => PropTypes.element` |
9+
| children | Function || Function respondible for rendering a virtualized Grid. This function should implement the following signature: `({ adjustedWidth, getColumnWidth, registerChild }) => PropTypes.element` |
1010
| columnMaxWidth | Number | | Optional maximum allowed column width |
1111
| columnMinWidth | Number | | Optional minimum allowed column width |
1212
| width | Number || Width of Grid or `FlexTable` child |

docs/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Documentation
1010
* [VirtualScroll](VirtualScroll.md)
1111

1212
### High-Order Components
13+
* [ArrowKeyStepper](ArrowKeyStepper.md)
1314
* [AutoSizer](AutoSizer.md)
1415
* [ColumnSizer](ColumnSizer.md)
1516
* [InfiniteLoader](InfiniteLoader.md)

docs/ScrollSync.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ High order component that simplifies the process of synchronizing scrolling betw
66
### Prop Types
77
| Property | Type | Required? | Description |
88
|:---|:---|:---:|:---|
9-
| children | PropTypes.Element || Function respondible for rendering 2 or more virtualized components. This function should implement the following signature: `({ onScroll, scrollLeft, scrollTop }) => PropTypes.element` |
9+
| children | Function || Function respondible for rendering 2 or more virtualized components. This function should implement the following signature: `({ onScroll, scrollLeft, scrollTop }) => PropTypes.element` |
1010

1111
### Children function
1212

source/ArrowKeyStepper/ArrowKeyStepper.example.js

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -36,38 +36,40 @@ export default class ArrowKeyStepperExample extends Component {
3636

3737
<ContentBoxParagraph>
3838
This high-order component decorates a <code>VirtualScroll</code>, <code>FlexTable</code>, or <code>Grid</code> and responds to arrow-key events by scrolling one row or column at a time.
39-
Focus in the `Grid` below and type the left, right, up, or down arrow key for an example.
39+
Focus in the `Grid` below and use the left, right, up, or down arrow keys to move around within the grid.
40+
</ContentBoxParagraph>
41+
42+
<ContentBoxParagraph>
43+
Note that unlike the other HOCs in react-virtualized, the <code>ArrowKeyStepper</code> adds a <code>&lt;div&gt;</code> element around its children in order to attach a key-down event handler.
4044
</ContentBoxParagraph>
4145

4246
<ArrowKeyStepper
4347
columnsCount={100}
4448
rowsCount={100}
4549
>
46-
{({ onKeyDown, onSectionRendered, scrollToColumn, scrollToRow }) => (
50+
{({ onSectionRendered, scrollToColumn, scrollToRow }) => (
4751
<div>
4852
<ContentBoxParagraph>
4953
{`Most-recently-stepped column: ${scrollToColumn}, row: ${scrollToRow}`}
5054
</ContentBoxParagraph>
5155

52-
<div onKeyDown={onKeyDown}>
53-
<AutoSizer disableHeight>
54-
{({ width }) => (
55-
<Grid
56-
className={styles.Grid}
57-
columnWidth={this._getColumnWidth}
58-
columnsCount={100}
59-
height={300}
60-
onSectionRendered={onSectionRendered}
61-
renderCell={this._renderCell}
62-
rowHeight={this._getRowHeight}
63-
rowsCount={100}
64-
scrollToColumn={scrollToColumn}
65-
scrollToRow={scrollToRow}
66-
width={width}
67-
/>
68-
)}
69-
</AutoSizer>
70-
</div>
56+
<AutoSizer disableHeight>
57+
{({ width }) => (
58+
<Grid
59+
className={styles.Grid}
60+
columnWidth={this._getColumnWidth}
61+
columnsCount={100}
62+
height={200}
63+
onSectionRendered={onSectionRendered}
64+
renderCell={this._renderCell}
65+
rowHeight={this._getRowHeight}
66+
rowsCount={100}
67+
scrollToColumn={scrollToColumn}
68+
scrollToRow={scrollToRow}
69+
width={width}
70+
/>
71+
)}
72+
</AutoSizer>
7173
</div>
7274
)}
7375
</ArrowKeyStepper>

source/ArrowKeyStepper/ArrowKeyStepper.js

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/** @flow */
2-
import { Component, PropTypes } from 'react'
2+
import React, { Component, PropTypes } from 'react'
33
import shouldPureComponentUpdate from 'react-pure-render/function'
44

55
/**
@@ -22,6 +22,11 @@ export default class ArrowKeyStepper extends Component {
2222
scrollToRow: 0
2323
}
2424

25+
this._columnStartIndex = 0
26+
this._columnStopIndex = 0
27+
this._rowStartIndex = 0
28+
this._rowStopIndex = 0
29+
2530
this._onKeyDown = this._onKeyDown.bind(this)
2631
this._onSectionRendered = this._onSectionRendered.bind(this)
2732
}
@@ -30,12 +35,15 @@ export default class ArrowKeyStepper extends Component {
3035
const { children } = this.props
3136
const { scrollToColumn, scrollToRow } = this.state
3237

33-
return children({
34-
onKeyDown: this._onKeyDown,
35-
onSectionRendered: this._onSectionRendered,
36-
scrollToColumn,
37-
scrollToRow
38-
})
38+
return (
39+
<div onKeyDown={this._onKeyDown}>
40+
{children({
41+
onSectionRendered: this._onSectionRendered,
42+
scrollToColumn,
43+
scrollToRow
44+
})}
45+
</div>
46+
)
3947
}
4048

4149
_onKeyDown (event) {
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import React from 'react'
2+
import { findDOMNode } from 'react-dom'
3+
import { render } from '../TestUtils'
4+
import ArrowKeyStepper from './ArrowKeyStepper'
5+
import { Simulate } from 'react-addons-test-utils'
6+
7+
function renderTextContent (scrollToColumn, scrollToRow) {
8+
return `scrollToColumn:${scrollToColumn}, scrollToRow:${scrollToRow}`
9+
}
10+
11+
function ChildComponent ({ scrollToColumn, scrollToRow }) {
12+
return (
13+
<div>{renderTextContent(scrollToColumn, scrollToRow)}</div>
14+
)
15+
}
16+
17+
describe('ArrowKeyStepper', () => {
18+
function renderHelper ({
19+
columnsCount = 10,
20+
rowsCount = 10
21+
} = {}) {
22+
let onSectionRenderedCallback
23+
24+
const node = findDOMNode(render(
25+
<ArrowKeyStepper
26+
columnsCount={columnsCount}
27+
rowsCount={rowsCount}
28+
>
29+
{({ onSectionRendered, scrollToColumn, scrollToRow }) => {
30+
onSectionRenderedCallback = onSectionRendered
31+
32+
return (
33+
<ChildComponent
34+
scrollToColumn={scrollToColumn}
35+
scrollToRow={scrollToRow}
36+
/>
37+
)
38+
}}
39+
</ArrowKeyStepper>
40+
))
41+
42+
return {
43+
node,
44+
onSectionRendered: onSectionRenderedCallback
45+
}
46+
}
47+
48+
function assertCurrentScrollTo (node, scrollToColumn, scrollToRow) {
49+
expect(node.textContent).toEqual(renderTextContent(scrollToColumn, scrollToRow))
50+
}
51+
52+
it('should update :scrollToColumn and :scrollToRow in response to arrow keys', () => {
53+
const { node } = renderHelper()
54+
assertCurrentScrollTo(node, 0, 0)
55+
Simulate.keyDown(node, {key: 'ArrowDown'})
56+
assertCurrentScrollTo(node, 0, 1)
57+
Simulate.keyDown(node, {key: 'ArrowRight'})
58+
assertCurrentScrollTo(node, 1, 1)
59+
Simulate.keyDown(node, {key: 'ArrowUp'})
60+
assertCurrentScrollTo(node, 1, 0)
61+
Simulate.keyDown(node, {key: 'ArrowLeft'})
62+
assertCurrentScrollTo(node, 0, 0)
63+
})
64+
65+
it('should not scroll past the row and column boundaries provided', () => {
66+
const { node } = renderHelper({
67+
columnsCount: 2,
68+
rowsCount: 2
69+
})
70+
Simulate.keyDown(node, {key: 'ArrowDown'})
71+
Simulate.keyDown(node, {key: 'ArrowDown'})
72+
Simulate.keyDown(node, {key: 'ArrowDown'})
73+
assertCurrentScrollTo(node, 0, 1)
74+
Simulate.keyDown(node, {key: 'ArrowUp'})
75+
Simulate.keyDown(node, {key: 'ArrowUp'})
76+
Simulate.keyDown(node, {key: 'ArrowUp'})
77+
assertCurrentScrollTo(node, 0, 0)
78+
Simulate.keyDown(node, {key: 'ArrowRight'})
79+
Simulate.keyDown(node, {key: 'ArrowRight'})
80+
Simulate.keyDown(node, {key: 'ArrowRight'})
81+
assertCurrentScrollTo(node, 1, 0)
82+
Simulate.keyDown(node, {key: 'ArrowLeft'})
83+
Simulate.keyDown(node, {key: 'ArrowLeft'})
84+
Simulate.keyDown(node, {key: 'ArrowLeft'})
85+
assertCurrentScrollTo(node, 0, 0)
86+
})
87+
88+
it('should update :scrollToColumn and :scrollToRow relative to the most recent :onSectionRendered event', () => {
89+
const { node, onSectionRendered } = renderHelper()
90+
onSectionRendered({ // Simulate a scroll
91+
columnStartIndex: 0,
92+
columnStopIndex: 4,
93+
rowStartIndex: 4,
94+
rowStopIndex: 6
95+
})
96+
Simulate.keyDown(node, {key: 'ArrowDown'})
97+
assertCurrentScrollTo(node, 0, 7)
98+
99+
onSectionRendered({ // Simulate a scroll
100+
columnStartIndex: 5,
101+
columnStopIndex: 10,
102+
rowStartIndex: 2,
103+
rowStopIndex: 4
104+
})
105+
Simulate.keyDown(node, {key: 'ArrowUp'})
106+
assertCurrentScrollTo(node, 0, 1)
107+
108+
onSectionRendered({ // Simulate a scroll
109+
columnStartIndex: 4,
110+
columnStopIndex: 8,
111+
rowStartIndex: 5,
112+
rowStopIndex: 10
113+
})
114+
Simulate.keyDown(node, {key: 'ArrowRight'})
115+
assertCurrentScrollTo(node, 9, 1)
116+
117+
onSectionRendered({ // Simulate a scroll
118+
columnStartIndex: 2,
119+
columnStopIndex: 4,
120+
rowStartIndex: 2,
121+
rowStopIndex: 4
122+
})
123+
Simulate.keyDown(node, {key: 'ArrowLeft'})
124+
assertCurrentScrollTo(node, 1, 1)
125+
})
126+
})

source/Grid/Grid.test.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,9 @@ describe('Grid', () => {
137137
// Target offset for the last item then is 2,000 - 100
138138
expect(grid.state.scrollTop).toEqual(1900)
139139
})
140+
141+
// @TODO Test updating with new row + scrollTop, removing row, scrolling, then re-adding row (with same scrollTop)
142+
// @TODO Test updating with new row + scrollToIndex, removing row, scrolling, then re-adding row (with same scrollToIndex)
140143
})
141144

142145
describe('property updates', () => {

0 commit comments

Comments
 (0)