office-ui-fabric-react
Version:
Reusable React components for building experiences for Office 365.
367 lines • 18 kB
JavaScript
import * as tslib_1 from "tslib";
import * as React from 'react';
import * as PropTypes from 'prop-types';
import { BaseComponent, classNamesFunction, divProperties, getNativeProps, createRef, getRTL } from '../../Utilities';
var getClassNames = classNamesFunction();
var ScrollablePaneBase = /** @class */ (function (_super) {
tslib_1.__extends(ScrollablePaneBase, _super);
function ScrollablePaneBase(props) {
var _this = _super.call(this, props) || this;
_this._root = createRef();
_this._stickyAboveRef = createRef();
_this._stickyBelowRef = createRef();
_this._contentContainer = 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.notifySubscribers();
};
_this.removeSticky = function (sticky) {
_this._stickies.delete(sticky);
_this._removeStickyFromContainers(sticky);
_this.notifySubscribers();
};
_this.sortSticky = function (sticky) {
if (_this.stickyAbove && _this.stickyBelow) {
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._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.distanceFromTop - b.distanceFromTop;
})
.filter(function (item) {
var stickyContent = stickyContainer === _this.stickyAbove ? item.stickyContentTop : item.stickyContentBottom;
if (stickyContent) {
return stickyChildrenElements_1.indexOf(stickyContent) > -1;
}
});
// 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 in stickyListSorted) {
if (stickyListSorted[i].distanceFromTop >= sticky.distanceFromTop) {
targetStickyToAppendBefore = stickyListSorted[i];
break;
}
}
// If target element to append before is known, then 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.removeChild(sticky.stickyContentTop);
}
if (_this.stickyBelow && 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({ height: height }, (getRTL()
? {
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();
_this.state = {
stickyTopHeight: 0,
stickyBottomHeight: 0,
scrollbarWidth: undefined,
scrollbarHeight: undefined
};
_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.getChildContext = function () {
return {
scrollablePane: {
subscribe: this.subscribe,
unsubscribe: this.unsubscribe,
addSticky: this.addSticky,
removeSticky: this.removeSticky,
updateStickyRefHeights: this.updateStickyRefHeights,
sortSticky: this.sortSticky,
notifySubscribers: this.notifySubscribers
}
};
};
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;
}
// 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.off(this.contentContainer);
this._events.off(window);
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({}, getNativeProps(this.props, divProperties), { ref: this._root, className: classNames.root }),
React.createElement("div", { ref: this._contentContainer, className: classNames.contentContainer, "data-is-scrollable": true }, this.props.children),
React.createElement("div", { ref: this._stickyAboveRef, className: classNames.stickyAbove, style: this._getStickyContainerStyle(stickyTopHeight, true) }),
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 : undefined;
};
ScrollablePaneBase.prototype._getScrollbarHeight = function () {
var contentContainer = this.contentContainer;
return contentContainer ? contentContainer.offsetHeight - contentContainer.clientHeight : undefined;
};
ScrollablePaneBase.childContextTypes = {
scrollablePane: PropTypes.object
};
return ScrollablePaneBase;
}(BaseComponent));
export { ScrollablePaneBase };
//# sourceMappingURL=ScrollablePane.base.js.map