UNPKG

matrix-react-sdk

Version:
899 lines (735 loc) 108 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _react = _interopRequireWildcard(require("react")); var _propTypes = _interopRequireDefault(require("prop-types")); var _Timer = _interopRequireDefault(require("../../utils/Timer")); var _AutoHideScrollbar = _interopRequireDefault(require("./AutoHideScrollbar")); var _replaceableComponent = require("../../utils/replaceableComponent"); var _KeyBindingsManager = require("../../KeyBindingsManager"); var _dec, _class, _class2, _temp; const DEBUG_SCROLL = false; // The amount of extra scroll distance to allow prior to unfilling. // See _getExcessHeight. const UNPAGINATION_PADDING = 6000; // The number of milliseconds to debounce calls to onUnfillRequest, to prevent // many scroll events causing many unfilling requests. const UNFILL_REQUEST_DEBOUNCE_MS = 200; // _updateHeight makes the height a ceiled multiple of this so we // don't have to update the height too often. It also allows the user // to scroll past the pagination spinner a bit so they don't feel blocked so // much while the content loads. const PAGE_SIZE = 400; let debuglog; if (DEBUG_SCROLL) { // using bind means that we get to keep useful line numbers in the console debuglog = console.log.bind(console, "ScrollPanel debuglog:"); } else { debuglog = function () {}; } /* This component implements an intelligent scrolling list. * * It wraps a list of <li> children; when items are added to the start or end * of the list, the scroll position is updated so that the user still sees the * same position in the list. * * It also provides a hook which allows parents to provide more list elements * when we get close to the start or end of the list. * * Each child element should have a 'data-scroll-tokens'. This string of * comma-separated tokens may contain a single token or many, where many indicates * that the element contains elements that have scroll tokens themselves. The first * token in 'data-scroll-tokens' is used to serialise the scroll state, and returned * as the 'trackedScrollToken' attribute by getScrollState(). * * IMPORTANT: INDIVIDUAL TOKENS WITHIN 'data-scroll-tokens' MUST NOT CONTAIN COMMAS. * * Some notes about the implementation: * * The saved 'scrollState' can exist in one of two states: * * - stuckAtBottom: (the default, and restored by resetScrollState): the * viewport is scrolled down as far as it can be. When the children are * updated, the scroll position will be updated to ensure it is still at * the bottom. * * - fixed, in which the viewport is conceptually tied at a specific scroll * offset. We don't save the absolute scroll offset, because that would be * affected by window width, zoom level, amount of scrollback, etc. Instead * we save an identifier for the last fully-visible message, and the number * of pixels the window was scrolled below it - which is hopefully near * enough. * * The 'stickyBottom' property controls the behaviour when we reach the bottom * of the window (either through a user-initiated scroll, or by calling * scrollToBottom). If stickyBottom is enabled, the scrollState will enter * 'stuckAtBottom' state - ensuring that new additions cause the window to * scroll down further. If stickyBottom is disabled, we just save the scroll * offset as normal. */ let ScrollPanel = (_dec = (0, _replaceableComponent.replaceableComponent)("structures.ScrollPanel"), _dec(_class = (_temp = _class2 = class ScrollPanel extends _react.default.Component { constructor(props) { super(props); (0, _defineProperty2.default)(this, "onScroll", ev => { // skip scroll events caused by resizing if (this.props.resizeNotifier && this.props.resizeNotifier.isResizing) return; debuglog("onScroll", this._getScrollNode().scrollTop); this._scrollTimeout.restart(); this._saveScrollState(); this.updatePreventShrinking(); this.props.onScroll(ev); this.checkFillState(); }); (0, _defineProperty2.default)(this, "onResize", () => { debuglog("onResize"); this.checkScroll(); // update preventShrinkingState if present if (this.preventShrinkingState) { this.preventShrinking(); } }); (0, _defineProperty2.default)(this, "checkScroll", () => { if (this.unmounted) { return; } this._restoreSavedScrollState(); this.checkFillState(); }); (0, _defineProperty2.default)(this, "isAtBottom", () => { const sn = this._getScrollNode(); // fractional values (both too big and too small) // for scrollTop happen on certain browsers/platforms // when scrolled all the way down. E.g. Chrome 72 on debian. // so check difference <= 1; return Math.abs(sn.scrollHeight - (sn.scrollTop + sn.clientHeight)) <= 1; }); (0, _defineProperty2.default)(this, "checkFillState", async (depth = 0) => { if (this.unmounted) { return; } const isFirstCall = depth === 0; const sn = this._getScrollNode(); // if there is less than a screenful of messages above or below the // viewport, try to get some more messages. // // scrollTop is the number of pixels between the top of the content and // the top of the viewport. // // scrollHeight is the total height of the content. // // clientHeight is the height of the viewport (excluding borders, // margins, and scrollbars). // // // .---------. - - // | | | scrollTop | // .-+---------+-. - - | // | | | | | | // | | | | | clientHeight | scrollHeight // | | | | | | // `-+---------+-' - | // | | | // | | | // `---------' - // // as filling is async and recursive, // don't allow more than 1 chain of calls concurrently // do make a note when a new request comes in while already running one, // so we can trigger a new chain of calls once done. if (isFirstCall) { if (this._isFilling) { debuglog("_isFilling: not entering while request is ongoing, marking for a subsequent request"); this._fillRequestWhileRunning = true; return; } debuglog("_isFilling: setting"); this._isFilling = true; } const itemlist = this._itemlist.current; const firstTile = itemlist && itemlist.firstElementChild; const contentTop = firstTile && firstTile.offsetTop; const fillPromises = []; // if scrollTop gets to 1 screen from the top of the first tile, // try backward filling if (!firstTile || sn.scrollTop - contentTop < sn.clientHeight) { // need to back-fill fillPromises.push(this._maybeFill(depth, true)); } // if scrollTop gets to 2 screens from the end (so 1 screen below viewport), // try forward filling if (sn.scrollHeight - sn.scrollTop < sn.clientHeight * 2) { // need to forward-fill fillPromises.push(this._maybeFill(depth, false)); } if (fillPromises.length) { try { await Promise.all(fillPromises); } catch (err) { console.error(err); } } if (isFirstCall) { debuglog("_isFilling: clearing"); this._isFilling = false; } if (this._fillRequestWhileRunning) { this._fillRequestWhileRunning = false; this.checkFillState(); } }); (0, _defineProperty2.default)(this, "getScrollState", () => this.scrollState); (0, _defineProperty2.default)(this, "resetScrollState", () => { this.scrollState = { stuckAtBottom: this.props.startAtBottom }; this._bottomGrowth = 0; this._pages = 0; this._scrollTimeout = new _Timer.default(100); this._heightUpdateInProgress = false; }); (0, _defineProperty2.default)(this, "scrollToTop", () => { this._getScrollNode().scrollTop = 0; this._saveScrollState(); }); (0, _defineProperty2.default)(this, "scrollToBottom", () => { // the easiest way to make sure that the scroll state is correctly // saved is to do the scroll, then save the updated state. (Calculating // it ourselves is hard, and we can't rely on an onScroll callback // happening, since there may be no user-visible change here). const sn = this._getScrollNode(); sn.scrollTop = sn.scrollHeight; this._saveScrollState(); }); (0, _defineProperty2.default)(this, "scrollRelative", mult => { const scrollNode = this._getScrollNode(); const delta = mult * scrollNode.clientHeight * 0.9; scrollNode.scrollBy(0, delta); this._saveScrollState(); }); (0, _defineProperty2.default)(this, "handleScrollKey", ev => { const roomAction = (0, _KeyBindingsManager.getKeyBindingsManager)().getRoomAction(ev); switch (roomAction) { case _KeyBindingsManager.RoomAction.ScrollUp: this.scrollRelative(-1); break; case _KeyBindingsManager.RoomAction.RoomScrollDown: this.scrollRelative(1); break; case _KeyBindingsManager.RoomAction.JumpToFirstMessage: this.scrollToTop(); break; case _KeyBindingsManager.RoomAction.JumpToLatestMessage: this.scrollToBottom(); break; } }); (0, _defineProperty2.default)(this, "scrollToToken", (scrollToken, pixelOffset, offsetBase) => { pixelOffset = pixelOffset || 0; offsetBase = offsetBase || 0; // set the trackedScrollToken so we can get the node through _getTrackedNode this.scrollState = { stuckAtBottom: false, trackedScrollToken: scrollToken }; const trackedNode = this._getTrackedNode(); const scrollNode = this._getScrollNode(); if (trackedNode) { // set the scrollTop to the position we want. // note though, that this might not succeed if the combination of offsetBase and pixelOffset // would position the trackedNode towards the top of the viewport. // This because when setting the scrollTop only 10 or so events might be loaded, // not giving enough content below the trackedNode to scroll downwards // enough so it ends up in the top of the viewport. debuglog("scrollToken: setting scrollTop", { offsetBase, pixelOffset, offsetTop: trackedNode.offsetTop }); scrollNode.scrollTop = trackedNode.offsetTop - scrollNode.clientHeight * offsetBase + pixelOffset; this._saveScrollState(); } }); (0, _defineProperty2.default)(this, "_collectScroll", divScroll => { this._divScroll = divScroll; }); (0, _defineProperty2.default)(this, "preventShrinking", () => { const messageList = this._itemlist.current; const tiles = messageList && messageList.children; if (!messageList) { return; } let lastTileNode; for (let i = tiles.length - 1; i >= 0; i--) { const node = tiles[i]; if (node.dataset.scrollTokens) { lastTileNode = node; break; } } if (!lastTileNode) { return; } this.clearPreventShrinking(); const offsetFromBottom = messageList.clientHeight - (lastTileNode.offsetTop + lastTileNode.clientHeight); this.preventShrinkingState = { offsetFromBottom: offsetFromBottom, offsetNode: lastTileNode }; debuglog("prevent shrinking, last tile ", offsetFromBottom, "px from bottom"); }); (0, _defineProperty2.default)(this, "clearPreventShrinking", () => { const messageList = this._itemlist.current; const balanceElement = messageList && messageList.parentElement; if (balanceElement) balanceElement.style.paddingBottom = null; this.preventShrinkingState = null; debuglog("prevent shrinking cleared"); }); (0, _defineProperty2.default)(this, "updatePreventShrinking", () => { if (this.preventShrinkingState) { const sn = this._getScrollNode(); const scrollState = this.scrollState; const messageList = this._itemlist.current; const { offsetNode, offsetFromBottom } = this.preventShrinkingState; // element used to set paddingBottom to balance the typing notifs disappearing const balanceElement = messageList.parentElement; // if the offsetNode got unmounted, clear let shouldClear = !offsetNode.parentElement; // also if 200px from bottom if (!shouldClear && !scrollState.stuckAtBottom) { const spaceBelowViewport = sn.scrollHeight - (sn.scrollTop + sn.clientHeight); shouldClear = spaceBelowViewport >= 200; } // try updating if not clearing if (!shouldClear) { const currentOffset = messageList.clientHeight - (offsetNode.offsetTop + offsetNode.clientHeight); const offsetDiff = offsetFromBottom - currentOffset; if (offsetDiff > 0) { balanceElement.style.paddingBottom = `${offsetDiff}px`; debuglog("update prevent shrinking ", offsetDiff, "px from bottom"); } else if (offsetDiff < 0) { shouldClear = true; } } if (shouldClear) { this.clearPreventShrinking(); } } }); this._pendingFillRequests = { b: null, f: null }; if (this.props.resizeNotifier) { this.props.resizeNotifier.on("middlePanelResizedNoisy", this.onResize); } this.resetScrollState(); this._itemlist = /*#__PURE__*/(0, _react.createRef)(); } componentDidMount() { this.checkScroll(); } componentDidUpdate() { // after adding event tiles, we may need to tweak the scroll (either to // keep at the bottom of the timeline, or to maintain the view after // adding events to the top). // // This will also re-check the fill state, in case the paginate was inadequate this.checkScroll(); this.updatePreventShrinking(); } componentWillUnmount() { // set a boolean to say we've been unmounted, which any pending // promises can use to throw away their results. // // (We could use isMounted(), but facebook have deprecated that.) this.unmounted = true; if (this.props.resizeNotifier) { this.props.resizeNotifier.removeListener("middlePanelResizedNoisy", this.onResize); } } // returns the vertical height in the given direction that can be removed from // the content box (which has a height of scrollHeight, see checkFillState) without // pagination occuring. // // padding* = UNPAGINATION_PADDING // // ### Region determined as excess. // // .---------. - - // |#########| | | // |#########| - | scrollTop | // | | | padding* | | // | | | | | // .-+---------+-. - - | | // : | | : | | | // : | | : | clientHeight | | // : | | : | | | // .-+---------+-. - - | // | | | | | | // | | | | | clientHeight | scrollHeight // | | | | | | // `-+---------+-' - | // : | | : | | // : | | : | clientHeight | // : | | : | | // `-+---------+-' - - | // | | | padding* | // | | | | // |#########| - | // |#########| | // `---------' - _getExcessHeight(backwards) { const sn = this._getScrollNode(); const contentHeight = this._getMessagesHeight(); const listHeight = this._getListHeight(); const clippedHeight = contentHeight - listHeight; const unclippedScrollTop = sn.scrollTop + clippedHeight; if (backwards) { return unclippedScrollTop - sn.clientHeight - UNPAGINATION_PADDING; } else { return contentHeight - (unclippedScrollTop + 2 * sn.clientHeight) - UNPAGINATION_PADDING; } } // check the scroll state and send out backfill requests if necessary. // check if unfilling is possible and send an unfill request if necessary _checkUnfillState(backwards) { let excessHeight = this._getExcessHeight(backwards); if (excessHeight <= 0) { return; } const origExcessHeight = excessHeight; const tiles = this._itemlist.current.children; // The scroll token of the first/last tile to be unpaginated let markerScrollToken = null; // Subtract heights of tiles to simulate the tiles being unpaginated until the // excess height is less than the height of the next tile to subtract. This // prevents excessHeight becoming negative, which could lead to future // pagination. // // If backwards is true, we unpaginate (remove) tiles from the back (top). let tile; for (let i = 0; i < tiles.length; i++) { tile = tiles[backwards ? i : tiles.length - 1 - i]; // Subtract height of tile as if it were unpaginated excessHeight -= tile.clientHeight; //If removing the tile would lead to future pagination, break before setting scroll token if (tile.clientHeight > excessHeight) { break; } // The tile may not have a scroll token, so guard it if (tile.dataset.scrollTokens) { markerScrollToken = tile.dataset.scrollTokens.split(',')[0]; } } if (markerScrollToken) { // Use a debouncer to prevent multiple unfill calls in quick succession // This is to make the unfilling process less aggressive if (this._unfillDebouncer) { clearTimeout(this._unfillDebouncer); } this._unfillDebouncer = setTimeout(() => { this._unfillDebouncer = null; debuglog("unfilling now", backwards, origExcessHeight); this.props.onUnfillRequest(backwards, markerScrollToken); }, UNFILL_REQUEST_DEBOUNCE_MS); } } // check if there is already a pending fill request. If not, set one off. _maybeFill(depth, backwards) { const dir = backwards ? 'b' : 'f'; if (this._pendingFillRequests[dir]) { debuglog("Already a " + dir + " fill in progress - not starting another"); return; } debuglog("starting " + dir + " fill"); // onFillRequest can end up calling us recursively (via onScroll // events) so make sure we set this before firing off the call. this._pendingFillRequests[dir] = true; // wait 1ms before paginating, because otherwise // this will block the scroll event handler for +700ms // if messages are already cached in memory, // This would cause jumping to happen on Chrome/macOS. return new Promise(resolve => setTimeout(resolve, 1)).then(() => { return this.props.onFillRequest(backwards); }).finally(() => { this._pendingFillRequests[dir] = false; }).then(hasMoreResults => { if (this.unmounted) { return; } // Unpaginate once filling is complete this._checkUnfillState(!backwards); debuglog("" + dir + " fill complete; hasMoreResults:" + hasMoreResults); if (hasMoreResults) { // further pagination requests have been disabled until now, so // it's time to check the fill state again in case the pagination // was insufficient. return this.checkFillState(depth + 1); } }); } /* get the current scroll state. This returns an object with the following * properties: * * boolean stuckAtBottom: true if we are tracking the bottom of the * scroll. false if we are tracking a particular child. * * string trackedScrollToken: undefined if stuckAtBottom is true; if it is * false, the first token in data-scroll-tokens of the child which we are * tracking. * * number bottomOffset: undefined if stuckAtBottom is true; if it is false, * the number of pixels the bottom of the tracked child is above the * bottom of the scroll panel. */ _saveScrollState() { if (this.props.stickyBottom && this.isAtBottom()) { this.scrollState = { stuckAtBottom: true }; debuglog("saved stuckAtBottom state"); return; } const scrollNode = this._getScrollNode(); const viewportBottom = scrollNode.scrollHeight - (scrollNode.scrollTop + scrollNode.clientHeight); const itemlist = this._itemlist.current; const messages = itemlist.children; let node = null; // TODO: do a binary search here, as items are sorted by offsetTop // loop backwards, from bottom-most message (as that is the most common case) for (let i = messages.length - 1; i >= 0; --i) { if (!messages[i].dataset.scrollTokens) { continue; } node = messages[i]; // break at the first message (coming from the bottom) // that has it's offsetTop above the bottom of the viewport. if (this._topFromBottom(node) > viewportBottom) { // Use this node as the scrollToken break; } } if (!node) { debuglog("unable to save scroll state: found no children in the viewport"); return; } const scrollToken = node.dataset.scrollTokens.split(',')[0]; debuglog("saving anchored scroll state to message", node && node.innerText, scrollToken); const bottomOffset = this._topFromBottom(node); this.scrollState = { stuckAtBottom: false, trackedNode: node, trackedScrollToken: scrollToken, bottomOffset: bottomOffset, pixelOffset: bottomOffset - viewportBottom //needed for restoring the scroll position when coming back to the room }; } async _restoreSavedScrollState() { const scrollState = this.scrollState; if (scrollState.stuckAtBottom) { const sn = this._getScrollNode(); if (sn.scrollTop !== sn.scrollHeight) { sn.scrollTop = sn.scrollHeight; } } else if (scrollState.trackedScrollToken) { const itemlist = this._itemlist.current; const trackedNode = this._getTrackedNode(); if (trackedNode) { const newBottomOffset = this._topFromBottom(trackedNode); const bottomDiff = newBottomOffset - scrollState.bottomOffset; this._bottomGrowth += bottomDiff; scrollState.bottomOffset = newBottomOffset; const newHeight = `${this._getListHeight()}px`; if (itemlist.style.height !== newHeight) { itemlist.style.height = newHeight; } debuglog("balancing height because messages below viewport grew by", bottomDiff); } } if (!this._heightUpdateInProgress) { this._heightUpdateInProgress = true; try { await this._updateHeight(); } finally { this._heightUpdateInProgress = false; } } else { debuglog("not updating height because request already in progress"); } } // need a better name that also indicates this will change scrollTop? Rebalance height? Reveal content? async _updateHeight() { // wait until user has stopped scrolling if (this._scrollTimeout.isRunning()) { debuglog("updateHeight waiting for scrolling to end ... "); await this._scrollTimeout.finished(); } else { debuglog("updateHeight getting straight to business, no scrolling going on."); } // We might have unmounted since the timer finished, so abort if so. if (this.unmounted) { return; } const sn = this._getScrollNode(); const itemlist = this._itemlist.current; const contentHeight = this._getMessagesHeight(); const minHeight = sn.clientHeight; const height = Math.max(minHeight, contentHeight); this._pages = Math.ceil(height / PAGE_SIZE); this._bottomGrowth = 0; const newHeight = `${this._getListHeight()}px`; const scrollState = this.scrollState; if (scrollState.stuckAtBottom) { if (itemlist.style.height !== newHeight) { itemlist.style.height = newHeight; } if (sn.scrollTop !== sn.scrollHeight) { sn.scrollTop = sn.scrollHeight; } debuglog("updateHeight to", newHeight); } else if (scrollState.trackedScrollToken) { const trackedNode = this._getTrackedNode(); // if the timeline has been reloaded // this can be called before scrollToBottom or whatever has been called // so don't do anything if the node has disappeared from // the currently filled piece of the timeline if (trackedNode) { const oldTop = trackedNode.offsetTop; if (itemlist.style.height !== newHeight) { itemlist.style.height = newHeight; } const newTop = trackedNode.offsetTop; const topDiff = newTop - oldTop; // important to scroll by a relative amount as // reading scrollTop and then setting it might // yield out of date values and cause a jump // when setting it sn.scrollBy(0, topDiff); debuglog("updateHeight to", { newHeight, topDiff }); } } } _getTrackedNode() { const scrollState = this.scrollState; const trackedNode = scrollState.trackedNode; if (!trackedNode || !trackedNode.parentElement) { let node; const messages = this._itemlist.current.children; const scrollToken = scrollState.trackedScrollToken; for (let i = messages.length - 1; i >= 0; --i) { const m = messages[i]; // 'data-scroll-tokens' is a DOMString of comma-separated scroll tokens // There might only be one scroll token if (m.dataset.scrollTokens && m.dataset.scrollTokens.split(',').indexOf(scrollToken) !== -1) { node = m; break; } } if (node) { debuglog("had to find tracked node again for " + scrollState.trackedScrollToken); } scrollState.trackedNode = node; } if (!scrollState.trackedNode) { debuglog("No node with ; '" + scrollState.trackedScrollToken + "'"); return; } return scrollState.trackedNode; } _getListHeight() { return this._bottomGrowth + this._pages * PAGE_SIZE; } _getMessagesHeight() { const itemlist = this._itemlist.current; const lastNode = itemlist.lastElementChild; const lastNodeBottom = lastNode ? lastNode.offsetTop + lastNode.clientHeight : 0; const firstNodeTop = itemlist.firstElementChild ? itemlist.firstElementChild.offsetTop : 0; // 18 is itemlist padding return lastNodeBottom - firstNodeTop + 18 * 2; } _topFromBottom(node) { // current capped height - distance from top = distance from bottom of container to top of tracked element return this._itemlist.current.clientHeight - node.offsetTop; } /* get the DOM node which has the scrollTop property we care about for our * message panel. */ _getScrollNode() { if (this.unmounted) { // this shouldn't happen, but when it does, turn the NPE into // something more meaningful. throw new Error("ScrollPanel._getScrollNode called when unmounted"); } if (!this._divScroll) { // Likewise, we should have the ref by this point, but if not // turn the NPE into something meaningful. throw new Error("ScrollPanel._getScrollNode called before AutoHideScrollbar ref collected"); } return this._divScroll; } render() { // TODO: the classnames on the div and ol could do with being updated to // reflect the fact that we don't necessarily contain a list of messages. // it's not obvious why we have a separate div and ol anyway. // give the <ol> an explicit role=list because Safari+VoiceOver seems to think an ordered-list with // list-style-type: none; is no longer a list return /*#__PURE__*/_react.default.createElement(_AutoHideScrollbar.default, { wrappedRef: this._collectScroll, onScroll: this.onScroll, className: `mx_ScrollPanel ${this.props.className}`, style: this.props.style }, this.props.fixedChildren, /*#__PURE__*/_react.default.createElement("div", { className: "mx_RoomView_messageListWrapper" }, /*#__PURE__*/_react.default.createElement("ol", { ref: this._itemlist, className: "mx_RoomView_MessageList", "aria-live": "polite", role: "list" }, this.props.children))); } }, (0, _defineProperty2.default)(_class2, "propTypes", { /* stickyBottom: if set to true, then once the user hits the bottom of * the list, any new children added to the list will cause the list to * scroll down to show the new element, rather than preserving the * existing view. */ stickyBottom: _propTypes.default.bool, /* startAtBottom: if set to true, the view is assumed to start * scrolled to the bottom. * XXX: It's likely this is unnecessary and can be derived from * stickyBottom, but I'm adding an extra parameter to ensure * behaviour stays the same for other uses of ScrollPanel. * If so, let's remove this parameter down the line. */ startAtBottom: _propTypes.default.bool, /* onFillRequest(backwards): a callback which is called on scroll when * the user nears the start (backwards = true) or end (backwards = * false) of the list. * * This should return a promise; no more calls will be made until the * promise completes. * * The promise should resolve to true if there is more data to be * retrieved in this direction (in which case onFillRequest may be * called again immediately), or false if there is no more data in this * directon (at this time) - which will stop the pagination cycle until * the user scrolls again. */ onFillRequest: _propTypes.default.func, /* onUnfillRequest(backwards): a callback which is called on scroll when * there are children elements that are far out of view and could be removed * without causing pagination to occur. * * This function should accept a boolean, which is true to indicate the back/top * of the panel and false otherwise, and a scroll token, which refers to the * first element to remove if removing from the front/bottom, and last element * to remove if removing from the back/top. */ onUnfillRequest: _propTypes.default.func, /* onScroll: a callback which is called whenever any scroll happens. */ onScroll: _propTypes.default.func, /* className: classnames to add to the top-level div */ className: _propTypes.default.string, /* style: styles to add to the top-level div */ style: _propTypes.default.object, /* resizeNotifier: ResizeNotifier to know when middle column has changed size */ resizeNotifier: _propTypes.default.object, /* fixedChildren: allows for children to be passed which are rendered outside * of the wrapper */ fixedChildren: _propTypes.default.node }), (0, _defineProperty2.default)(_class2, "defaultProps", { stickyBottom: true, startAtBottom: true, onFillRequest: function (backwards) { return Promise.resolve(false); }, onUnfillRequest: function (backwards, scrollToken) {}, onScroll: function () {} }), _temp)) || _class); exports.default = ScrollPanel; //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9jb21wb25lbnRzL3N0cnVjdHVyZXMvU2Nyb2xsUGFuZWwuanMiXSwibmFtZXMiOlsiREVCVUdfU0NST0xMIiwiVU5QQUdJTkFUSU9OX1BBRERJTkciLCJVTkZJTExfUkVRVUVTVF9ERUJPVU5DRV9NUyIsIlBBR0VfU0laRSIsImRlYnVnbG9nIiwiY29uc29sZSIsImxvZyIsImJpbmQiLCJTY3JvbGxQYW5lbCIsIlJlYWN0IiwiQ29tcG9uZW50IiwiY29uc3RydWN0b3IiLCJwcm9wcyIsImV2IiwicmVzaXplTm90aWZpZXIiLCJpc1Jlc2l6aW5nIiwiX2dldFNjcm9sbE5vZGUiLCJzY3JvbGxUb3AiLCJfc2Nyb2xsVGltZW91dCIsInJlc3RhcnQiLCJfc2F2ZVNjcm9sbFN0YXRlIiwidXBkYXRlUHJldmVudFNocmlua2luZyIsIm9uU2Nyb2xsIiwiY2hlY2tGaWxsU3RhdGUiLCJjaGVja1Njcm9sbCIsInByZXZlbnRTaHJpbmtpbmdTdGF0ZSIsInByZXZlbnRTaHJpbmtpbmciLCJ1bm1vdW50ZWQiLCJfcmVzdG9yZVNhdmVkU2Nyb2xsU3RhdGUiLCJzbiIsIk1hdGgiLCJhYnMiLCJzY3JvbGxIZWlnaHQiLCJjbGllbnRIZWlnaHQiLCJkZXB0aCIsImlzRmlyc3RDYWxsIiwiX2lzRmlsbGluZyIsIl9maWxsUmVxdWVzdFdoaWxlUnVubmluZyIsIml0ZW1saXN0IiwiX2l0ZW1saXN0IiwiY3VycmVudCIsImZpcnN0VGlsZSIsImZpcnN0RWxlbWVudENoaWxkIiwiY29udGVudFRvcCIsIm9mZnNldFRvcCIsImZpbGxQcm9taXNlcyIsInB1c2giLCJfbWF5YmVGaWxsIiwibGVuZ3RoIiwiUHJvbWlzZSIsImFsbCIsImVyciIsImVycm9yIiwic2Nyb2xsU3RhdGUiLCJzdHVja0F0Qm90dG9tIiwic3RhcnRBdEJvdHRvbSIsIl9ib3R0b21Hcm93dGgiLCJfcGFnZXMiLCJUaW1lciIsIl9oZWlnaHRVcGRhdGVJblByb2dyZXNzIiwibXVsdCIsInNjcm9sbE5vZGUiLCJkZWx0YSIsInNjcm9sbEJ5Iiwicm9vbUFjdGlvbiIsImdldFJvb21BY3Rpb24iLCJSb29tQWN0aW9uIiwiU2Nyb2xsVXAiLCJzY3JvbGxSZWxhdGl2ZSIsIlJvb21TY3JvbGxEb3duIiwiSnVtcFRvRmlyc3RNZXNzYWdlIiwic2Nyb2xsVG9Ub3AiLCJKdW1wVG9MYXRlc3RNZXNzYWdlIiwic2Nyb2xsVG9Cb3R0b20iLCJzY3JvbGxUb2tlbiIsInBpeGVsT2Zmc2V0Iiwib2Zmc2V0QmFzZSIsInRyYWNrZWRTY3JvbGxUb2tlbiIsInRyYWNrZWROb2RlIiwiX2dldFRyYWNrZWROb2RlIiwiZGl2U2Nyb2xsIiwiX2RpdlNjcm9sbCIsIm1lc3NhZ2VMaXN0IiwidGlsZXMiLCJjaGlsZHJlbiIsImxhc3RUaWxlTm9kZSIsImkiLCJub2RlIiwiZGF0YXNldCIsInNjcm9sbFRva2VucyIsImNsZWFyUHJldmVudFNocmlua2luZyIsIm9mZnNldEZyb21Cb3R0b20iLCJvZmZzZXROb2RlIiwiYmFsYW5jZUVsZW1lbnQiLCJwYXJlbnRFbGVtZW50Iiwic3R5bGUiLCJwYWRkaW5nQm90dG9tIiwic2hvdWxkQ2xlYXIiLCJzcGFjZUJlbG93Vmlld3BvcnQiLCJjdXJyZW50T2Zmc2V0Iiwib2Zmc2V0RGlmZiIsIl9wZW5kaW5nRmlsbFJlcXVlc3RzIiwiYiIsImYiLCJvbiIsIm9uUmVzaXplIiwicmVzZXRTY3JvbGxTdGF0ZSIsImNvbXBvbmVudERpZE1vdW50IiwiY29tcG9uZW50RGlkVXBkYXRlIiwiY29tcG9uZW50V2lsbFVubW91bnQiLCJyZW1vdmVMaXN0ZW5lciIsIl9nZXRFeGNlc3NIZWlnaHQiLCJiYWNrd2FyZHMiLCJjb250ZW50SGVpZ2h0IiwiX2dldE1lc3NhZ2VzSGVpZ2h0IiwibGlzdEhlaWdodCIsIl9nZXRMaXN0SGVpZ2h0IiwiY2xpcHBlZEhlaWdodCIsInVuY2xpcHBlZFNjcm9sbFRvcCIsIl9jaGVja1VuZmlsbFN0YXRlIiwiZXhjZXNzSGVpZ2h0Iiwib3JpZ0V4Y2Vzc0hlaWdodCIsIm1hcmtlclNjcm9sbFRva2VuIiwidGlsZSIsInNwbGl0IiwiX3VuZmlsbERlYm91bmNlciIsImNsZWFyVGltZW91dCIsInNldFRpbWVvdXQiLCJvblVuZmlsbFJlcXVlc3QiLCJkaXIiLCJyZXNvbHZlIiwidGhlbiIsIm9uRmlsbFJlcXVlc3QiLCJmaW5hbGx5IiwiaGFzTW9yZVJlc3VsdHMiLCJzdGlja3lCb3R0b20iLCJpc0F0Qm90dG9tIiwidmlld3BvcnRCb3R0b20iLCJtZXNzYWdlcyIsIl90b3BGcm9tQm90dG9tIiwiaW5uZXJUZXh0IiwiYm90dG9tT2Zmc2V0IiwibmV3Qm90dG9tT2Zmc2V0IiwiYm90dG9tRGlmZiIsIm5ld0hlaWdodCIsImhlaWdodCIsIl91cGRhdGVIZWlnaHQiLCJpc1J1bm5pbmciLCJmaW5pc2hlZCIsIm1pbkhlaWdodCIsIm1heCIsImNlaWwiLCJvbGRUb3AiLCJuZXdUb3AiLCJ0b3BEaWZmIiwibSIsImluZGV4T2YiLCJsYXN0Tm9kZSIsImxhc3RFbGVtZW50Q2hpbGQiLCJsYXN0Tm9kZUJvdHRvbSIsImZpcnN0Tm9kZVRvcCIsIkVycm9yIiwicmVuZGVyIiwiX2NvbGxlY3RTY3JvbGwiLCJjbGFzc05hbWUiLCJmaXhlZENoaWxkcmVuIiwiUHJvcFR5cGVzIiwiYm9vbCIsImZ1bmMiLCJzdHJpbmciLCJvYmplY3QiXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7QUFnQkE7O0FBQ0E7O0FBQ0E7O0FBQ0E7O0FBQ0E7O0FBQ0E7Ozs7QUFFQSxNQUFNQSxZQUFZLEdBQUcsS0FBckIsQyxDQUVBO0FBQ0E7O0FBQ0EsTUFBTUMsb0JBQW9CLEdBQUcsSUFBN0IsQyxDQUNBO0FBQ0E7O0FBQ0EsTUFBTUMsMEJBQTBCLEdBQUcsR0FBbkMsQyxDQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUNBLE1BQU1DLFNBQVMsR0FBRyxHQUFsQjtBQUVBLElBQUlDLFFBQUo7O0FBQ0EsSUFBSUosWUFBSixFQUFrQjtBQUNkO0FBQ0FJLEVBQUFBLFFBQVEsR0FBR0MsT0FBTyxD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