Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 12 additions & 11 deletions docs/AutoSizer.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@ High-order component that automatically adjusts the width and height of a single

### Prop Types

| Property | Type | Required? | Description |
| :------------ | :------- | :-------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| children | Function | ✓ | Function responsible for rendering children. This function should implement the following signature: `({ height: number, width: number }) => PropTypes.element` |
| className | String | | Optional custom CSS class name to attach to root `AutoSizer` element. This is an advanced property and is not typically necessary. |
| defaultHeight | Number | | Height passed to child for initial render; useful for server-side rendering. This value will be overridden with an accurate height after mounting. |
| defaultWidth | Number | | Width passed to child for initial render; useful for server-side rendering. This value will be overridden with an accurate width after mounting. |
| disableHeight | Boolean | | Fixed `height`; if specified, the child's `height` property will not be managed |
| disableWidth | Boolean | | Fixed `width`; if specified, the child's `width` property will not be managed |
| nonce | String | | Nonce of the inlined stylesheets for [Content Security Policy](https://www.w3.org/TR/2016/REC-CSP2-20161215/#script-src-the-nonce-attribute) |
| onResize | Function | | Callback to be invoked on-resize; it is passed the following named parameters: `({ height: number, width: number })`. |
| style | Object | | Optional custom inline style to attach to root `AutoSizer` element. This is an advanced property and is not typically necessary. |
| Property | Type | Required? | Description |
| :------------ | :------- | :-------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| children | Function | ✓ | Function responsible for rendering children. This function should implement the following signature: `({ height: number, width: number }) => PropTypes.element` |
| className | String | | Optional custom CSS class name to attach to root `AutoSizer` element. This is an advanced property and is not typically necessary. |
| defaultHeight | Number | | Height passed to child for initial render; useful for server-side rendering. This value will be overridden with an accurate height after mounting. |
| defaultWidth | Number | | Width passed to child for initial render; useful for server-side rendering. This value will be overridden with an accurate width after mounting. |
| disableHeight | Boolean | | Fixed `height`; if specified, the child's `height` property will not be managed |
| disableWidth | Boolean | | Fixed `width`; if specified, the child's `width` property will not be managed |
| nonce | String | | Nonce of the inlined stylesheets for [Content Security Policy](https://www.w3.org/TR/2016/REC-CSP2-20161215/#script-src-the-nonce-attribute) |
| onResize | Function | | Callback to be invoked on-resize; it is passed the following named parameters: `({ height: number, width: number })`. |
| roundingMode | String | | How to handle fractional (sub-pixel) measurements: `"ceil"` (default, rounds up), `"floor"` (rounds down so children never overflow the parent), or `"round"` (nearest integer). |
| style | Object | | Optional custom inline style to attach to root `AutoSizer` element. This is an advanced property and is not typically necessary. |

### Examples

Expand Down
30 changes: 28 additions & 2 deletions source/AutoSizer/AutoSizer.jest.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ describe('AutoSizer', () => {
paddingLeft = 0,
paddingRight = 0,
paddingTop = 0,
roundingMode = undefined,
style = undefined,
width = 200,
} = {}) {
Expand All @@ -49,6 +50,7 @@ describe('AutoSizer', () => {
disableHeight={disableHeight}
disableWidth={disableWidth}
onResize={onResize}
roundingMode={roundingMode}
style={style}>
{({height, width}) => (
<ChildComponent
Expand All @@ -63,8 +65,8 @@ describe('AutoSizer', () => {
);
}

// AutoSizer uses offsetWidth and offsetHeight.
// Jest runs in JSDom which doesn't support measurements APIs.
// AutoSizer reads getBoundingClientRect; detectElementResize (resize
// simulation below) reads offsetWidth/offsetHeight. Both need mocking in JSDom.
function mockOffsetSize(width, height) {
Object.defineProperty(HTMLElement.prototype, 'offsetHeight', {
configurable: true,
Expand All @@ -74,6 +76,10 @@ describe('AutoSizer', () => {
configurable: true,
value: width,
});
HTMLElement.prototype.getBoundingClientRect = jest.fn(() => ({
height,
width,
}));
}

it('should relay properties to ChildComponent or React child', () => {
Expand All @@ -88,6 +94,26 @@ describe('AutoSizer', () => {
expect(rendered.textContent).toContain('width:200');
});

describe('roundingMode', () => {
it.each`
roundingMode | height | width | expectedHeight | expectedWidth | scenario
${undefined} | ${100.4} | ${199.1} | ${101} | ${200} | ${'round fractional measurements up by default'}
${'ceil'} | ${100.4} | ${199.1} | ${101} | ${200} | ${'round up when roundingMode is "ceil"'}
${'floor'} | ${100.6} | ${199.6} | ${100} | ${199} | ${'round down when roundingMode is "floor"'}
${'round'} | ${100.4} | ${199.6} | ${100} | ${200} | ${'round to the nearest integer when roundingMode is "round"'}
${'invalid'} | ${100.4} | ${199.1} | ${101} | ${200} | ${'fall back to rounding up when roundingMode is not a recognized value'}
`(
'should $scenario',
({roundingMode, height, width, expectedHeight, expectedWidth}) => {
const rendered = findDOMNode(
render(getMarkup({height, width, roundingMode})),
);
expect(rendered.textContent).toContain(`height:${expectedHeight}`);
expect(rendered.textContent).toContain(`width:${expectedWidth}`);
},
);
});

it('should account for padding when calculating the available width and height', () => {
const rendered = findDOMNode(
render(
Expand Down
22 changes: 19 additions & 3 deletions source/AutoSizer/AutoSizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ type Size = {
width: number,
};

type RoundingMode = 'ceil' | 'floor' | 'round';

const ROUNDING_FNS: {[RoundingMode]: (number) => number} = {
ceil: Math.ceil,
floor: Math.floor,
round: Math.round,
};

type Props = {
/** Function responsible for rendering children.*/
children: Size => React.Element<*>,
Expand All @@ -33,6 +41,9 @@ type Props = {
/** Callback to be invoked on-resize */
onResize: Size => void,

/** How to round fractional pixel measurements: 'ceil' (default), 'floor', or 'round' */
roundingMode: RoundingMode,

/** Optional inline style */
style: ?Object,
};
Expand All @@ -54,6 +65,7 @@ export default class AutoSizer extends React.Component<Props, State> {
onResize: () => {},
disableHeight: false,
disableWidth: false,
roundingMode: 'ceil',
style: {},
};

Expand Down Expand Up @@ -160,15 +172,19 @@ export default class AutoSizer extends React.Component<Props, State> {
}

_onResize = () => {
const {disableHeight, disableWidth, onResize} = this.props;
const {disableHeight, disableWidth, onResize, roundingMode} = this.props;

if (this._parentNode) {
// Guard against AutoSizer component being removed from the DOM immediately after being added.
// This can result in invalid style values which can result in NaN values if we don't handle them.
// See issue #150 for more context.

const height = this._parentNode.offsetHeight || 0;
const width = this._parentNode.offsetWidth || 0;
// getBoundingClientRect returns fractional pixel measurements;
// roundingMode controls how they're rounded to whole pixels.
const round = ROUNDING_FNS[roundingMode] || Math.ceil;
const boundingRect = this._parentNode.getBoundingClientRect();
const height = round(boundingRect.height) || 0;
const width = round(boundingRect.width) || 0;

const win = this._window || window;
const style = win.getComputedStyle(this._parentNode) || {};
Expand Down