init
This commit is contained in:
193
components/BottomSheet.js
Normal file
193
components/BottomSheet.js
Normal file
@@ -0,0 +1,193 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user