UNPKG

office-ui-fabric-react

Version:

Reusable React components for building experiences for Microsoft 365.

308 lines • 14.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 Styling_1 = require("../../Styling"); var ScrollablePane_types_1 = require("../ScrollablePane/ScrollablePane.types"); var Sticky_types_1 = require("./Sticky.types"); var Sticky = /** @class */ (function (_super) { tslib_1.__extends(Sticky, _super); function Sticky(props) { var _this = _super.call(this, props) || this; _this._root = React.createRef(); _this._stickyContentTop = React.createRef(); _this._stickyContentBottom = React.createRef(); _this._nonStickyContent = React.createRef(); _this._placeHolder = React.createRef(); _this.syncScroll = function (container) { var nonStickyContent = _this.nonStickyContent; if (nonStickyContent && _this.props.isScrollSynced) { nonStickyContent.scrollLeft = container.scrollLeft; } }; // eslint-disable-next-line deprecation/deprecation _this._getContext = function () { return _this.context; }; _this._onScrollEvent = function (container, footerStickyContainer) { if (_this.root && _this.nonStickyContent) { var distanceFromTop = _this._getNonStickyDistanceFromTop(container); var isStickyTop = false; var isStickyBottom = false; if (_this.canStickyTop) { var distanceToStickTop = distanceFromTop - _this._getStickyDistanceFromTop(); isStickyTop = distanceToStickTop < container.scrollTop; } // Can sticky bottom if the scrollablePane - total sticky footer height is smaller than the sticky's distance // from the top of the pane if (_this.canStickyBottom && container.clientHeight - footerStickyContainer.offsetHeight <= distanceFromTop) { isStickyBottom = distanceFromTop - Math.floor(container.scrollTop) >= _this._getStickyDistanceFromTopForFooter(container, footerStickyContainer); } if (document.activeElement && _this.nonStickyContent.contains(document.activeElement) && (_this.state.isStickyTop !== isStickyTop || _this.state.isStickyBottom !== isStickyBottom)) { _this._activeElement = document.activeElement; } else { _this._activeElement = undefined; } _this.setState({ isStickyTop: _this.canStickyTop && isStickyTop, isStickyBottom: isStickyBottom, distanceFromTop: distanceFromTop, }); } }; _this._getStickyDistanceFromTop = function () { var distance = 0; if (_this.stickyContentTop) { distance = _this.stickyContentTop.offsetTop; } return distance; }; _this._getStickyDistanceFromTopForFooter = function (container, footerStickyVisibleContainer) { var distance = 0; if (_this.stickyContentBottom) { distance = container.clientHeight - footerStickyVisibleContainer.offsetHeight + _this.stickyContentBottom.offsetTop; } return distance; }; _this._getNonStickyDistanceFromTop = function (container) { var distance = 0; var currElem = _this.root; if (currElem) { while (currElem && currElem.offsetParent !== container) { distance += currElem.offsetTop; currElem = currElem.offsetParent; } if (currElem && currElem.offsetParent === container) { distance += currElem.offsetTop; } } return distance; }; Utilities_1.initializeComponentRef(_this); _this.state = { isStickyTop: false, isStickyBottom: false, distanceFromTop: undefined, }; _this._activeElement = undefined; return _this; } Object.defineProperty(Sticky.prototype, "root", { get: function () { return this._root.current; }, enumerable: true, configurable: true }); Object.defineProperty(Sticky.prototype, "placeholder", { get: function () { return this._placeHolder.current; }, enumerable: true, configurable: true }); Object.defineProperty(Sticky.prototype, "stickyContentTop", { get: function () { return this._stickyContentTop.current; }, enumerable: true, configurable: true }); Object.defineProperty(Sticky.prototype, "stickyContentBottom", { get: function () { return this._stickyContentBottom.current; }, enumerable: true, configurable: true }); Object.defineProperty(Sticky.prototype, "nonStickyContent", { get: function () { return this._nonStickyContent.current; }, enumerable: true, configurable: true }); Object.defineProperty(Sticky.prototype, "canStickyTop", { get: function () { return (this.props.stickyPosition === Sticky_types_1.StickyPositionType.Both || this.props.stickyPosition === Sticky_types_1.StickyPositionType.Header); }, enumerable: true, configurable: true }); Object.defineProperty(Sticky.prototype, "canStickyBottom", { get: function () { return (this.props.stickyPosition === Sticky_types_1.StickyPositionType.Both || this.props.stickyPosition === Sticky_types_1.StickyPositionType.Footer); }, enumerable: true, configurable: true }); Sticky.prototype.componentDidMount = function () { var scrollablePane = this._getContext().scrollablePane; if (!scrollablePane) { return; } scrollablePane.subscribe(this._onScrollEvent); scrollablePane.addSticky(this); }; Sticky.prototype.componentWillUnmount = function () { var scrollablePane = this._getContext().scrollablePane; if (!scrollablePane) { return; } scrollablePane.unsubscribe(this._onScrollEvent); scrollablePane.removeSticky(this); }; Sticky.prototype.componentDidUpdate = function (prevProps, prevState) { var scrollablePane = this._getContext().scrollablePane; if (!scrollablePane) { return; } var _a = this.state, isStickyBottom = _a.isStickyBottom, isStickyTop = _a.isStickyTop, distanceFromTop = _a.distanceFromTop; var syncScroll = false; if (prevState.distanceFromTop !== distanceFromTop) { scrollablePane.sortSticky(this, true /*sortAgain*/); syncScroll = true; } if (prevState.isStickyTop !== isStickyTop || prevState.isStickyBottom !== isStickyBottom) { if (this._activeElement) { this._activeElement.focus(); } scrollablePane.updateStickyRefHeights(); syncScroll = true; } if (syncScroll) { // Sync Sticky scroll position with content container on each update scrollablePane.syncScrollSticky(this); } }; Sticky.prototype.shouldComponentUpdate = function (nextProps, nextState) { // eslint-disable-next-line deprecation/deprecation if (!this.context.scrollablePane) { return true; } var _a = this.state, isStickyTop = _a.isStickyTop, isStickyBottom = _a.isStickyBottom, distanceFromTop = _a.distanceFromTop; return (isStickyTop !== nextState.isStickyTop || isStickyBottom !== nextState.isStickyBottom || this.props.stickyPosition !== nextProps.stickyPosition || this.props.children !== nextProps.children || distanceFromTop !== nextState.distanceFromTop || _isOffsetHeightDifferent(this._nonStickyContent, this._stickyContentTop) || _isOffsetHeightDifferent(this._nonStickyContent, this._stickyContentBottom) || _isOffsetHeightDifferent(this._nonStickyContent, this._placeHolder)); }; Sticky.prototype.render = function () { var _a = this.state, isStickyTop = _a.isStickyTop, isStickyBottom = _a.isStickyBottom; var _b = this.props, stickyClassName = _b.stickyClassName, children = _b.children; // eslint-disable-next-line deprecation/deprecation if (!this.context.scrollablePane) { return React.createElement("div", null, this.props.children); } return (React.createElement("div", { ref: this._root }, this.canStickyTop && (React.createElement("div", { ref: this._stickyContentTop, style: { pointerEvents: isStickyTop ? 'auto' : 'none' } }, React.createElement("div", { style: this._getStickyPlaceholderHeight(isStickyTop) }))), this.canStickyBottom && (React.createElement("div", { ref: this._stickyContentBottom, style: { pointerEvents: isStickyBottom ? 'auto' : 'none' } }, React.createElement("div", { style: this._getStickyPlaceholderHeight(isStickyBottom) }))), React.createElement("div", { style: this._getNonStickyPlaceholderHeightAndWidth(), ref: this._placeHolder }, (isStickyTop || isStickyBottom) && React.createElement("span", { style: Styling_1.hiddenContentStyle }, children), React.createElement("div", { ref: this._nonStickyContent, className: isStickyTop || isStickyBottom ? stickyClassName : undefined, style: this._getContentStyles(isStickyTop || isStickyBottom) }, children)))); }; Sticky.prototype.addSticky = function (stickyContent) { if (this.nonStickyContent) { stickyContent.appendChild(this.nonStickyContent); } }; Sticky.prototype.resetSticky = function () { if (this.nonStickyContent && this.placeholder) { this.placeholder.appendChild(this.nonStickyContent); } }; Sticky.prototype.setDistanceFromTop = function (container) { var distanceFromTop = this._getNonStickyDistanceFromTop(container); this.setState({ distanceFromTop: distanceFromTop }); }; Sticky.prototype._getContentStyles = function (isSticky) { return { backgroundColor: this.props.stickyBackgroundColor || this._getBackground(), overflow: isSticky ? 'hidden' : '', }; }; Sticky.prototype._getStickyPlaceholderHeight = function (isSticky) { var height = this.nonStickyContent ? this.nonStickyContent.offsetHeight : 0; return { visibility: isSticky ? 'hidden' : 'visible', height: isSticky ? 0 : height, }; }; Sticky.prototype._getNonStickyPlaceholderHeightAndWidth = function () { var _a = this.state, isStickyTop = _a.isStickyTop, isStickyBottom = _a.isStickyBottom; if (isStickyTop || isStickyBottom) { var height = 0; var width = 0; // Why is placeholder width needed? // ScrollablePane's content container is reponsible for providing scrollbars depending on content overflow. // - If the overflow is caused by content of sticky component when it is in non-sticky state, the container will // provide horizontal scrollbar. // - If the component becomes sticky, i.e., when state.isStickyTop || state.isStickyBottom becomes true, // its actual content is no longer inside the container, so the container will see no need for horizontal // scrollbar (assuming no other content is causing overflow). The complete content of sticky component will // not be viewable. So it is necessary to provide a placeholder of a certain width (height is already being set) // in the container, to get a horizontal scrollbar & be able to view the complete content of sticky component. if (this.nonStickyContent && this.nonStickyContent.firstElementChild) { height = this.nonStickyContent.offsetHeight; // What value should be substituted for placeholder width? // Assumptions: // 1. Content inside <Sticky> should always be wrapped in a single div. // <Sticky><div id={'firstElementChild'}>{intended_content}</div><Sticky/> // 2. -ve padding, margin, etc. are not be used. // 3. scrollWidth of a parent is greater than or equal to max of scrollWidths of its children, and same holds // for children. // placeholder width should be computed in the best possible way to prevent overscroll/underscroll. width = this.nonStickyContent.firstElementChild.scrollWidth + (this.nonStickyContent.firstElementChild.offsetWidth - this.nonStickyContent.firstElementChild.clientWidth); } return { height: height, width: width, }; } else { return {}; } }; // Gets background of nearest parent element that has a declared background-color attribute Sticky.prototype._getBackground = function () { if (!this.root) { return undefined; } var curr = this.root; while (window.getComputedStyle(curr).getPropertyValue('background-color') === 'rgba(0, 0, 0, 0)' || window.getComputedStyle(curr).getPropertyValue('background-color') === 'transparent') { if (curr.tagName === 'HTML') { // Fallback color if no element has a declared background-color attribute return undefined; } if (curr.parentElement) { curr = curr.parentElement; } } return window.getComputedStyle(curr).getPropertyValue('background-color'); }; Sticky.defaultProps = { stickyPosition: Sticky_types_1.StickyPositionType.Both, isScrollSynced: true, }; Sticky.contextType = ScrollablePane_types_1.ScrollablePaneContext; return Sticky; }(React.Component)); exports.Sticky = Sticky; function _isOffsetHeightDifferent(a, b) { return (a && b && a.current && b.current && a.current.offsetHeight !== b.current.offsetHeight); } //# sourceMappingURL=Sticky.js.map