matrix-react-sdk
Version:
SDK for matrix.org using React
899 lines (735 loc) • 108 kB
JavaScript
"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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