office-ui-fabric-react
Version:
Reusable React components for building experiences for Microsoft 365.
391 lines • 19.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var React = require("react");
var Utilities_1 = require("../../Utilities");
var ScrollablePane_types_1 = require("./ScrollablePane.types");
var getClassNames = Utilities_1.classNamesFunction();
var ScrollablePaneBase = /** @class */ (function (_super) {
tslib_1.__extends(ScrollablePaneBase, _super);
function ScrollablePaneBase(props) {
var _this = _super.call(this, props) || this;
_this._root = React.createRef();
_this._stickyAboveRef = React.createRef();
_this._stickyBelowRef = React.createRef();
_this._contentContainer = React.createRef();
_this.subscribe = function (handler) {
_this._subscribers.add(handler);
};
_this.unsubscribe = function (handler) {
_this._subscribers.delete(handler);
};
_this.addSticky = function (sticky) {
_this._stickies.add(sticky);
// If ScrollablePane is mounted, then sort sticky in correct place
if (_this.contentContainer) {
sticky.setDistanceFromTop(_this.contentContainer);
_this.sortSticky(sticky);
}
};
_this.removeSticky = function (sticky) {
_this._stickies.delete(sticky);
_this._removeStickyFromContainers(sticky);
_this.notifySubscribers();
};
_this.sortSticky = function (sticky, sortAgain) {
if (_this.stickyAbove && _this.stickyBelow) {
if (sortAgain) {
_this._removeStickyFromContainers(sticky);
}
if (sticky.canStickyTop && sticky.stickyContentTop) {
_this._addToStickyContainer(sticky, _this.stickyAbove, sticky.stickyContentTop);
}
if (sticky.canStickyBottom && sticky.stickyContentBottom) {
_this._addToStickyContainer(sticky, _this.stickyBelow, sticky.stickyContentBottom);
}
}
};
_this.updateStickyRefHeights = function () {
var stickyItems = _this._stickies;
var stickyTopHeight = 0;
var stickyBottomHeight = 0;
stickyItems.forEach(function (sticky) {
var _a = sticky.state, isStickyTop = _a.isStickyTop, isStickyBottom = _a.isStickyBottom;
if (sticky.nonStickyContent) {
if (isStickyTop) {
stickyTopHeight += sticky.nonStickyContent.offsetHeight;
}
if (isStickyBottom) {
stickyBottomHeight += sticky.nonStickyContent.offsetHeight;
}
_this._checkStickyStatus(sticky);
}
});
_this.setState({
stickyTopHeight: stickyTopHeight,
stickyBottomHeight: stickyBottomHeight,
});
};
_this.notifySubscribers = function () {
if (_this.contentContainer) {
_this._subscribers.forEach(function (handle) {
// this.stickyBelow is passed in for calculating distance to determine Sticky status
handle(_this.contentContainer, _this.stickyBelow);
});
}
};
_this.getScrollPosition = function () {
if (_this.contentContainer) {
return _this.contentContainer.scrollTop;
}
return 0;
};
_this.syncScrollSticky = function (sticky) {
if (sticky && _this.contentContainer) {
sticky.syncScroll(_this.contentContainer);
}
};
_this._getScrollablePaneContext = function () {
return {
scrollablePane: {
subscribe: _this.subscribe,
unsubscribe: _this.unsubscribe,
addSticky: _this.addSticky,
removeSticky: _this.removeSticky,
updateStickyRefHeights: _this.updateStickyRefHeights,
sortSticky: _this.sortSticky,
notifySubscribers: _this.notifySubscribers,
syncScrollSticky: _this.syncScrollSticky,
},
};
};
_this._addToStickyContainer = function (sticky, stickyContainer, stickyContentToAdd) {
// If there's no children, append child to list, otherwise, sort though array and append at correct position
if (!stickyContainer.children.length) {
stickyContainer.appendChild(stickyContentToAdd);
}
else {
// If stickyContentToAdd isn't a child element of target container, then append
if (!stickyContainer.contains(stickyContentToAdd)) {
var stickyChildrenElements_1 = [].slice.call(stickyContainer.children);
var stickyList_1 = [];
// Get stickies. Filter by canStickyTop/Bottom, then sort by distance from top, and then
// filter by elements that are in the stickyContainer already.
_this._stickies.forEach(function (stickyItem) {
if (stickyContainer === _this.stickyAbove && sticky.canStickyTop) {
stickyList_1.push(stickyItem);
}
else if (sticky.canStickyBottom) {
stickyList_1.push(stickyItem);
}
});
var stickyListSorted = stickyList_1
.sort(function (a, b) {
return (a.state.distanceFromTop || 0) - (b.state.distanceFromTop || 0);
})
.filter(function (item) {
var stickyContent = stickyContainer === _this.stickyAbove ? item.stickyContentTop : item.stickyContentBottom;
if (stickyContent) {
return stickyChildrenElements_1.indexOf(stickyContent) > -1;
}
return false;
});
// Get first element that has a distance from top that is further than our sticky that is being added
var targetStickyToAppendBefore = undefined;
for (var _i = 0, stickyListSorted_1 = stickyListSorted; _i < stickyListSorted_1.length; _i++) {
var stickyListItem = stickyListSorted_1[_i];
if ((stickyListItem.state.distanceFromTop || 0) >= (sticky.state.distanceFromTop || 0)) {
targetStickyToAppendBefore = stickyListItem;
break;
}
}
// If target element to append before is known, grab respective stickyContentTop/Bottom element
// and insert before
var targetContainer = null;
if (targetStickyToAppendBefore) {
targetContainer =
stickyContainer === _this.stickyAbove
? targetStickyToAppendBefore.stickyContentTop
: targetStickyToAppendBefore.stickyContentBottom;
}
stickyContainer.insertBefore(stickyContentToAdd, targetContainer);
}
}
};
_this._removeStickyFromContainers = function (sticky) {
if (_this.stickyAbove && sticky.stickyContentTop && _this.stickyAbove.contains(sticky.stickyContentTop)) {
_this.stickyAbove.removeChild(sticky.stickyContentTop);
}
if (_this.stickyBelow && sticky.stickyContentBottom && _this.stickyBelow.contains(sticky.stickyContentBottom)) {
_this.stickyBelow.removeChild(sticky.stickyContentBottom);
}
};
_this._onWindowResize = function () {
var scrollbarWidth = _this._getScrollbarWidth();
var scrollbarHeight = _this._getScrollbarHeight();
_this.setState({
scrollbarWidth: scrollbarWidth,
scrollbarHeight: scrollbarHeight,
});
_this.notifySubscribers();
};
_this._getStickyContainerStyle = function (height, isTop) {
return tslib_1.__assign(tslib_1.__assign({ height: height }, (Utilities_1.getRTL(_this.props.theme)
? {
right: '0',
left: (_this.state.scrollbarWidth || _this._getScrollbarWidth() || 0) + "px",
}
: {
left: '0',
right: (_this.state.scrollbarWidth || _this._getScrollbarWidth() || 0) + "px",
})), (isTop
? {
top: '0',
}
: {
bottom: (_this.state.scrollbarHeight || _this._getScrollbarHeight() || 0) + "px",
}));
};
_this._onScroll = function () {
var contentContainer = _this.contentContainer;
if (contentContainer) {
_this._stickies.forEach(function (sticky) {
sticky.syncScroll(contentContainer);
});
}
_this._notifyThrottled();
};
_this._subscribers = new Set();
_this._stickies = new Set();
Utilities_1.initializeComponentRef(_this);
_this._async = new Utilities_1.Async(_this);
_this._events = new Utilities_1.EventGroup(_this);
_this.state = {
stickyTopHeight: 0,
stickyBottomHeight: 0,
scrollbarWidth: 0,
scrollbarHeight: 0,
};
_this._notifyThrottled = _this._async.throttle(_this.notifySubscribers, 50);
return _this;
}
Object.defineProperty(ScrollablePaneBase.prototype, "root", {
get: function () {
return this._root.current;
},
enumerable: true,
configurable: true
});
Object.defineProperty(ScrollablePaneBase.prototype, "stickyAbove", {
get: function () {
return this._stickyAboveRef.current;
},
enumerable: true,
configurable: true
});
Object.defineProperty(ScrollablePaneBase.prototype, "stickyBelow", {
get: function () {
return this._stickyBelowRef.current;
},
enumerable: true,
configurable: true
});
Object.defineProperty(ScrollablePaneBase.prototype, "contentContainer", {
get: function () {
return this._contentContainer.current;
},
enumerable: true,
configurable: true
});
ScrollablePaneBase.prototype.componentDidMount = function () {
var _this = this;
var initialScrollPosition = this.props.initialScrollPosition;
this._events.on(this.contentContainer, 'scroll', this._onScroll);
this._events.on(window, 'resize', this._onWindowResize);
if (this.contentContainer && initialScrollPosition) {
this.contentContainer.scrollTop = initialScrollPosition;
}
// Set sticky distances from top property, then sort in correct order and notify subscribers
this.setStickiesDistanceFromTop();
this._stickies.forEach(function (sticky) {
_this.sortSticky(sticky);
});
this.notifySubscribers();
if ('MutationObserver' in window) {
this._mutationObserver = new MutationObserver(function (mutation) {
// Function to check if mutation is occuring in stickyAbove or stickyBelow
function checkIfMutationIsSticky(mutationRecord) {
if (this.stickyAbove !== null && this.stickyBelow !== null) {
return this.stickyAbove.contains(mutationRecord.target) || this.stickyBelow.contains(mutationRecord.target);
}
return false;
}
// Compute the scrollbar height, which might have changed if the content's width changed and caused overflow
var scrollbarHeight = _this._getScrollbarHeight();
// If the scrollbar height changed, update state so it's postioned correctly below sticky footer
if (scrollbarHeight !== _this.state.scrollbarHeight) {
_this.setState({
scrollbarHeight: scrollbarHeight,
});
}
// Notify subscribers again to re-check whether Sticky should be Sticky'd or not
_this.notifySubscribers();
// If mutation occurs in sticky header or footer, then update sticky top/bottom heights
if (mutation.some(checkIfMutationIsSticky.bind(_this))) {
_this.updateStickyRefHeights();
}
else {
// If mutation occurs in scrollable region, then find Sticky it belongs to and force update
var stickyList_2 = [];
_this._stickies.forEach(function (sticky) {
if (sticky.root && sticky.root.contains(mutation[0].target)) {
stickyList_2.push(sticky);
}
});
if (stickyList_2.length) {
stickyList_2.forEach(function (sticky) {
sticky.forceUpdate();
});
}
}
});
if (this.root) {
this._mutationObserver.observe(this.root, {
childList: true,
attributes: true,
subtree: true,
characterData: true,
});
}
}
};
ScrollablePaneBase.prototype.componentWillUnmount = function () {
this._events.dispose();
this._async.dispose();
if (this._mutationObserver) {
this._mutationObserver.disconnect();
}
};
// Only updates if props/state change, just to prevent excessive setState with updateStickyRefHeights
ScrollablePaneBase.prototype.shouldComponentUpdate = function (nextProps, nextState) {
return (this.props.children !== nextProps.children ||
this.props.initialScrollPosition !== nextProps.initialScrollPosition ||
this.props.className !== nextProps.className ||
this.state.stickyTopHeight !== nextState.stickyTopHeight ||
this.state.stickyBottomHeight !== nextState.stickyBottomHeight ||
this.state.scrollbarWidth !== nextState.scrollbarWidth ||
this.state.scrollbarHeight !== nextState.scrollbarHeight);
};
ScrollablePaneBase.prototype.componentDidUpdate = function (prevProps, prevState) {
var initialScrollPosition = this.props.initialScrollPosition;
if (this.contentContainer &&
typeof initialScrollPosition === 'number' &&
prevProps.initialScrollPosition !== initialScrollPosition) {
this.contentContainer.scrollTop = initialScrollPosition;
}
// Update subscribers when stickyTopHeight/stickyBottomHeight changes
if (prevState.stickyTopHeight !== this.state.stickyTopHeight ||
prevState.stickyBottomHeight !== this.state.stickyBottomHeight) {
this.notifySubscribers();
}
this._async.setTimeout(this._onWindowResize, 0);
};
ScrollablePaneBase.prototype.render = function () {
var _a = this.props, className = _a.className, theme = _a.theme, styles = _a.styles;
var _b = this.state, stickyTopHeight = _b.stickyTopHeight, stickyBottomHeight = _b.stickyBottomHeight;
var classNames = getClassNames(styles, {
theme: theme,
className: className,
scrollbarVisibility: this.props.scrollbarVisibility,
});
return (React.createElement("div", tslib_1.__assign({}, Utilities_1.getNativeProps(this.props, Utilities_1.divProperties), { ref: this._root, className: classNames.root }),
React.createElement("div", { ref: this._stickyAboveRef, className: classNames.stickyAbove, style: this._getStickyContainerStyle(stickyTopHeight, true) }),
React.createElement("div", { ref: this._contentContainer, className: classNames.contentContainer, "data-is-scrollable": true },
React.createElement(ScrollablePane_types_1.ScrollablePaneContext.Provider, { value: this._getScrollablePaneContext() }, this.props.children)),
React.createElement("div", { className: classNames.stickyBelow, style: this._getStickyContainerStyle(stickyBottomHeight, false) },
React.createElement("div", { ref: this._stickyBelowRef, className: classNames.stickyBelowItems }))));
};
ScrollablePaneBase.prototype.setStickiesDistanceFromTop = function () {
var _this = this;
if (this.contentContainer) {
this._stickies.forEach(function (sticky) {
sticky.setDistanceFromTop(_this.contentContainer);
});
}
};
ScrollablePaneBase.prototype.forceLayoutUpdate = function () {
this._onWindowResize();
};
ScrollablePaneBase.prototype._checkStickyStatus = function (sticky) {
if (this.stickyAbove && this.stickyBelow && this.contentContainer && sticky.nonStickyContent) {
// If sticky is sticky, then append content to appropriate container
if (sticky.state.isStickyTop || sticky.state.isStickyBottom) {
if (sticky.state.isStickyTop &&
!this.stickyAbove.contains(sticky.nonStickyContent) &&
sticky.stickyContentTop) {
sticky.addSticky(sticky.stickyContentTop);
}
if (sticky.state.isStickyBottom &&
!this.stickyBelow.contains(sticky.nonStickyContent) &&
sticky.stickyContentBottom) {
sticky.addSticky(sticky.stickyContentBottom);
}
}
else if (!this.contentContainer.contains(sticky.nonStickyContent)) {
// Reset sticky if it's not sticky and not in the contentContainer element
sticky.resetSticky();
}
}
};
ScrollablePaneBase.prototype._getScrollbarWidth = function () {
var contentContainer = this.contentContainer;
return contentContainer ? contentContainer.offsetWidth - contentContainer.clientWidth : 0;
};
ScrollablePaneBase.prototype._getScrollbarHeight = function () {
var contentContainer = this.contentContainer;
return contentContainer ? contentContainer.offsetHeight - contentContainer.clientHeight : 0;
};
return ScrollablePaneBase;
}(React.Component));
exports.ScrollablePaneBase = ScrollablePaneBase;
//# sourceMappingURL=ScrollablePane.base.js.map