318 lines
9.9 KiB
JavaScript
318 lines
9.9 KiB
JavaScript
|
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 };
|