office-ui-fabric-react
Version:
Reusable React components for building experiences for Office 365.
254 lines (252 loc) • 11.9 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var React = require("react");
var Utilities_1 = require("../../Utilities");
var styles = require("./ResizeGroup.scss");
var RESIZE_DELAY = 16;
/**
* Returns a simple object is able to store measurements with a given key.
*/
exports.getMeasurementCache = function () {
var measurementsCache = {};
return {
/**
* Checks if the provided data has a cacheKey. If it has a cacheKey and there is a
* corresponding entry in the measurementsCache, then it will return that value.
* Returns undefined otherwise.
*/
getCachedMeasurement: function (data) {
if (data && data.cacheKey && measurementsCache.hasOwnProperty(data.cacheKey)) {
return measurementsCache[data.cacheKey];
}
return undefined;
},
/**
* Should be called whenever there is a new measurement associated with a given data object.
* If the data has a cacheKey, store that measurement in the measurementsCache.
*/
addMeasurementToCache: function (data, measurement) {
if (data.cacheKey) {
measurementsCache[data.cacheKey] = measurement;
}
}
};
};
/**
* Returns a function that is able to compute the next state for the ResizeGroup given the current
* state and any measurement updates.
*/
exports.getNextResizeGroupStateProvider = function (measurementCache) {
if (measurementCache === void 0) { measurementCache = exports.getMeasurementCache(); }
var _measurementCache = measurementCache;
var _containerWidth;
/**
* Gets the width of the data rendered in a hidden div.
* @param measuredData - The data corresponding to the measurement we wish to take.
* @param getElementToMeasureWidth - A function that returns the measurement of the rendered data. Only called when the measurement
* is not in the cache.
*/
function _getMeasuredWidth(measuredData, getElementToMeasureWidth) {
var cachedWidth = _measurementCache.getCachedMeasurement(measuredData);
if (cachedWidth !== undefined) {
return cachedWidth;
}
var measuredWidth = getElementToMeasureWidth();
_measurementCache.addMeasurementToCache(measuredData, measuredWidth);
return measuredWidth;
}
/**
* Will get the next IResizeGroupState based on the current data while trying to shrink contents
* to fit in the container.
* @param data - The initial data point to start measuring.
* @param onReduceData - Function that transforms the data into something that should render with less width.
* @param getElementToMeasureWidth - A function that returns the measurement of the rendered data. Only called when the measurement
* is not in the cache.
*/
function _shrinkContentsUntilTheyFit(data, onReduceData, getElementToMeasureWidth) {
var dataToMeasure = data;
var measuredWidth = _getMeasuredWidth(data, getElementToMeasureWidth);
while (measuredWidth > _containerWidth) {
var nextMeasuredData = onReduceData(dataToMeasure);
// We don't want to get stuck in an infinite render loop when there are no more
// scaling steps, so implementations of onReduceData should return undefined when
// there are no more scaling states to apply.
if (nextMeasuredData === undefined) {
return {
renderedData: dataToMeasure,
resizeDirection: undefined,
dataToMeasure: undefined
};
}
measuredWidth = _measurementCache.getCachedMeasurement(nextMeasuredData);
// If the measurement isn't in the cache, we need to rerender with some data in a hidden div
if (measuredWidth === undefined) {
return {
dataToMeasure: nextMeasuredData,
resizeDirection: 'shrink'
};
}
dataToMeasure = nextMeasuredData;
}
return {
renderedData: dataToMeasure,
resizeDirection: undefined,
dataToMeasure: undefined
};
}
/**
* This function should be called when the state changes in a manner that might allow for more content to fit
* on the screen, such as the window width growing.
* @param data - The initial data point to start measuring.
* @param onGrowData - Function that transforms the data into something that may take up more space when rendering.
* @param getElementToMeasureWidth - A function that returns the measurement of the rendered data. Only called when the measurement
* is not in the cache.
*/
function _growDataUntilItDoesNotFit(data, onGrowData, getElementToMeasureWidth) {
var dataToMeasure = data;
var measuredWidth = _getMeasuredWidth(data, getElementToMeasureWidth);
while (measuredWidth < _containerWidth) {
var nextMeasuredData = onGrowData(dataToMeasure);
// We don't want to get stuck in an infinite render loop when there are no more
// scaling steps, so implementations of onGrowData should return undefined when
// there are no more scaling states to apply.
if (nextMeasuredData === undefined) {
return {
renderedData: dataToMeasure,
resizeDirection: undefined,
dataToMeasure: undefined
};
}
measuredWidth = _measurementCache.getCachedMeasurement(nextMeasuredData);
// If the measurement isn't in the cache, we need to rerender with some data in a hidden div
if (measuredWidth === undefined) {
return {
dataToMeasure: nextMeasuredData
};
}
dataToMeasure = nextMeasuredData;
}
// Once the loop is done, we should now shrink until the contents fit.
return {
dataToMeasure: dataToMeasure,
resizeDirection: 'shrink'
};
}
/**
* Handles an update to the container width. Should only be called when we knew the previous container width.
* @param newWidth - The new width of the container.
* @param fullWidthData - The initial data passed in as a prop to resizeGroup.
* @param renderedData - The data that was rendered prior to the container size changing.
* @param onGrowData - Set to true if the Resize group has an onGrowData function.
*/
function _updateContainerWidth(newWidth, fullWidthData, renderedData, onGrowData) {
var nextState;
if (newWidth > _containerWidth) {
if (onGrowData) {
nextState = {
resizeDirection: 'grow',
dataToMeasure: onGrowData(renderedData)
};
}
else {
nextState = {
resizeDirection: 'shrink',
dataToMeasure: fullWidthData
};
}
}
else {
nextState = {
resizeDirection: 'shrink',
dataToMeasure: renderedData
};
}
_containerWidth = newWidth;
return tslib_1.__assign({}, nextState, { measureContainer: false });
}
return function (props, currentState, getElementToMeasureWidth, newContainerWidth) {
// If there is no new container width or data to measure, there is no need for a new state update
if (newContainerWidth === undefined && currentState.dataToMeasure === undefined) {
return undefined;
}
if (newContainerWidth) {
// If we know what the last container size was and we rendered data at that width, we can do an optimized render
if (_containerWidth && currentState.renderedData && !currentState.dataToMeasure) {
return tslib_1.__assign({}, currentState, _updateContainerWidth(newContainerWidth, props.data, currentState.renderedData, props.onGrowData));
}
// If we are just setting the container width for the first time, we can't do any optimizations
_containerWidth = newContainerWidth;
}
var nextState = tslib_1.__assign({}, currentState, { measureContainer: false });
if (currentState.dataToMeasure) {
if (currentState.resizeDirection === 'grow' && props.onGrowData) {
nextState = tslib_1.__assign({}, nextState, _growDataUntilItDoesNotFit(currentState.dataToMeasure, props.onGrowData, getElementToMeasureWidth));
}
else {
nextState = tslib_1.__assign({}, nextState, _shrinkContentsUntilTheyFit(currentState.dataToMeasure, props.onReduceData, getElementToMeasureWidth));
}
}
return nextState;
};
};
var ResizeGroup = (function (_super) {
tslib_1.__extends(ResizeGroup, _super);
function ResizeGroup(props) {
var _this = _super.call(this, props) || this;
_this._getNextResizeGroupState = exports.getNextResizeGroupStateProvider();
_this.state = {
dataToMeasure: tslib_1.__assign({}, _this.props.data),
resizeDirection: 'grow',
measureContainer: true,
};
return _this;
}
ResizeGroup.prototype.render = function () {
var _a = this.props, onRenderData = _a.onRenderData, data = _a.data;
var _b = this.state, dataToMeasure = _b.dataToMeasure, renderedData = _b.renderedData;
return (React.createElement("div", { className: Utilities_1.css('ms-ResizeGroup'), ref: this._resolveRef('_root') },
dataToMeasure && (React.createElement("div", { className: Utilities_1.css(styles.measured), ref: this._resolveRef('_measured') }, onRenderData(dataToMeasure))),
renderedData && onRenderData(renderedData)));
};
ResizeGroup.prototype.componentDidMount = function () {
this._afterComponentRendered();
this._events.on(window, 'resize', this._async.debounce(this._onResize, RESIZE_DELAY, { leading: true }));
};
ResizeGroup.prototype.componentWillReceiveProps = function (nextProps) {
this.setState({
dataToMeasure: tslib_1.__assign({}, nextProps.data),
resizeDirection: 'grow',
measureContainer: true // Receiving new props means the parent might rerender and the root width might change
});
};
ResizeGroup.prototype.componentDidUpdate = function (prevProps) {
if (this.state.renderedData) {
if (this.props.dataDidRender) {
this.props.dataDidRender(this.state.renderedData);
}
}
this._afterComponentRendered();
};
ResizeGroup.prototype._afterComponentRendered = function () {
var _this = this;
this._async.requestAnimationFrame(function () {
var containerWidth = undefined;
if (_this.state.measureContainer) {
containerWidth = _this._root.getBoundingClientRect().width;
}
var nextState = _this._getNextResizeGroupState(_this.props, _this.state, function () { return _this._measured.scrollWidth; }, containerWidth);
if (nextState) {
_this.setState(nextState);
}
});
};
ResizeGroup.prototype._onResize = function () {
if (this._root) {
this.setState({ measureContainer: true });
}
};
return ResizeGroup;
}(Utilities_1.BaseComponent));
exports.ResizeGroup = ResizeGroup;
//# sourceMappingURL=ResizeGroup.js.map
;