react-flexbox-layout
Version:
Simple flexible layouts for IE9+
202 lines (163 loc) • 5.7 kB
JSX
import extend from 'lodash/extend';
import invokeMap from 'lodash/invokeMap';
import range from 'lodash/range';
import sum from 'lodash/sum';
import React from 'react';
import ReactDOM from 'react-dom';
import VLayoutItemIE9 from './vertical_item_ie9';
import {VLayoutPropTypes, VLayoutDefaultPropTypes, cleanProps} from './prop_types';
import {
getVGutterSizes, makeVLayoutItemChildProps,
forEachNonEmpty, mapNonEmpty, countNonEmpty,
sumSizes, addTo, getSizeCalc,
didDefineHeight, didDefineWidth
} from './util';
import {register, deregister, requestAsyncUpdate} from './update_engine_ie9';
export default function(defaultGutter, gutterMultiplier, defaultGutterUnit) {
class VLayoutIE9 extends React.Component {
componentWillMount() {
register(this);
}
componentDidMount() {
this.node = ReactDOM.findDOMNode(this);
requestAsyncUpdate();
}
componentWillUnmount() {
deregister(this);
}
componentDidUpdate() {
requestAsyncUpdate();
}
render() {
this.itemsRefs = [];
this.gutterSizes = getVGutterSizes(this.props.children, this.props.gutter);
let children = mapNonEmpty(this.props.children, (child, index) => {
return this._buildChild(child, index, this.gutterSizes);
});
return (
<div ref="wrapper" data-display-name="VLayoutWrapper"
{...cleanProps(this.props)}
style={extend(this._getLayoutWrapperStyles(), this.props.style)}
>
<div ref="container" data-display-name="VLayout"
style={this._getLayoutStyles()}
>
{children}
</div>
</div>
);
}
_buildChild(child, index, gutterSizes) {
let props = makeVLayoutItemChildProps(this.props, child.props, index, gutterSizes, gutterMultiplier);
let ref = `item_${index}`;
this.itemsRefs.push(ref);
props.ref = ref;
if (child.type && child.type._isLayoutChild) {
return React.cloneElement(child, props);
} else {
return <VLayoutItemIE9 {...props}>{child}</VLayoutItemIE9>;
}
}
_unsetLayoutStyles() {
const style = this.node.style;
if (!didDefineHeight(this.props)) {
style.height = '';
}
if (!didDefineWidth(this.props)) {
style.width = '';
}
range(countNonEmpty(this.props.children)).forEach((i) => {
this.refs[`item_${i}`]._unsetLayoutStyles();
}, this);
}
_measureInheritedStyles() {
const computedStyle = window.getComputedStyle(this.node);
this._inheritedTextAlign = computedStyle.textAlign;
}
_measureWidths() {}
_applyInheritedStyles() {
const items = this.itemsRefs.map(ref => this.refs[ref]);
invokeMap(items, '_applyInheritedStyles', this._inheritedTextAlign);
}
_applyWidths() {
if (!didDefineWidth(this.props)) {
// our element is display:table, so add width:100% to make it behave like a block element
this.node.style.width = '100%';
}
}
_measureItemHeights() {
const items = this.itemsRefs.map(ref => this.refs[ref]);
this._measuredHeights = items.map((item) => {
if (didDefineHeight(item.props) || item.props.flexGrow) {
return null;
}
return item._measureHeight();
});
}
_applyFlexHeights() {
const items = this.itemsRefs.map(ref => this.refs[ref]);
const flexGrowValues = items
.filter(item => item.props.flexGrow)
.map(item => item.props.flexGrow === true ? 1 : item.props.flexGrow);
const totalFlexGrow = sum(flexGrowValues);
// sum heights used up by elements
const usedSpace = sumSizes('height', items);
// add computed heights
const measuredHeightsAsNumbers = this._measuredHeights
.filter(i => i !== null)
.map(measurement => parseFloat(measurement.slice(0, -2)));
addTo(usedSpace, 'px', sum(measuredHeightsAsNumbers));
// add gutters
addTo(usedSpace, this.props.gutterUnit, sum(this.gutterSizes));
range(countNonEmpty(this.props.children)).forEach((i) => {
const item = this.refs[`item_${i}`];
if (item.props.flexGrow) {
return item._applyHeight(getSizeCalc(usedSpace, item.props.flexGrow, totalFlexGrow));
}
});
}
_callDidLayout() {
this.props.onLayout && this.props.onLayout();
}
_getLayoutWrapperStyles() {
let styles = {
width: this.props.width,
height: this.props.height
};
if (this._isFlexboxLayout()) {
styles.display = 'block';
} else {
// NOTE: use !important override rflGrowChildStatic className, which uses display: block !important
// to override children who use display: inline-block
styles.display = 'table !important';
styles.tableLayout = 'fixed';
}
return styles;
}
_getLayoutStyles() {
let styles = {
display: this._isFlexboxLayout() ? 'block' : 'table-cell',
height: '100%',
verticalAlign: this.props.alignItems
};
return styles;
}
// Whenever one of the children has a flexGrow the whole layout
// becomes 'flex'
_isFlexboxLayout() {
let hasFlexGrowChild = false;
forEachNonEmpty(this.props.children, (child) => {
if (child.props.flexGrow) {
hasFlexGrowChild = true;
}
});
return hasFlexGrowChild;
}
}
VLayoutIE9.propTypes = VLayoutPropTypes;
VLayoutIE9.defaultProps = extend({}, VLayoutDefaultPropTypes, {
gutter: defaultGutter,
gutterUnit: defaultGutterUnit
});
return VLayoutIE9;
}