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; + } +}