@alegendstale/holly-components
Version:
Reusable UI components created using lit
190 lines (188 loc) • 6.54 kB
JavaScript
import { LitElement as g, html as c } from "lit";
import { query as p, property as h, customElement as u } from "lit/decorators.js";
import d from "./bottom-sheet.styles.js";
import { HCSnappedEvent as f } from "../../events/snapped.js";
var m = Object.defineProperty, y = Object.getOwnPropertyDescriptor, r = (t, e, i, n) => {
for (var s = n > 1 ? void 0 : n ? y(e, i) : e, a = t.length - 1, l; a >= 0; a--)
(l = t[a]) && (s = (n ? l(e, i, s) : l(s)) || s);
return n && s && m(e, i, s), s;
};
let o = class extends g {
constructor() {
super(...arguments), this.open = !1, this.nonmodal = !1, this.dragging = !1, this.scrolling = !1, this.snapPoints = [], this.defaultSnap = NaN, this.startY = 0, this.startHeight = 0, this.previousTouch = null, this.lastScrollTop = 0;
}
set height(t) {
const e = Math.min(Math.max(t, 0), 100);
this.dispatchEvent(new f({ isSnapped: this.isSnapped(e) })), isFinite(t) ? this.style.setProperty("--height", `${Math.round(e)}dvh`) : this.style.setProperty("--height", "fit-content");
}
get height() {
return parseInt(this.style.getPropertyValue("--height"));
}
connectedCallback() {
super.connectedCallback();
const t = this.snapPoints.length === 2 ? 0 : 1;
this.defaultSnap = this.snapPoints.length > 0 ? isNaN(this.defaultSnap) ? this.snapPoints[Math.round(this.snapPoints.length / 2) - t] : this.defaultSnap : NaN, requestAnimationFrame(() => {
this.showSheet(this.open);
});
}
render() {
return requestAnimationFrame(() => {
this.setDialog(this.open);
}), c`
<dialog
=${(t) => {
t.stopPropagation(), this.toggleSheet();
}}
>
<button
id="handle"
part="__handle"
=${this.pointerDown}
=${this.pointerMove}
=${this.pointerUp}
=${(t) => {
t.stopPropagation();
}}
>
⸻
</button>
<div
id='content'
part="__content"
tabindex='0'
=${this.touchDown}
=${this.touchUp}
=${this.touchMove}
=${this.contentScroll}
=${(t) => {
console.warn("User Agent touchcancelled the event.", t.target);
}}
=${(t) => {
t.preventDefault();
}}
=${(t) => {
t.stopPropagation();
}}
>
<slot></slot>
</div>
</dialog>
`;
}
/**
* Find closest point index to target in array
*/
closestSnapPointIndex(t, e) {
return t.reduce((i, n, s) => Math.abs(n - e) < Math.abs(t[i] - e) ? s : i, 0);
}
/**
* Sets sheet height to the closest snap
* @param height percentage
*/
setSnap(t) {
if (this.snapPoints.length === 0) return;
const e = this.snapPoints[this.closestSnapPointIndex(this.snapPoints, t)];
e === this.snapPoints[0] && this.showSheet(!1), this.height = e;
}
/**
* @returns Whether sheet is snapped to a snap point
*/
isSnapped(t) {
return this.snapPoints.some((e) => e === Math.round(t));
}
/**
* Sets the display state of the sheet
*/
showSheet(t) {
this.open = t, this.height = isNaN(this.defaultSnap) ? 1 / 0 : this.defaultSnap, this.setOverscroll(t), this.setDialog(t);
}
/**
* Toggles the display state of the sheet
*/
toggleSheet() {
this.showSheet(!this.open);
}
/**
* Remove overscroll to prevent mobile browsers from refreshing during drag
*/
setOverscroll(t) {
const e = document.querySelector("html"), i = document.querySelector("body"), n = t ? "none" : "unset";
e && e.style.setProperty("overscroll-behavior-block", n), i && i.style.setProperty("overscroll-behavior-block", n);
}
setDialog(t) {
t && !this.dialog.open ? this.nonmodal ? this.dialog.show() : this.dialog.showModal() : t && this.dialog.open ? this.nonmodal && this.modalDialog ? (this.dialog.close(), this.dialog.show()) : !this.nonmodal && this.nonModalDialog && (this.dialog.close(), this.dialog.showModal()) : this.dialog.close();
}
touchDown(t) {
t.target instanceof HTMLElement && (this.startY = t.touches[0].pageY, this.startHeight = this.height);
}
touchMove(t) {
if (!(t.target instanceof HTMLElement)) return;
const e = t.touches[0];
let i = NaN;
this.previousTouch && (i = e.pageY - this.previousTouch.pageY), this.previousTouch = e;
let n = this.startY - t.touches[0].pageY;
const s = this.startHeight + n / window.innerHeight * 100;
!this.scrolling && this.dragging ? this.height = s : this.scrolling && this.dragging || this.scrolling && !this.dragging ? t.target.focus({ preventScroll: !0 }) : !this.scrolling && !this.dragging && (i >= 0 ? this.height = Math.min(s, this.defaultSnap || this.snapPoints[this.snapPoints.length - 1]) : t.target.focus({ preventScroll: !0 }));
}
touchUp(t) {
t.target instanceof HTMLElement && (this.previousTouch = null, this.setSnap(this.height));
}
pointerDown(t) {
t.target instanceof HTMLElement && (t.target.setPointerCapture(t.pointerId), this.startY = t.pageY, this.startHeight = this.height);
}
pointerMove(t) {
if (!(t.target instanceof HTMLElement && t.target.hasPointerCapture(t.pointerId)) || !t.isPrimary) return;
t.preventDefault();
let e = this.startY - t.pageY;
const i = this.startHeight + e / window.innerHeight * 100;
t.target.id === "handle" && (this.height = i);
}
pointerUp(t) {
t.target instanceof HTMLElement && (t.target.releasePointerCapture(t.pointerId), this.setSnap(this.height));
}
onSnapped(t, e) {
this.dragging = !t;
}
contentScroll(t) {
if (!(t.target instanceof HTMLElement && t.target.id === "content")) return;
const e = t.target.scrollTop;
this.scrolling = e !== this.lastScrollTop, this.lastScrollTop = e, e === 0 && (this.scrolling = !1);
}
};
o.styles = [d];
r([
p("dialog")
], o.prototype, "dialog", 2);
r([
p("dialog:not(:modal)")
], o.prototype, "nonModalDialog", 2);
r([
p("dialog:modal")
], o.prototype, "modalDialog", 2);
r([
h({ type: Boolean, reflect: !0 })
], o.prototype, "open", 2);
r([
h({ type: Boolean, reflect: !0 })
], o.prototype, "nonmodal", 2);
r([
h({ type: Boolean, reflect: !0 })
], o.prototype, "dragging", 2);
r([
h({ type: Boolean, reflect: !0 })
], o.prototype, "scrolling", 2);
r([
h({ type: Array })
], o.prototype, "snapPoints", 2);
r([
h({ type: Number })
], o.prototype, "defaultSnap", 2);
r([
h({ type: Number, reflect: !0 })
], o.prototype, "height", 1);
o = r([
u("bottom-sheet")
], o);
export {
o as BottomSheet
};