UNPKG

@alegendstale/holly-components

Version:

Reusable UI components created using lit

190 lines (188 loc) 6.54 kB
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 @click=${(t) => { t.stopPropagation(), this.toggleSheet(); }} > <button id="handle" part="__handle" @pointerdown=${this.pointerDown} @pointermove=${this.pointerMove} @pointerup=${this.pointerUp} @click=${(t) => { t.stopPropagation(); }} > ⸻ </button> <div id='content' part="__content" tabindex='0' @touchstart=${this.touchDown} @touchend=${this.touchUp} @touchmove=${this.touchMove} @scroll=${this.contentScroll} @touchcancel=${(t) => { console.warn("User Agent touchcancelled the event.", t.target); }} @dragstart=${(t) => { t.preventDefault(); }} @click=${(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 };