194 lines
5.9 KiB
JavaScript
194 lines
5.9 KiB
JavaScript
css(`
|
|
bottomsheet- {
|
|
scrollbar-width: none;
|
|
-ms-overflow-style: none;
|
|
}
|
|
|
|
bottomsheet- .VStack::-webkit-scrollbar {
|
|
display: none;
|
|
width: 0px;
|
|
height: 0px;
|
|
}
|
|
|
|
bottomsheet- .VStack::-webkit-scrollbar-thumb {
|
|
background: transparent;
|
|
}
|
|
|
|
bottomsheet- .VStack::-webkit-scrollbar-track {
|
|
background: transparent;
|
|
}
|
|
`)
|
|
|
|
class BottomSheet extends Shadow {
|
|
swipeDragStartX = null;
|
|
swipeDragStartY = null;
|
|
swipeDragStartTime = null;
|
|
isSwiping = false;
|
|
swipeAxisLocked = false;
|
|
swipeIsVertical = false;
|
|
swipeTranslate = 0;
|
|
|
|
SWIPE_COMMIT_DISTANCE = window.outerHeight * 0.25;
|
|
SWIPE_VELOCITY_THRESHOLD = 0.4;
|
|
|
|
renderContent = null;
|
|
_closeOverride = null;
|
|
|
|
constructor(zOffset = 0) {
|
|
super()
|
|
this.zOffset = zOffset;
|
|
}
|
|
|
|
show(renderFn, onClose = null) {
|
|
this._closeOverride = null;
|
|
this.renderContent = renderFn;
|
|
this._onClose = onClose;
|
|
this.rerender();
|
|
requestAnimationFrame(() => requestAnimationFrame(() => this.setSheet(true)));
|
|
}
|
|
|
|
replace(renderFn) {
|
|
this.renderContent = renderFn;
|
|
this.rerender();
|
|
if (this.sheetEl) {
|
|
this.sheetEl.style.transition = "none";
|
|
this.sheetEl.style.top = "7.5%";
|
|
this.sheetEl.style.pointerEvents = "auto";
|
|
}
|
|
if (this.overlayEl) {
|
|
this.overlayEl.style.transition = "none";
|
|
this.overlayEl.style.background = "rgba(0, 0, 0, 0.3)";
|
|
this.overlayEl.style.pointerEvents = "auto";
|
|
}
|
|
}
|
|
|
|
render() {
|
|
ZStack(() => {
|
|
this.overlayEl = ZStack()
|
|
.position("fixed")
|
|
.inset(0)
|
|
.zIndex(90 + this.zOffset)
|
|
.background("transparent")
|
|
.pointerEvents("none")
|
|
.onTap(() => this.toggle())
|
|
|
|
this.sheetEl = ZStack(() => {
|
|
VStack(() => {
|
|
if (this.renderContent) this.renderContent()
|
|
})
|
|
.height(100, pct)
|
|
.position("relative")
|
|
})
|
|
.position("fixed")
|
|
.height(92.5, pct)
|
|
.top(100, pct)
|
|
.left(0).right(0)
|
|
.zIndex(100 + this.zOffset)
|
|
.background("var(--main)")
|
|
.borderTopLeftRadius("10px").borderTopRightRadius("10px")
|
|
.boxSizing("border-box")
|
|
.transition("top .3s")
|
|
.pointerEvents("none")
|
|
.onTouch((start, e) => this.handleSwipeTouch(start, e))
|
|
})
|
|
}
|
|
|
|
get isOpen() {
|
|
return this.sheetEl?.style.top === "7.5%";
|
|
}
|
|
|
|
toggle() {
|
|
this.setSheet(!this.isOpen);
|
|
}
|
|
|
|
forceClose() {
|
|
this._closeOverride = null
|
|
this.setSheet(false)
|
|
}
|
|
|
|
setSheet(open) {
|
|
if (!open && this._closeOverride) { this._closeOverride(); return }
|
|
if (this.overlayEl) {
|
|
this.overlayEl.style.transition = "background 0.3s";
|
|
this.overlayEl.style.pointerEvents = open ? "auto" : "none";
|
|
this.overlayEl.style.background = open ? "rgba(0, 0, 0, 0.3)" : "transparent";
|
|
}
|
|
if (this.sheetEl) {
|
|
this.sheetEl.style.transition = "top 0.3s";
|
|
this.sheetEl.style.top = open ? "7.5%" : "100%";
|
|
this.sheetEl.style.pointerEvents = open ? "auto" : "none";
|
|
}
|
|
if (!open) {
|
|
if (this._onClose) {
|
|
const cb = this._onClose;
|
|
this._onClose = null;
|
|
setTimeout(cb, 300);
|
|
}
|
|
this.isSwiping = false;
|
|
this.swipeTranslate = 0;
|
|
this.swipeDragStartX = null;
|
|
this.swipeDragStartY = null;
|
|
document.removeEventListener("touchmove", this.onSwipeMove);
|
|
}
|
|
}
|
|
|
|
handleSwipeTouch(start, e) {
|
|
if (start) {
|
|
if (this.sheetEl?.style.top !== "7.5%") return;
|
|
e.stopPropagation();
|
|
this.swipeDragStartX = e.touches[0].clientX;
|
|
this.swipeDragStartY = e.touches[0].clientY;
|
|
this.swipeDragStartTime = Date.now();
|
|
this.isSwiping = true;
|
|
this.swipeAxisLocked = false;
|
|
this.swipeIsVertical = false;
|
|
document.addEventListener("touchmove", this.onSwipeMove, { passive: false });
|
|
} else {
|
|
if (!this.isSwiping) return;
|
|
document.removeEventListener("touchmove", this.onSwipeMove);
|
|
|
|
if (!this.swipeIsVertical) {
|
|
this.isSwiping = false;
|
|
this.setSheet(true);
|
|
return;
|
|
}
|
|
|
|
const delta = e.changedTouches[0].clientY - this.swipeDragStartY;
|
|
const velocity = Math.abs(delta) / (Date.now() - this.swipeDragStartTime);
|
|
const shouldCommit = delta > 0 && (delta > this.SWIPE_COMMIT_DISTANCE || velocity > this.SWIPE_VELOCITY_THRESHOLD);
|
|
|
|
this.swipeTranslate = 0;
|
|
this.setSheet(!shouldCommit);
|
|
|
|
this.isSwiping = false;
|
|
this.swipeDragStartX = null;
|
|
this.swipeDragStartY = null;
|
|
}
|
|
}
|
|
|
|
onSwipeMove = (e) => {
|
|
if (!this.isSwiping) return;
|
|
|
|
const dx = e.touches[0].clientX - this.swipeDragStartX;
|
|
const dy = e.touches[0].clientY - this.swipeDragStartY;
|
|
|
|
if (!this.swipeAxisLocked) {
|
|
if (Math.abs(dx) < 5 && Math.abs(dy) < 5) return;
|
|
this.swipeAxisLocked = true;
|
|
this.swipeIsVertical = Math.abs(dy) > Math.abs(dx);
|
|
}
|
|
if (!this.swipeIsVertical) return;
|
|
|
|
const delta = e.touches[0].clientY - this.swipeDragStartY;
|
|
const scrollEl = this.querySelector(".VStack");
|
|
if (delta <= 0 || (scrollEl && scrollEl.scrollTop > 0)) return;
|
|
|
|
e.preventDefault();
|
|
this.swipeTranslate = delta;
|
|
this.sheetEl.style.transition = "";
|
|
this.sheetEl.style.top = `calc(7.5% + ${delta}px)`;
|
|
}
|
|
}
|
|
|
|
register(BottomSheet)
|