diff --git a/components/app.js b/components/app.js
index 0d39426..16fcc9b 100644
--- a/components/app.js
+++ b/components/app.js
@@ -4,6 +4,7 @@ import Buffer from "/components/buffer.js";
import BufferList from "/components/buffer-list.js";
import Connect from "/components/connect.js";
import Composer from "/components/composer.js";
+import ScrollManager from "/components/scroll-manager.js";
import { html, Component, createRef } from "/lib/index.js";
import { SERVER_BUFFER, Status, Unread } from "/state.js";
@@ -37,6 +38,7 @@ export default class App extends Component {
buffers: new Map(),
activeBuffer: null,
};
+ buffer = createRef();
composer = createRef();
constructor(props) {
@@ -414,9 +416,11 @@ export default class App extends Component {
-
- <${Buffer} buffer=${activeBuffer}/>
-
+ <${ScrollManager} target=${this.buffer} scrollKey=${this.state.activeBuffer}>
+
+ <${Buffer} buffer=${activeBuffer}/>
+
+ >
<${Composer} ref=${this.composer} readOnly=${this.state.activeBuffer == SERVER_BUFFER} onSubmit=${this.handleComposerSubmit}/>
`;
}
diff --git a/components/scroll-manager.js b/components/scroll-manager.js
new file mode 100644
index 0000000..7071819
--- /dev/null
+++ b/components/scroll-manager.js
@@ -0,0 +1,76 @@
+import { html, Component } from "/lib/index.js";
+
+var store = new Map();
+
+export default class ScrollManager extends Component {
+ stickToBottom = false;
+
+ constructor(props) {
+ super(props);
+
+ this.handleScroll = this.handleScroll.bind(this);
+ }
+
+ isAtBottom() {
+ var target = this.props.target.current;
+ return target.scrollTop >= target.scrollHeight - target.offsetHeight;
+ }
+
+ scroll(pos) {
+ var target = this.props.target.current;
+ if (pos.bottom) {
+ pos.y = target.scrollHeight - target.offsetHeight;
+ }
+ target.scrollTop = pos.y;
+ }
+
+ saveScrollPosition() {
+ var target = this.props.target.current;
+ store.set(this.props.scrollKey, {
+ y: target.scrollTop,
+ bottom: this.isAtBottom(),
+ });
+ }
+
+ restoreScrollPosition() {
+ var target = this.props.target.current;
+ var pos = store.get(this.props.scrollKey);
+ if (!pos) {
+ pos = { bottom: true };
+ }
+ this.scroll(pos);
+ this.stickToBottom = pos.bottom;
+ }
+
+ handleScroll() {
+ this.stickToBottom = this.isAtBottom();
+ }
+
+ componentDidMount() {
+ this.restoreScrollPosition();
+ this.props.target.current.addEventListener("scroll", this.handleScroll);
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (this.props.scrollKey !== nextProps.scrollKey) {
+ this.saveScrollPosition();
+ }
+ }
+
+ componentDidUpdate(prevProps) {
+ if (this.props.scrollKey !== prevProps.scrollKey) {
+ this.restoreScrollPosition();
+ } else if (this.stickToBottom) {
+ this.scroll({ bottom: true });
+ }
+ }
+
+ componentWillUnmount() {
+ this.props.target.current.removeEventListener("scroll", this.handleScroll);
+ this.saveScrollPosition();
+ }
+
+ render() {
+ return this.props.children;
+ }
+}