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'); 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'; } } }