-
Notifications
You must be signed in to change notification settings - Fork 3k
Expand file tree
/
Copy pathAutoSizer.js
More file actions
200 lines (165 loc) · 5.53 KB
/
Copy pathAutoSizer.js
File metadata and controls
200 lines (165 loc) · 5.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
/** @flow */
import * as React from 'react';
import createDetectElementResize from '../vendor/detectElementResize';
type Size = {
height: number,
width: number,
};
type Props = {
/** Function responsible for rendering children.*/
children: Size => React.Element<*>,
/** Optional custom CSS class name to attach to root AutoSizer element. */
className?: string,
/** Default height to use for initial render; useful for SSR */
defaultHeight?: number,
/** Default width to use for initial render; useful for SSR */
defaultWidth?: number,
/** Disable dynamic :height property */
disableHeight: boolean,
/** Disable dynamic :width property */
disableWidth: boolean,
/** Nonce of the inlined stylesheet for Content Security Policy */
nonce?: string,
/** Callback to be invoked on-resize */
onResize: Size => void,
/** Optional inline style */
style: ?Object,
};
type State = {
height: number,
width: number,
};
type ResizeHandler = (element: HTMLElement, onResize: () => void) => void;
type DetectElementResize = {
addResizeListener: ResizeHandler,
removeResizeListener: ResizeHandler,
};
export default class AutoSizer extends React.Component<Props, State> {
static defaultProps = {
onResize: () => {},
disableHeight: false,
disableWidth: false,
style: {},
};
state = {
height: this.props.defaultHeight || 0,
width: this.props.defaultWidth || 0,
};
_parentNode: ?HTMLElement;
_autoSizer: ?HTMLElement;
_window: ?any; // uses any instead of Window because Flow doesn't have window type
_detectElementResize: DetectElementResize;
componentDidMount() {
const {nonce} = this.props;
if (
this._autoSizer &&
this._autoSizer.parentNode &&
this._autoSizer.parentNode.ownerDocument &&
this._autoSizer.parentNode.ownerDocument.defaultView &&
this._autoSizer.parentNode instanceof
this._autoSizer.parentNode.ownerDocument.defaultView.HTMLElement
) {
// Delay access of parentNode until mount.
// This handles edge-cases where the component has already been unmounted before its ref has been set,
// As well as libraries like react-lite which have a slightly different lifecycle.
this._parentNode = this._autoSizer.parentNode;
this._window = this._autoSizer.parentNode.ownerDocument.defaultView;
// Defer requiring resize handler in order to support server-side rendering.
// See issue #41
this._detectElementResize = createDetectElementResize(
nonce,
this._window,
);
this._detectElementResize.addResizeListener(
this._parentNode,
this._onResize,
);
this._onResize();
}
}
componentWillUnmount() {
if (this._detectElementResize && this._parentNode) {
this._detectElementResize.removeResizeListener(
this._parentNode,
this._onResize,
);
}
}
render() {
const {
children,
className,
disableHeight,
disableWidth,
style,
} = this.props;
const {height, width} = this.state;
// Outer div should not force width/height since that may prevent containers from shrinking.
// Inner component should overflow and use calculated width/height.
// See issue #68 for more information.
const outerStyle: Object = {overflow: 'visible'};
const childParams: Object = {};
if (!disableHeight) {
outerStyle.height = 0;
childParams.height = height;
}
if (!disableWidth) {
outerStyle.width = 0;
childParams.width = width;
}
/**
* TODO: Avoid rendering children before the initial measurements have been collected.
* At best this would just be wasting cycles.
* Add this check into version 10 though as it could break too many ref callbacks in version 9.
* Note that if default width/height props were provided this would still work with SSR.
if (
height !== 0 &&
width !== 0
) {
child = children({ height, width })
}
*/
return (
<div
className={className}
ref={this._setRef}
style={{
...outerStyle,
...style,
}}>
{children(childParams)}
</div>
);
}
_onResize = () => {
const {disableHeight, disableWidth, onResize} = 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;
const win = this._window || window;
const style = win.getComputedStyle(this._parentNode) || {};
const paddingLeft = parseInt(style.paddingLeft, 10) || 0;
const paddingRight = parseInt(style.paddingRight, 10) || 0;
const paddingTop = parseInt(style.paddingTop, 10) || 0;
const paddingBottom = parseInt(style.paddingBottom, 10) || 0;
const newHeight = height - paddingTop - paddingBottom;
const newWidth = width - paddingLeft - paddingRight;
if (
(!disableHeight && this.state.height !== newHeight) ||
(!disableWidth && this.state.width !== newWidth)
) {
this.setState({
height: height - paddingTop - paddingBottom,
width: width - paddingLeft - paddingRight,
});
onResize({height, width});
}
}
};
_setRef = (autoSizer: ?HTMLElement) => {
this._autoSizer = autoSizer;
};
}