react-virtualized
Advanced tools
Comparing version 2.8.0 to 3.0.0
Changelog | ||
------------ | ||
## 3.0.0 | ||
CSS styles have been split into two groups: functional styles (eg. `position`, `overflow`) and presentational styles (eg. `text-transform`, `color`) and both have been converted to inline styles rather than being loaded as CSS. This was done primarily to simplify usage for universal/isomorphic rendering. | ||
For more information on customizing styles refer to the [documentation](https://github.com/bvaughn/react-virtualized/#customizing-styles)... | ||
#### 2.8.0 | ||
@@ -53,3 +58,3 @@ Changed `Autosizer` component to support a single child instead of the `ChildComponent` property. | ||
#### 2.0.0 | ||
## 2.0.0 | ||
Set `shouldPureComponentUpdate` on component prototypes instead of instances. | ||
@@ -70,3 +75,3 @@ Dropped half-ass support for React 0.13. This module has always depended on React 0.14 but it was checking in previous versions and trying to be backwards compatible with 0.13. Since that check is no longer in place, this is a major version bump (even though there is no real new functionality being added). | ||
#### 1.0.0 | ||
## 1.0.0 | ||
Package JSON updated so that "main" entry points to `dist/react-virtualized.js` to provide easier integration for users that don't want Babel/Webpack to have to process their `node_modules` folder. | ||
@@ -73,0 +78,0 @@ |
@@ -9,2 +9,13 @@ AutoSizer | ||
|:---|:---|:---:|:---| | ||
| ChildComponent| ReactComponent | ✓ | React component (class or function) to be created and managed. | | ||
| children | element | ✓ | Element to be parameterized with `width` and `height` properties | | ||
| ChildComponent| ReactComponent | | React component (class or function) to be created and managed. This property has been deprecated in favor of `children`. | | ||
| className | string | | Optional CSS class name. | | ||
| styleSheet | object | | Presentational styles for component. | | ||
### Stylesheet properties | ||
The AutoSizer component supports the following nested styles in its `styleSheet` property: | ||
| Property | Description | | ||
|:---|:---|:---:|:---| | ||
| AutoSizer | Main (outer) element | |
@@ -27,2 +27,3 @@ FlexTable | ||
| sortDirection | [SortDirection](SortDirection.md) | | Data is currently sorted in this direction (if it is sorted at all) | | ||
| styleSheet | object | | Presentational styles for component. | | ||
| width | Number | ✓ | Fixed/available width for out DOM element | | ||
@@ -41,1 +42,17 @@ | verticalPadding | Number | | Vertical padding of outer DOM element | | ||
Scroll the list to ensure the row at the specified index is visible. This method exists so that a user can forcefully scroll to the same row twice. (The `scrollToIndex` property would not change in that case and so it would not be picked up by VirtualScroll.) | ||
### Stylesheet properties | ||
The FlexTable component supports the following nested styles in its `styleSheet` property: | ||
| Property | Description | | ||
|:---|:---|:---:|:---| | ||
| FlexTable | Main (outer) element | | ||
| headerColumn | Header cell (similar to `thead > tr > th`) | | ||
| headerRow | Header row (similar to `thead > tr`) | | ||
| headerTruncatedText | Element within header cell responsible for truncating text | | ||
| row | Table row (akin to `tbody > tr`) | | ||
| rowColumn | Table column (akin to `tbody > tr > td`) | | ||
| sortableHeaderColumn | Applied to header columns that are sortable | | ||
| sortableHeaderIcon | SVG sort indicator | | ||
| truncatedColumnText | Element within table column responsible for truncating text | |
@@ -17,2 +17,3 @@ VirtualScroll | ||
| scrollToIndex | Number | | Row index to ensure visible (by forcefully scrolling if necessary) | | ||
| styleSheet | object | | Presentational styles for component. | | ||
@@ -29,1 +30,9 @@ ### Public Methods | ||
Scroll the list to ensure the row at the specified index is visible. This method exists so that a user can forcefully scroll to the same row twice. (The `scrollToIndex` property would not change in that case and so it would not be picked up by VirtualScroll.) | ||
### Stylesheet properties | ||
The VirtualScroll component supports the following nested styles in its `styleSheet` property: | ||
| Property | Description | | ||
|:---|:---|:---:|:---| | ||
| VirtualScroll | Main (outer) element | |
@@ -6,3 +6,3 @@ { | ||
"user": "bvaughn", | ||
"version": "2.8.0", | ||
"version": "3.0.0", | ||
"scripts": { | ||
@@ -109,3 +109,4 @@ "build": "npm run build:demo && npm run build:dist", | ||
"dependencies": { | ||
"classnames": "^2.2.1", | ||
"classnames": "^2.2.3", | ||
"inline-style-prefixer": "^0.6.2", | ||
"raf": "^3.1.0", | ||
@@ -112,0 +113,0 @@ "react-pure-render": "^1.0.2" |
@@ -25,2 +25,28 @@ <img src="https://cloud.githubusercontent.com/assets/29597/11737732/0ca1e55e-9f91-11e5-97f3-098f2f8ed866.png" alt="React virtualized" data-canonical-src="https://cloud.githubusercontent.com/assets/29597/11737732/0ca1e55e-9f91-11e5-97f3-098f2f8ed866.png" width="330" height="100" /> | ||
### Customizing Styles | ||
React virtual CSS styles are split into two groups: functional styles (eg. `position`, `overflow`) and presentational styles (eg. `text-transform`, `color`). Both are defined as inline styles (rather than external CSS) to simplify usage for universal/isomorphic rendering. | ||
Functional styles cannot be overridden but you can override presentational styles in a variety of ways: | ||
###### Styling a single component | ||
Supply a custom `styleSheet` to a component (eg. `<VirtualScroll styleSheet={...}/>`) to override default styles for a single component instance. Styles injected as properties will be automatically processed to add vendor prefixes. | ||
Learn more about which styles a component supports in the [API docs](https://github.com/bvaughn/react-virtualized/blob/master/docs/). | ||
###### Styling all instances | ||
Override the static `defaultStyleSheet` property of a component class (eg. `FlexTable.defaultStyleSheet = {...}` to customize styles for all instances. | ||
###### Global CSS | ||
Load an external CSS file that defines global classes (eg. `FlexTable`, `FlexTable__row`) to append to default inline styles. | ||
Learn more about which class names a component supports in the [API docs](https://github.com/bvaughn/react-virtualized/blob/master/docs/). | ||
###### CSS Modules | ||
If you are using CSS modules you can specify custom class names to be appended to a component instance (eg. `FlexTable` supports `className`, `headerClassName`, and `rowClassName` properties). | ||
Learn more about which class names are supported in the [API docs](https://github.com/bvaughn/react-virtualized/blob/master/docs/). | ||
Examples | ||
@@ -27,0 +53,0 @@ --------------- |
/** @flow */ | ||
import cn from 'classnames' | ||
import React, { Component, PropTypes } from 'react' | ||
import shouldPureComponentUpdate from 'react-pure-render/function' | ||
import styles from './AutoSizer.css' | ||
import { prefixStyleSheet } from '../utils' | ||
@@ -19,3 +20,2 @@ /** | ||
children: PropTypes.element, | ||
/** | ||
@@ -27,3 +27,7 @@ * React component to manage as a child. | ||
*/ | ||
ChildComponent: PropTypes.any | ||
ChildComponent: PropTypes.any, | ||
/** Optional CSS class name */ | ||
className: PropTypes.string, | ||
/** Specifies presentational styles for component. */ | ||
styleSheet: PropTypes.object | ||
} | ||
@@ -36,2 +40,3 @@ | ||
height: 0, | ||
styleSheet: prefixStyleSheet(props.styleSheet || AutoSizer.defaultStyleSheet), | ||
width: 0 | ||
@@ -57,5 +62,13 @@ } | ||
componentWillUpdate (nextProps, nextState) { | ||
if (this.props.styleSheet !== nextProps.styleSheet) { | ||
this.setState({ | ||
styleSheet: prefixStyleSheet(nextProps.styleSheet) | ||
}) | ||
} | ||
} | ||
render () { | ||
const { children, ChildComponent, ...props } = this.props | ||
const { height, width } = this.state | ||
const { children, ChildComponent, className, ...props } = this.props | ||
const { height, styleSheet, width } = this.state | ||
@@ -80,3 +93,7 @@ let child | ||
ref={this._setRef} | ||
className={styles.Wrapper} | ||
className={cn('AutoSizer', className)} | ||
style={{ | ||
...styleSheet.AutoSizer, | ||
...functionalStyles.AutoSizer | ||
}} | ||
> | ||
@@ -101,1 +118,14 @@ {child} | ||
} | ||
const functionalStyles = { | ||
AutoSizer: { | ||
width: '100%', | ||
height: '100%' | ||
} | ||
} | ||
/** Default presentational styles for all <AutoSizer> instances. */ | ||
AutoSizer.defaultStyleSheet = { | ||
AutoSizer: { | ||
} | ||
} |
@@ -15,3 +15,3 @@ import React from 'react' | ||
// Used by the renderOrUpdateList() helper method | ||
// Used by the renderOrUpdateComponent() helper method | ||
// Unless we attach the node to body, getBoundingClientRect() won't work | ||
@@ -31,4 +31,6 @@ var node = null | ||
bar = 123, | ||
className = undefined, | ||
foo = 456, | ||
height = 100, | ||
styleSheet = undefined, | ||
width = 200 | ||
@@ -41,3 +43,6 @@ } = {} | ||
autoSizer = ( | ||
<AutoSizer> | ||
<AutoSizer | ||
className={className} | ||
styleSheet={styleSheet} | ||
> | ||
<ChildComponent | ||
@@ -54,3 +59,5 @@ bar={bar} | ||
bar={bar} | ||
className={className} | ||
foo={foo} | ||
styleSheet={styleSheet} | ||
/> | ||
@@ -67,4 +74,4 @@ ) | ||
function renderOrUpdateList (useReactChild, props) { | ||
let rendered = render(getMarkup(props), node) | ||
function renderOrUpdateComponent (useReactChild, props) { | ||
let rendered = render(getMarkup(useReactChild, props), node) | ||
@@ -76,3 +83,3 @@ return findDOMNode(rendered) | ||
[false, true].forEach(useReactChild => { | ||
const component = renderOrUpdateList(useReactChild) | ||
const component = renderOrUpdateComponent(useReactChild) | ||
expect(findDOMNode(component).textContent).toContain('foo:456') | ||
@@ -85,3 +92,3 @@ expect(findDOMNode(component).textContent).toContain('bar:123') | ||
[false, true].forEach(useReactChild => { | ||
const component = renderOrUpdateList(useReactChild) | ||
const component = renderOrUpdateComponent(useReactChild) | ||
let domNode = findDOMNode(component) | ||
@@ -93,3 +100,32 @@ expect(domNode.textContent).toContain('height:100') | ||
describe('styles and classeNames', () => { | ||
it('should use the expected global CSS classNames', () => { | ||
const node = renderOrUpdateComponent() | ||
expect(node.querySelector('.AutoSizer')).toBeTruthy() | ||
}) | ||
it('should use a custom :className if specified', () => { | ||
const node = renderOrUpdateComponent(true, { className: 'foo' }) | ||
expect(node.querySelector('.AutoSizer').className).toContain('foo') | ||
}) | ||
it('should use custom :styleSheet if specified', () => { | ||
const node = renderOrUpdateComponent(true, { | ||
styleSheet: { | ||
AutoSizer: { color: 'red' } | ||
} | ||
}) | ||
expect(node.querySelector('.AutoSizer').style.color).toEqual('red') | ||
}) | ||
it('should use overriden static styles', () => { | ||
const backup = { ...AutoSizer.defaultStyleSheet } | ||
AutoSizer.defaultStyleSheet.AutoSizer = { color: 'blue' } | ||
const node = renderOrUpdateComponent() | ||
expect(node.querySelector('.AutoSizer').style.color).toEqual('blue') | ||
AutoSizer.defaultStyleSheet = backup | ||
}) | ||
}) | ||
// TODO It would be nice to test that resize events update the width/height | ||
}) |
@@ -117,2 +117,3 @@ /** @flow */ | ||
width={430} | ||
headerClassName={styles.headerColumn} | ||
headerHeight={headerHeight} | ||
@@ -119,0 +120,0 @@ height={height} |
/** @flow */ | ||
import cn from 'classnames' | ||
import FlexColumn from './FlexColumn' | ||
import React, { Component, PropTypes } from 'react' | ||
import cn from 'classnames' | ||
import shouldPureComponentUpdate from 'react-pure-render/function' | ||
import VirtualScroll from '../VirtualScroll' | ||
import FlexColumn from './FlexColumn' | ||
import styles from './FlexTable.css' | ||
import { prefixStyle, prefixStyleSheet } from '../utils' | ||
@@ -30,11 +30,2 @@ export const SortDirection = { | ||
static defaultProps = { | ||
disableHeader: false, | ||
horizontalPadding: 0, | ||
noRowsRenderer: () => null, | ||
onRowClick: () => null, | ||
onRowsRendered: () => null, | ||
verticalPadding: 0 | ||
} | ||
static propTypes = { | ||
@@ -98,2 +89,4 @@ /** One or more FlexColumns describing the data displayed in this row */ | ||
sortDirection: PropTypes.oneOf([SortDirection.ASC, SortDirection.DESC]), | ||
/** Specifies presentational styles for component. */ | ||
styleSheet: PropTypes.object, | ||
/** Fixed/available width for out DOM element */ | ||
@@ -105,2 +98,11 @@ width: PropTypes.number.isRequired, | ||
static defaultProps = { | ||
disableHeader: false, | ||
horizontalPadding: 0, | ||
noRowsRenderer: () => null, | ||
onRowClick: () => null, | ||
onRowsRendered: () => null, | ||
verticalPadding: 0 | ||
} | ||
constructor (props) { | ||
@@ -110,2 +112,6 @@ super(props) | ||
this._createRow = this._createRow.bind(this) | ||
this.state = { | ||
styleSheet: prefixStyleSheet(props.styleSheet || FlexTable.defaultStyleSheet) | ||
} | ||
} | ||
@@ -127,2 +133,10 @@ | ||
componentWillUpdate (nextProps, nextState) { | ||
if (this.props.styleSheet !== nextProps.styleSheet) { | ||
this.setState({ | ||
styleSheet: prefixStyleSheet(nextProps.styleSheet) | ||
}) | ||
} | ||
} | ||
render () { | ||
@@ -143,2 +157,4 @@ const { | ||
const { styleSheet } = this.state | ||
const availableRowsHeight = height - headerHeight - verticalPadding | ||
@@ -151,2 +167,3 @@ | ||
} | ||
const rowClass = rowClassName instanceof Function ? rowClassName(-1) : rowClassName | ||
@@ -156,4 +173,6 @@ | ||
<div | ||
className={cn(styles.FlexTable, className)} | ||
className={cn('FlexTable', className)} | ||
style={{ | ||
...styleSheet.FlexTable, | ||
...functionalStyles.FlexTable, | ||
maxWidth: width | ||
@@ -164,4 +183,6 @@ }} | ||
<div | ||
className={cn(styles.headerRow, rowClass)} | ||
className={cn('FlexTable__headerRow', rowClass)} | ||
style={{ | ||
...styleSheet.headerRow, | ||
...functionalStyles.headerRow, | ||
height: headerHeight | ||
@@ -196,2 +217,3 @@ }} | ||
} = column.props | ||
const { styleSheet } = this.state | ||
const cellData = cellDataGetter(dataKey, rowData, columnData) | ||
@@ -201,7 +223,2 @@ const renderedCell = cellRenderer(cellData, dataKey, rowData, rowIndex, columnData) | ||
const flex = this._getFlexStyleForColumn(column) | ||
const style = { | ||
WebkitFlex: flex, | ||
msFlex: flex, | ||
flex: flex | ||
} | ||
@@ -215,7 +232,12 @@ const title = typeof renderedCell === 'string' | ||
key={`Row${rowIndex}-Col${columnIndex}`} | ||
className={styles.rowColumn} | ||
style={style} | ||
className={cn('FlexTable__rowColumn', cellClassName)} | ||
style={{ | ||
...styleSheet.rowColumn, | ||
...functionalStyles.rowColumn, | ||
...prefixStyle({ flex }) | ||
}} | ||
> | ||
<div | ||
className={cn(styles.truncatedColumnText, cellClassName)} | ||
className='FlexTable__truncatedColumnText' | ||
style={styleSheet.truncatedColumnText} | ||
title={title} | ||
@@ -230,3 +252,4 @@ > | ||
_createHeader (column, columnIndex) { | ||
const { sort, sortBy, sortDirection } = this.props | ||
const { headerClassName, sort, sortBy, sortDirection } = this.props | ||
const { styleSheet } = this.state | ||
const { dataKey, disableSort, label } = column.props | ||
@@ -236,12 +259,15 @@ const showSortIndicator = sortBy === dataKey | ||
const classNames = cn(styles.headerColumn, | ||
this.props.headerClassName, | ||
const sortableStyles = sortEnabled | ||
? styleSheet.sortableHeaderColumn | ||
: {} | ||
const classNames = cn( | ||
'FlexTable__headerColumn', | ||
headerClassName, | ||
column.props.headerClassName, | ||
{ | ||
[styles.sortableHeaderColumn]: sortEnabled | ||
'FlexTable__sortableHeaderColumn': sortEnabled | ||
} | ||
) | ||
const style = { | ||
flex: this._getFlexStyleForColumn(column) | ||
} | ||
const flex = this._getFlexStyleForColumn(column) | ||
@@ -258,7 +284,13 @@ // If this is a sortable header, clicking it should update the table data's sorting. | ||
className={classNames} | ||
style={style} | ||
style={{ | ||
...styleSheet.headerColumn, | ||
...functionalStyles.headerColumn, | ||
...sortableStyles, | ||
...prefixStyle({ flex }) | ||
}} | ||
onClick={onClick} | ||
> | ||
<div | ||
className={styles.headerTruncatedText} | ||
className='FlexTable__headerTruncatedText' | ||
style={styleSheet.headerTruncatedText} | ||
title={label} | ||
@@ -269,3 +301,6 @@ > | ||
{showSortIndicator && | ||
<SortIndicator sortDirection={sortDirection} /> | ||
<SortIndicator | ||
sortDirection={sortDirection} | ||
styleSheet={styleSheet} | ||
/> | ||
} | ||
@@ -284,4 +319,6 @@ </div> | ||
} = this.props | ||
const { styleSheet } = this.state | ||
const rowClass = rowClassName instanceof Function ? rowClassName(rowIndex) : rowClassName | ||
const renderedRow = React.Children.map( | ||
@@ -300,5 +337,7 @@ children, | ||
key={rowIndex} | ||
className={cn(styles.row, rowClass)} | ||
className={cn('FlexTable__row', rowClass)} | ||
onClick={() => onRowClick(rowIndex)} | ||
style={{ | ||
...styleSheet.row, | ||
...functionalStyles.row, | ||
height: rowHeight | ||
@@ -340,19 +379,22 @@ }} | ||
*/ | ||
export function SortIndicator ({ sortDirection }) { | ||
export function SortIndicator ({ sortDirection, styleSheet }) { | ||
const classNames = cn('FlexTable__sortableHeaderIcon', { | ||
'FlexTable__sortableHeaderIcon--ASC': sortDirection === SortDirection.ASC, | ||
'FlexTable__sortableHeaderIcon--DESC': sortDirection === SortDirection.DESC | ||
}) | ||
return ( | ||
<div data-sort-direction={sortDirection}> | ||
<svg | ||
className={styles.sortableHeaderIcon} | ||
width={18} | ||
height={18} | ||
viewBox='0 0 24 24' | ||
xmlns='http://www.w3.org/2000/svg' | ||
> | ||
{sortDirection === SortDirection.ASC | ||
? <path d='M7 14l5-5 5 5z'/> | ||
: <path d='M7 10l5 5 5-5z'/> | ||
} | ||
<path d='M0 0h24v24H0z' fill='none'/> | ||
</svg> | ||
</div> | ||
<svg | ||
className={classNames} | ||
style={styleSheet.sortableHeaderIcon} | ||
width={18} | ||
height={18} | ||
viewBox='0 0 24 24' | ||
xmlns='http://www.w3.org/2000/svg' | ||
> | ||
{sortDirection === SortDirection.ASC | ||
? <path d='M7 14l5-5 5 5z'/> | ||
: <path d='M7 10l5 5 5-5z'/> | ||
} | ||
<path d='M0 0h24v24H0z' fill='none'/> | ||
</svg> | ||
) | ||
@@ -363,1 +405,74 @@ } | ||
} | ||
/** Functional styles can't be overridden so they only need to be prefixed once. */ | ||
const functionalStyles = prefixStyleSheet({ | ||
FlexTable: { | ||
width: '100%' | ||
}, | ||
headerColumn: { | ||
display: 'flex', | ||
flexDirection: 'row', | ||
overflow: 'hidden' | ||
}, | ||
headerRow: { | ||
display: 'flex', | ||
flexDirection: 'row', | ||
alignItems: 'center', | ||
overflow: 'hidden' | ||
}, | ||
row: { | ||
display: 'flex', | ||
flexDirection: 'row', | ||
alignItems: 'center', | ||
overflow: 'hidden' | ||
}, | ||
rowColumn: { | ||
display: 'flex', | ||
overflow: 'hidden', | ||
height: '100%' | ||
} | ||
}) | ||
/** Default presentational styles for all <FlexTable> instances. */ | ||
FlexTable.defaultStyleSheet = { | ||
FlexTable: { | ||
}, | ||
headerColumn: { | ||
marginRight: 10, | ||
minWidth: 0, | ||
alignItems: 'center' | ||
}, | ||
headerRow: { | ||
fontWeight: 700, | ||
textTransform: 'uppercase', | ||
paddingLeft: 10 | ||
}, | ||
headerTruncatedText: { | ||
whiteSpace: 'nowrap', | ||
textOverflow: 'ellipsis', | ||
overflow: 'hidden' | ||
}, | ||
row: { | ||
paddingLeft: 10 | ||
}, | ||
rowColumn: { | ||
marginRight: 10, | ||
minWidth: 0, | ||
justifyContent: 'center', | ||
flexDirection: 'column' | ||
}, | ||
sortableHeaderColumn: { | ||
cursor: 'pointer' | ||
}, | ||
sortableHeaderIcon: { | ||
flex: '0 0 24', | ||
height: '1em', | ||
width: '1em', | ||
fill: 'currentColor' | ||
}, | ||
truncatedColumnText: { | ||
whiteSpace: 'nowrap', | ||
textOverflow: 'ellipsis', | ||
overflow: 'hidden' | ||
} | ||
} |
@@ -8,16 +8,3 @@ import React from 'react' | ||
import FlexTable, { SortDirection } from './FlexTable' | ||
import styles from './FlexTable.css' | ||
// Helper functions to convert className style selectors to css-module friendly selector. | ||
function findAll (element, expression) { | ||
var parts = expression.replace('.', '').split(':') | ||
var className = styles[parts.shift()] | ||
var modifier = parts.length ? `:${parts.shift()}` : '' | ||
return element.querySelectorAll(`.${className}${modifier}`) | ||
} | ||
function find (element, expression) { | ||
const matches = findAll(element, expression) | ||
return matches.length ? matches[0] : null | ||
} | ||
describe('FlexTable', () => { | ||
@@ -56,3 +43,5 @@ beforeAll(() => jasmine.clock().install()) | ||
cellDataGetter = undefined, | ||
className = undefined, | ||
disableSort = false, | ||
headerClassName = undefined, | ||
headerHeight = 20, | ||
@@ -63,6 +52,6 @@ height = 100, | ||
onRowsRendered = undefined, | ||
rowClassName = undefined, | ||
rowGetter = immutableRowGetter, | ||
rowHeight = 10, | ||
rowsCount = list.size, | ||
rowClassName = undefined, | ||
scrollToIndex = undefined, | ||
@@ -72,2 +61,3 @@ sort = undefined, | ||
sortDirection = undefined, | ||
styleSheet = undefined, | ||
width = 100 | ||
@@ -77,3 +67,4 @@ } = {}) { | ||
<FlexTable | ||
width={width} | ||
className={className} | ||
headerClassName={headerClassName} | ||
headerHeight={headerHeight} | ||
@@ -84,9 +75,11 @@ height={height} | ||
onRowsRendered={onRowsRendered} | ||
rowClassName={rowClassName} | ||
rowGetter={rowGetter} | ||
rowHeight={rowHeight} | ||
rowsCount={rowsCount} | ||
rowClassName={rowClassName} | ||
sort={sort} | ||
sortBy={sortBy} | ||
sortDirection={sortDirection} | ||
styleSheet={styleSheet} | ||
width={width} | ||
> | ||
@@ -147,4 +140,4 @@ <FlexColumn | ||
// 100px height should fit 1 header (20px) and 8 rows (10px each) - | ||
expect(findAll(tableDOMNode, '.headerRow').length).toEqual(1) | ||
expect(findAll(tableDOMNode, '.row').length).toEqual(8) | ||
expect(tableDOMNode.querySelectorAll('.FlexTable__headerRow').length).toEqual(1) | ||
expect(tableDOMNode.querySelectorAll('.FlexTable__row').length).toEqual(8) | ||
}) | ||
@@ -157,3 +150,3 @@ | ||
const tableDOMNode = findDOMNode(table) | ||
const columns = findAll(tableDOMNode, '.headerColumn') | ||
const columns = tableDOMNode.querySelectorAll('.FlexTable__headerColumn') | ||
@@ -173,3 +166,3 @@ expect(columns.length).toEqual(2) | ||
const tableDOMNode = findDOMNode(table) | ||
const rows = findAll(tableDOMNode, '.row') | ||
const rows = tableDOMNode.querySelectorAll('.FlexTable__row') | ||
expect(rows.length).toEqual(2) | ||
@@ -180,3 +173,3 @@ | ||
let rowData = list.get(index) | ||
let columns = findAll(row, '.rowColumn') | ||
let columns = row.querySelectorAll('.FlexTable__rowColumn') | ||
expect(columns.length).toEqual(2) | ||
@@ -196,3 +189,3 @@ expect(columns[0].textContent).toEqual(rowData.get('name')) | ||
const tableDOMNode = findDOMNode(table) | ||
const nameColumns = findAll(tableDOMNode, '.rowColumn:first-of-type') | ||
const nameColumns = tableDOMNode.querySelectorAll('.FlexTable__rowColumn:first-of-type') | ||
@@ -210,3 +203,3 @@ for (let index = 0; index < nameColumns.length; index++) { | ||
const tableDOMNode = findDOMNode(table) | ||
const nameColumns = findAll(tableDOMNode, '.rowColumn:first-of-type') | ||
const nameColumns = tableDOMNode.querySelectorAll('.FlexTable__rowColumn:first-of-type') | ||
@@ -225,3 +218,3 @@ for (let index = 0; index < nameColumns.length; index++) { | ||
const tableDOMNode = findDOMNode(table) | ||
const nameColumn = find(tableDOMNode, '.rowColumn:first-of-type') | ||
const nameColumn = tableDOMNode.querySelector('.FlexTable__rowColumn:first-of-type') | ||
expect(nameColumn.children[0].getAttribute('title')).toContain('Custom') | ||
@@ -235,3 +228,3 @@ }) | ||
const tableDOMNode = findDOMNode(table) | ||
const nameColumn = find(tableDOMNode, '.rowColumn:first-of-type') | ||
const nameColumn = tableDOMNode.querySelector('.FlexTable__rowColumn:first-of-type') | ||
expect(nameColumn.children[0].getAttribute('title')).toEqual(null) | ||
@@ -245,5 +238,5 @@ }) | ||
const tableDOMNode = findDOMNode(table) | ||
const nameColumn = findAll(tableDOMNode, '.headerColumn:first-of-type') | ||
const nameColumn = tableDOMNode.querySelectorAll('.FlexTable__headerColumn:first-of-type') | ||
expect(nameColumn.className).not.toContain(styles.sortableHeaderColumn) | ||
expect(nameColumn.className).not.toContain('FlexTable__sortableHeaderColumn') | ||
}) | ||
@@ -257,6 +250,6 @@ | ||
const tableDOMNode = findDOMNode(table) | ||
const nameColumn = findAll(tableDOMNode, '.headerColumn:first-of-type') | ||
const nameColumn = tableDOMNode.querySelectorAll('.FlexTable__headerColumn:first-of-type') | ||
expect(nameColumn.className).not.toContain(styles.sortableHeaderColumn) | ||
expect(findAll(tableDOMNode, '.sortableHeaderColumn').length).toEqual(1) // Email only | ||
expect(nameColumn.className).not.toContain('FlexTable__sortableHeaderColumn') | ||
expect(tableDOMNode.querySelectorAll('.FlexTable__sortableHeaderColumn').length).toEqual(1) // Email only | ||
}) | ||
@@ -269,6 +262,6 @@ | ||
const tableDOMNode = findDOMNode(table) | ||
const nameColumn = find(tableDOMNode, '.headerColumn:first-of-type') | ||
const nameColumn = tableDOMNode.querySelector('.FlexTable__headerColumn:first-of-type') | ||
expect(nameColumn.className).toContain(styles.sortableHeaderColumn) | ||
expect(findAll(tableDOMNode, '.sortableHeaderColumn').length).toEqual(2) // Email and Name | ||
expect(nameColumn.className).toContain('FlexTable__sortableHeaderColumn') | ||
expect(tableDOMNode.querySelectorAll('.FlexTable__sortableHeaderColumn').length).toEqual(2) // Email and Name | ||
}) | ||
@@ -285,6 +278,6 @@ | ||
const tableDOMNode = findDOMNode(table) | ||
const nameColumn = find(tableDOMNode, '.headerColumn:first-of-type') | ||
const nameColumn = tableDOMNode.querySelector('.FlexTable__headerColumn:first-of-type') | ||
expect(find(nameColumn, '.sortableHeaderIcon')).not.toEqual(null) | ||
expect(nameColumn.querySelector(`[data-sort-direction=${sortDirection}]`)).not.toEqual(null) | ||
expect(nameColumn.querySelector('.FlexTable__sortableHeaderIcon')).not.toEqual(null) | ||
expect(nameColumn.querySelector(`.FlexTable__sortableHeaderIcon--${sortDirection}`)).not.toEqual(null) | ||
}) | ||
@@ -303,3 +296,3 @@ }) | ||
const tableDOMNode = findDOMNode(table) | ||
const nameColumn = find(tableDOMNode, '.headerColumn:first-of-type') | ||
const nameColumn = tableDOMNode.querySelector('.FlexTable__headerColumn:first-of-type') | ||
@@ -324,3 +317,3 @@ Simulate.click(nameColumn) | ||
const tableDOMNode = findDOMNode(table) | ||
const nameColumn = find(tableDOMNode, '.headerColumn:first-of-type') | ||
const nameColumn = tableDOMNode.querySelector('.FlexTable__headerColumn:first-of-type') | ||
@@ -362,3 +355,3 @@ Simulate.click(nameColumn) | ||
const tableDOMNode = findDOMNode(table) | ||
const rows = findAll(tableDOMNode, '.row') | ||
const rows = tableDOMNode.querySelectorAll('.FlexTable__row') | ||
Simulate.click(rows[0]) | ||
@@ -376,3 +369,3 @@ Simulate.click(rows[3]) | ||
const tableDOMNode = findDOMNode(table) | ||
const rows = findAll(tableDOMNode, '.row') | ||
const rows = tableDOMNode.querySelectorAll('.FlexTable__row') | ||
for (let index = 0; index < rows.length; index++) { | ||
@@ -389,3 +382,3 @@ let row = rows[index] | ||
const tableDOMNode = findDOMNode(table) | ||
const rows = findAll(tableDOMNode, '.row') | ||
const rows = tableDOMNode.querySelectorAll('.FlexTable__row') | ||
for (let index = 0; index < rows.length; index++) { | ||
@@ -465,2 +458,53 @@ let row = rows[index] | ||
}) | ||
describe('styles and classeNames', () => { | ||
it('should use the expected global CSS classNames', () => { | ||
const node = findDOMNode(renderTable({ | ||
sort: () => {}, | ||
sortBy: 'name', | ||
sortDirection: SortDirection.ASC | ||
})) | ||
expect(node.className).toEqual('FlexTable') | ||
expect(node.querySelector('.FlexTable__headerRow')).toBeTruthy() | ||
expect(node.querySelector('.FlexTable__rowColumn')).toBeTruthy() | ||
expect(node.querySelector('.FlexTable__truncatedColumnText')).toBeTruthy() | ||
expect(node.querySelector('.FlexTable__headerColumn')).toBeTruthy() | ||
expect(node.querySelector('.FlexTable__headerTruncatedText')).toBeTruthy() | ||
expect(node.querySelector('.FlexTable__row')).toBeTruthy() | ||
expect(node.querySelector('.FlexTable__sortableHeaderColumn')).toBeTruthy() | ||
expect(node.querySelector('.FlexTable__sortableHeaderIcon')).toBeTruthy() | ||
}) | ||
it('should use a custom :className if specified', () => { | ||
const node = findDOMNode(renderTable({ | ||
className: 'foo', | ||
headerClassName: 'bar', | ||
rowClassName: 'baz' | ||
})) | ||
expect(node.className).toContain('foo') | ||
expect(node.querySelectorAll('.bar').length).toEqual(2) | ||
expect(node.querySelectorAll('.baz').length).toEqual(9) | ||
}) | ||
it('should use custom :styleSheet if specified', () => { | ||
const node = findDOMNode(renderTable({ | ||
styleSheet: { | ||
headerColumn: { color: 'red' }, | ||
rowColumn: { color: 'green' } | ||
} | ||
})) | ||
expect(node.querySelector('.FlexTable__headerColumn').style.color).toEqual('red') | ||
expect(node.querySelector('.FlexTable__rowColumn').style.color).toEqual('green') | ||
}) | ||
it('should use overriden static styles', () => { | ||
const backup = { ...FlexTable.defaultStyleSheet } | ||
FlexTable.defaultStyleSheet.headerColumn = { color: 'blue' } | ||
FlexTable.defaultStyleSheet.rowColumn = { color: 'purple' } | ||
const node = findDOMNode(renderTable()) | ||
expect(node.querySelector('.FlexTable__headerColumn').style.color).toEqual('blue') | ||
expect(node.querySelector('.FlexTable__rowColumn').style.color).toEqual('purple') | ||
FlexTable.defaultStyleSheet = backup | ||
}) | ||
}) | ||
}) |
@@ -0,1 +1,5 @@ | ||
import Prefixer from 'inline-style-prefixer' | ||
const prefixer = new Prefixer() | ||
/** | ||
@@ -173,1 +177,19 @@ * Binary search function inspired by react-infinite. | ||
} | ||
/** | ||
* Adds vender prefixes to a style object. | ||
*/ | ||
export function prefixStyle (style) { | ||
return prefixer.prefix(style) | ||
} | ||
/** | ||
* Adds vender prefixes for all of the styles in a stylesheet and returns a prefixed copy. | ||
*/ | ||
export function prefixStyleSheet (styleSheet) { | ||
const prefixedStyleSheet = {} | ||
for (var style in styleSheet) { | ||
prefixedStyleSheet[style] = prefixStyle(styleSheet[style]) | ||
} | ||
return prefixedStyleSheet | ||
} |
/** @flow */ | ||
import shouldPureComponentUpdate from 'react-pure-render/function' | ||
import React, { Component, PropTypes } from 'react' | ||
import cn from 'classnames' | ||
import raf from 'raf' | ||
import { | ||
@@ -10,6 +6,14 @@ getUpdatedOffsetForIndex, | ||
initCellMetadata, | ||
initOnRowsRenderedHelper | ||
initOnRowsRenderedHelper, | ||
prefixStyleSheet | ||
} from '../utils' | ||
import styles from './VirtualScroll.css' | ||
import cn from 'classnames' | ||
import raf from 'raf' | ||
import React, { Component, PropTypes } from 'react' | ||
import shouldPureComponentUpdate from 'react-pure-render/function' | ||
/** | ||
* Specifies the number of miliseconds during which to disable pointer events while a scroll is in progress. | ||
* This improves performance and makes scrolling smoother. | ||
*/ | ||
const IS_SCROLLING_TIMEOUT = 150 | ||
@@ -50,3 +54,5 @@ | ||
/** Row index to ensure visible (by forcefully scrolling if necessary) */ | ||
scrollToIndex: PropTypes.number | ||
scrollToIndex: PropTypes.number, | ||
/** Specifies presentational styles for component. */ | ||
styleSheet: PropTypes.object | ||
} | ||
@@ -65,2 +71,3 @@ | ||
isScrolling: false, | ||
styleSheet: prefixStyleSheet(props.styleSheet || VirtualScroll.defaultStyleSheet), | ||
scrollTop: 0 | ||
@@ -188,2 +195,8 @@ } | ||
if (this.props.styleSheet !== nextProps.styleSheet) { | ||
this.setState({ | ||
styleSheet: prefixStyleSheet(nextProps.styleSheet) | ||
}) | ||
} | ||
// Don't compare rowHeight if it's a function because inline functions would cause infinite loops. | ||
@@ -227,3 +240,4 @@ // In that event users should use recomputeRowHeights() to inform of changes. | ||
isScrolling, | ||
scrollTop | ||
scrollTop, | ||
styleSheet | ||
} = this.state | ||
@@ -251,5 +265,8 @@ | ||
let datum = this._cellMetadata[i] | ||
let child = React.cloneElement( | ||
rowRenderer(i), { | ||
let child = rowRenderer(i) | ||
child = React.cloneElement( | ||
child, | ||
{ | ||
style: { | ||
...child.props.style, | ||
position: 'absolute', | ||
@@ -270,3 +287,3 @@ top: datum.offset, | ||
ref='scrollingContainer' | ||
className={cn(styles.VirtualScroll, className)} | ||
className={cn('VirtualScroll', className)} | ||
onKeyDown={this._onKeyPress} | ||
@@ -277,2 +294,4 @@ onScroll={this._onScroll} | ||
style={{ | ||
...styleSheet.VirtualScroll, | ||
...functionalStyles.VirtualScroll, | ||
height: height | ||
@@ -283,4 +302,4 @@ }} | ||
<div | ||
className={styles.InnerScrollContainer} | ||
style={{ | ||
...functionalStyles.innerScrollContainer, | ||
height: this._getTotalRowsHeight(), | ||
@@ -486,1 +505,21 @@ maxHeight: this._getTotalRowsHeight(), | ||
} | ||
/** Functional styles can't be overridden so they only need to be prefixed once. */ | ||
const functionalStyles = prefixStyleSheet({ | ||
VirtualScroll: { | ||
position: 'relative', | ||
overflow: 'auto', | ||
outline: 0 | ||
}, | ||
innerScrollContainer: { | ||
boxSizing: 'border-box', | ||
overflowX: 'auto', | ||
overflowY: 'hidden' | ||
} | ||
}) | ||
/** Default presentational styles for all <VirtualScroll> instances. */ | ||
VirtualScroll.defaultStyleSheet = { | ||
VirtualScroll: { | ||
} | ||
} |
@@ -24,2 +24,3 @@ import React from 'react' | ||
function getMarkup ({ | ||
className = undefined, | ||
height = 100, | ||
@@ -30,3 +31,4 @@ noRowsRenderer = undefined, | ||
rowsCount = list.size, | ||
scrollToIndex = undefined | ||
scrollToIndex = undefined, | ||
styleSheet = undefined | ||
} = {}) { | ||
@@ -46,2 +48,3 @@ function rowRenderer (index) { | ||
<VirtualScroll | ||
className={className} | ||
height={height} | ||
@@ -54,2 +57,3 @@ noRowsRenderer={noRowsRenderer} | ||
scrollToIndex={scrollToIndex} | ||
styleSheet={styleSheet} | ||
/> | ||
@@ -229,2 +233,31 @@ ) | ||
}) | ||
describe('styles and classeNames', () => { | ||
it('should use the expected global CSS classNames', () => { | ||
const node = findDOMNode(renderList()) | ||
expect(node.className).toEqual('VirtualScroll') | ||
}) | ||
it('should use a custom :className if specified', () => { | ||
const node = findDOMNode(renderList({ className: 'foo' })) | ||
expect(node.className).toContain('foo') | ||
}) | ||
it('should use custom :styleSheet if specified', () => { | ||
const node = findDOMNode(renderList({ | ||
styleSheet: { | ||
VirtualScroll: { color: 'red' } | ||
} | ||
})) | ||
expect(node.style.color).toEqual('red') | ||
}) | ||
it('should use overriden static styles', () => { | ||
const backup = { ...VirtualScroll.defaultStyleSheet } | ||
VirtualScroll.defaultStyleSheet.VirtualScroll = { color: 'blue' } | ||
const node = findDOMNode(renderList()) | ||
expect(node.style.color).toEqual('blue') | ||
VirtualScroll.defaultStyleSheet = backup | ||
}) | ||
}) | ||
}) |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
621821
6442
182
5
52
+ Addedinline-style-prefixer@^0.6.2
+ Addedbowser@1.9.4(transitive)
+ Addedinline-style-prefixer@0.6.7(transitive)
Updatedclassnames@^2.2.3