UNPKG

office-ui-fabric-react

Version:

Reusable React components for building experiences for Office 365.

254 lines (252 loc) • 11.9 kB
"use strict"; 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