import { ComponentId } from "../dataModels"; import { ComponentBase, ComponentState } from "./componentBase"; export type ScrollContainerState = ComponentState & { _type_: "ScrollContainer-builtin"; content?: ComponentId; scroll_x?: "never" | "auto" | "always"; scroll_y?: "never" | "auto" | "always"; initial_x?: number; initial_y?: number; sticky_bottom?: boolean; }; export class ScrollContainerComponent extends ComponentBase { state: Required; private scrollerElement: HTMLElement; private childContainer: HTMLElement; private scrollAnchor: HTMLElement; createElement(): HTMLElement { let element = document.createElement("div"); element.classList.add("rio-scroll-container"); this.scrollerElement = document.createElement("div"); element.appendChild(this.scrollerElement); // `sticky_bottom` is implemented via scroll anchoring, so we need a // column that contains the child component and the scroll anchor let column = document.createElement("div"); column.classList.add("rio-scroll-container-column"); this.scrollerElement.appendChild(column); this.childContainer = document.createElement("div"); this.childContainer.classList.add( "rio-scroll-container-child-container" ); column.appendChild(this.childContainer); this.scrollAnchor = document.createElement("div"); this.scrollAnchor.classList.add("rio-scroll-container-anchor"); column.appendChild(this.scrollAnchor); // Once the layouting is done, scroll to the initial position requestAnimationFrame(() => { this.scrollerElement.scrollLeft = this.state.initial_x * (this.scrollerElement.scrollWidth - this.scrollerElement.clientWidth); this.scrollerElement.scrollTop = this.state.initial_y * (this.scrollerElement.scrollHeight - this.scrollerElement.clientHeight); }); return element; } updateElement( deltaState: ScrollContainerState, latentComponents: Set ): void { super.updateElement(deltaState, latentComponents); this.replaceOnlyChild( latentComponents, deltaState.content, this.childContainer ); if (deltaState.scroll_x !== undefined) { this.element.dataset.scrollX = deltaState.scroll_x; } if (deltaState.scroll_y !== undefined) { this.element.dataset.scrollY = deltaState.scroll_y; } if (deltaState.sticky_bottom !== undefined) { this.scrollAnchor.style.overflowAnchor = deltaState.sticky_bottom ? "auto" : "none"; } } }