let register = Toolkit => Toolkit.ScrollPane = // Scrolling container for larger internal elements class ScrollPane extends Toolkit.Component { ///////////////////////// Initialization Methods ////////////////////////// constructor(app, options = {}) { super(app, options = Object.assign({ class: "tk scroll-pane" }, options, { style: Object.assign({ display : "inline-grid", gridAutoRows: "auto max-content", overflow : "hidden", position : "relative" }, options.style || {}) })); // Configure options this._overflowX = "auto"; this._overflowY = "auto"; if ("overflowX" in options) this.overflowX = options.overflowX; if ("overflowY" in options) this.overflowY = options.overflowY; // Component this.addEventListener("wheel", e=>this.onWheel(e)); // Viewport this.viewport = new Toolkit.Component(app, { class: "viewport", style: { overflow: "hidden" } }); this.viewport.element.id = Toolkit.id(); this.viewport.addEventListener("keydown", e=>this.onKeyDown (e)); this.viewport.addEventListener("resize" , e=>this.onResize ( )); this.viewport.addEventListener("scroll" , e=>this.onInnerScroll( )); this.viewport.addEventListener("visibility", e=>{ if (e.visible) this.onResize(); }); this.add(this.viewport); // View resize manager this.viewResizer = new ResizeObserver(()=>this.onResize()); // Vertical scroll bar this.vscroll = new Toolkit.ScrollBar(app, { orientation: "vertical", visibility : true }); this.vscroll.controls = this.viewport; this.vscroll.addEventListener("scroll", e=>this.onOuterScroll(e, true)); this.add(this.vscroll); // Horizontal scroll bar this.hscroll = new Toolkit.ScrollBar(app, { orientation: "horizontal", visibility : true }); this.hscroll.controls = this.viewport; this.hscroll.addEventListener("scroll", e=>this.onOuterScroll(e, false)); this.add(this.hscroll); // Corner mask (for when both scroll bars are visible) this.corner = new Toolkit.Component(app, { class: "corner" }); this.add(this.corner); // Configure view this.view = options.view || null; } ///////////////////////////// Event Handlers ////////////////////////////// // Key press onKeyDown(e) { // Error checking if (e.altKey || e.ctrlKey || e.shiftKey || this.disabled) return; // Processing by key switch(e.key) { case "ArrowDown": this.viewport.element.scrollTop += this.vscroll.unitIncrement; break; case "ArrowLeft": this.viewport.element.scrollLeft -= this.hscroll.unitIncrement; break; case "ArrowRight": this.viewport.element.scrollLeft += this.hscroll.unitIncrement; break; case "ArrowUp": this.viewport.element.scrollTop -= this.vscroll.unitIncrement; break; case "PageDown": this.viewport.element.scrollTop += this.vscroll.blockIncrement; break; case "PageUp": this.viewport.element.scrollTop -= this.vscroll.blockIncrement; break; default: return; } Toolkit.handle(e); } // Component resized onResize() { // Error checking if (!this.viewport) return; // Working variables let viewport = this.viewport.element.getBoundingClientRect(); let scrollHeight = this.hscroll.element.getBoundingClientRect().height; let scrollWidth = this.vscroll.element.getBoundingClientRect().width; let viewHeight = this.viewHeight; let viewWidth = this.viewWidth; let fullHeight = viewport.height + (this.hscroll.visible ? scrollHeight : 0); let fullWidth = viewport.width + (this.vscroll.visible ? scrollWidth : 0); // Configure scroll bars this.hscroll.max = viewWidth; this.hscroll.blockIncrement = viewport.width; this.hscroll.value = this.viewport.element.scrollLeft; this.vscroll.max = viewHeight; this.vscroll.blockIncrement = viewport.height; this.vscroll.value = this.viewport.element.scrollTop; // Determine whether the vertical scroll bar is visible let vert = false; if ( this.overflowY == "scroll" || this.overflowY == "auto" && viewHeight > fullHeight && scrollWidth <= viewWidth ) { fullWidth -= scrollWidth; vert = true; } // Determine whether the horizontal scroll bar is visible let horz = false; if ( this.overflowX == "scroll" || this.overflowX == "auto" && viewWidth > fullWidth && scrollHeight <= viewHeight ) { fullHeight -= scrollHeight; horz = true; } // The horizontal scroll bar necessitates the vertical scroll bar vert = vert || this.overflowY == "auto" && viewHeight > fullHeight && scrollWidth < viewWidth ; // Configure scroll bar visibility this.setScrollBars(horz, vert); } // View scrolled onInnerScroll() { this.hscroll.value = this.viewport.element.scrollLeft; this.vscroll.value = this.viewport.element.scrollTop; } // Scroll bar scrolled onOuterScroll(e, vertical) { this.viewport.element[vertical?"scrollTop":"scrollLeft"] = e.scroll; } // Mouse wheel scrolled onWheel(e) { let delta = e.deltaY; // Error checking if (e.altKey || e.ctrlKey || e.shiftKey || delta == 0) return; // Scrolling by pixel if (e.deltaMode == WheelEvent.DOM_DELTA_PIXEL) { let max = this.vscroll.unitIncrement * 3; delta = Math.min(max, Math.max(-max, delta)); } // Scrolling by line else if (e.deltaMode == WheelEvent.DOM_DELTA_LINE) { delta = Math[delta < 0 ? "floor" : "ceil"](delta) * this.vscroll.unitIncrement; } // Scrolling by page else if (e.deltaMode == WheelEvent.DOM_DELTA_PAGE) { delta = Math[delta < 0 ? "floor" : "ceil"](delta) * this.vscroll.blockIncrement; } this.viewport.element.scrollTop += delta; Toolkit.handle(e); } ///////////////////////////// Public Methods ////////////////////////////// // Horizontal scroll bar policy get overflowX() { return this._overflowX; } set overflowX(policy) { switch (policy) { case "auto": case "hidden": case "scroll": this._overflowX = policy; this.onResize(); } } // Vertical scroll bar policy get overflowY() { return this._overflowY; } set overflowY(policy) { switch (policy) { case "auto": case "hidden": case "scroll": this._overflowY = policy; this.onResize(); } } // Horizontal scrolling position get scrollLeft() { return this.viewport.element.scrollLeft; } set scrollLeft(left) { this.viewport.element.scrollLeft = left; } // Vertical scrolling position get scrollTop() { return this.viewport.element.scrollTop; } set scrollTop(top) { this.viewport.element.scrollTop = top; } // Represented scrollable region get view() { let ret = this.viewport.element.querySelector("*"); return ret && ret.component || ret || null; } set view(view) { view = view instanceof Toolkit.Component ? view.element : view; if (view == null) { view = this.viewport.element.querySelector("*"); if (view) this.viewResizer.unobserve(view); this.viewport.element.replaceChildren(); } else { this.viewport.element.replaceChildren(view); this.viewResizer.observe(view); } } // Height of the view element set viewHeight(x) { } get viewHeight() { let view = this.view && this.view.element || this.view; return Math.min( this.viewport.element.scrollHeight, view ? view.clientHeight : 0 ); } // width of the view element set viewWidth(x) { } get viewWidth() { let view = this.view && this.view.element || this.view; return Math.min( this.viewport.element.scrollWidth, view ? view.clientWidth : 0 ); } ///////////////////////////// Private Methods ///////////////////////////// // Configure scroll bar visibility setScrollBars(horz, vert) { this.element.style.gridTemplateColumns = "auto" + (vert ? " max-content" : ""); this.hscroll.visible = horz; this.hscroll.element.style[horz ? "removeProperty" : "setProperty"] ("position", "absolute"); this.vscroll.visible = vert; this.vscroll.element.style[vert ? "removeProperty" : "setProperty"] ("position", "absolute"); this.corner.visible = vert && horz; this.element.classList[horz ? "add" : "remove"]("horizontal"); this.element.classList[vert ? "add" : "remove"]("vertical"); } } export { register };