diff --git a/assets/js/virtualized_lines/index.js b/assets/js/virtualized_lines/index.js index fd38d42f9..fe26b0779 100644 --- a/assets/js/virtualized_lines/index.js +++ b/assets/js/virtualized_lines/index.js @@ -1,5 +1,9 @@ import HyperList from "hyperlist"; -import { getAttributeOrThrow, parseInteger } from "../lib/attribute"; +import { + getAttributeOrThrow, + parseBoolean, + parseInteger, +} from "../lib/attribute"; import { getLineHeight } from "../lib/utils"; /** @@ -10,6 +14,8 @@ import { getLineHeight } from "../lib/utils"; * * * `data-max-height` - the maximum height of the element, exceeding this height enables scrolling * + * * `data-follow` - whether to automatically scroll to the bottom as new lines appear + * * The element should have two children: * * * one annotated with `data-template` attribute, it should be hidden @@ -60,7 +66,19 @@ const VirtualizedLines = { this.props.maxHeight, this.state.lineHeight ); + + const scrollTop = Math.round(this.state.contentElement.scrollTop); + const maxScrollTop = Math.round( + this.state.contentElement.scrollHeight - + this.state.contentElement.clientHeight + ); + const isAtTheEnd = scrollTop === maxScrollTop; + this.virtualizedList.refresh(this.state.contentElement, config); + + if (this.props.follow && isAtTheEnd) { + this.state.contentElement.scrollTop = this.state.contentElement.scrollHeight; + } }, }; @@ -81,6 +99,7 @@ function hyperListConfig(templateElement, maxHeight, lineHeight) { function getProps(hook) { return { maxHeight: getAttributeOrThrow(hook.el, "data-max-height", parseInteger), + follow: getAttributeOrThrow(hook.el, "data-follow", parseBoolean), }; } diff --git a/lib/livebook_web/live/cell_component.ex b/lib/livebook_web/live/cell_component.ex index 7b69d0c75..8f2a1c1de 100644 --- a/lib/livebook_web/live/cell_component.ex +++ b/lib/livebook_web/live/cell_component.ex @@ -204,7 +204,7 @@ defmodule LivebookWeb.CellComponent do assigns = %{lines: lines, id: id} ~L""" -