/** @jsxImportSource react */ import { Icon } from "../Icon"; import { VDOM } from "../../ui/Widget"; import { StyledContainerBase, StyledContainerConfig } from "../../ui/Container"; import { ResizeManager } from "../../ui/ResizeManager"; import { isString, scrollElementIntoView } from "../../util"; import { RenderingContext } from "../../ui/RenderingContext"; import { Instance } from "../../ui/Instance"; import { BooleanProp, StringProp } from "../../ui/Prop"; export interface ScrollerConfig extends StyledContainerConfig { horizontal?: BooleanProp; vertical?: BooleanProp; scrollIntoViewSelector?: StringProp; } export class Scroller extends StyledContainerBase { declare baseClass: string; declare public horizontal?: any; declare public vertical?: any; declare public scrollIntoViewSelector?: any; constructor(config?: ScrollerConfig) { super(config); } init() { if (!this.vertical) this.horizontal = true; //default super.init(); } declareData(...args: any[]) { super.declareData(...args, { scrollIntoViewSelector: undefined, }); } render(context: RenderingContext, instance: Instance, key: string) { return ( {(this as any).renderChildren(context, instance)} ); } } Scroller.prototype.baseClass = "scroller"; interface HScrollerComponentProps { widget: Scroller; data: any; children?: any; } interface HScrollerComponentState { scrollable: boolean; } export class HScrollerComponent extends VDOM.Component { declare el?: HTMLElement | null; declare clip?: HTMLElement | null; declare scroller?: HTMLElement | null; declare content?: HTMLElement | null; unsubscribeResize?: () => void; declare doScroll?: any; stopScrolling: () => void; scrollLeft: (e: any) => void; scrollRight: (e: any) => void; scrollUp: (e: any) => void; scrollDown: (e: any) => void; constructor(props: HScrollerComponentProps) { super(props); this.stopScrolling = () => { delete this.doScroll; }; this.scrollLeft = (e) => this.scroll(e, "left"); this.scrollRight = (e) => this.scroll(e, "right"); this.scrollUp = (e) => this.scroll(e, "up"); this.scrollDown = (e) => this.scroll(e, "down"); this.state = { scrollable: false }; } render() { let { data, children, widget } = this.props; let { CSS, baseClass } = widget; return (
{ this.el = el; }} > {widget.horizontal && (
{Icon.render("drop-down", { className: CSS.element(baseClass, "icon") })}
)} {widget.horizontal && (
{Icon.render("drop-down", { className: CSS.element(baseClass, "icon") })}
)} {widget.vertical && (
{Icon.render("drop-down", { className: CSS.element(baseClass, "icon") })}
)} {widget.vertical && (
{Icon.render("drop-down", { className: CSS.element(baseClass, "icon") })}
)}
{ this.clip = el; }} >
{ this.scroller = el; }} >
{ this.content = el; }} > {children}
); } componentDidMount() { this.unsubscribeResize = ResizeManager.trackElement(this.clip, this.componentDidUpdate.bind(this)); this.componentDidUpdate(); } componentDidUpdate() { let { widget } = this.props; let scrollable = false; if (widget.horizontal) { let scrollSize = this.scroller!.offsetHeight - this.scroller!.clientHeight; this.scroller!.style.marginBottom = `${-scrollSize}px`; if (this.content!.scrollWidth > this.clip!.clientWidth) scrollable = true; } if (widget.vertical) { let scrollSize = this.scroller!.offsetWidth - this.scroller!.clientWidth; this.scroller!.style.marginRight = `${-scrollSize}px`; if (this.content!.scrollHeight > this.clip!.clientHeight) scrollable = true; } if (scrollable != this.state.scrollable) this.setState({ scrollable }); this.scrollIntoView(); } componentWillUnmount() { this.unsubscribeResize!(); } scroll(e: any, direction: string) { e.stopPropagation(); e.preventDefault(); this.doScroll = () => { if (!this.scroller) return; switch (direction) { case "left": this.scroller.scrollLeft -= 10; break; case "right": this.scroller.scrollLeft += 10; break; case "up": this.scroller.scrollTop -= 10; break; case "down": this.scroller.scrollTop += 10; break; } if (this.doScroll) requestAnimationFrame(this.doScroll); }; this.doScroll(); } scrollIntoView() { let { data, widget } = this.props; let { scrollIntoViewSelector } = data; if (isString(scrollIntoViewSelector)) { let child = this.el?.querySelector(scrollIntoViewSelector); if (child) scrollElementIntoView(child, widget.vertical, widget.horizontal); } } } export class HScroller extends Scroller {} HScroller.prototype.horizontal = true; export class VScroller extends Scroller {} VScroller.prototype.vertical = true;