@douxcode/vue-spring-bottom-sheet
Version:
Modern and Performant Bottom Sheet for Vue.js
422 lines (421 loc) • 15.1 kB
JavaScript
import { ref as C, computed as _, defineComponent as Be, watch as q, onMounted as Ie, toRefs as Ee, nextTick as N, onUnmounted as _e, createBlock as te, openBlock as ae, Teleport as De, createElementVNode as ne, createVNode as $, unref as m, withCtx as E, createCommentVNode as ge, normalizeStyle as He, normalizeClass as oe, renderSlot as le } from "vue";
import { useMotionValue as me, animate as B, AnimatePresence as ye, Motion as A } from "motion-v";
import { useVModel as Ve, useWindowSize as Fe, useElementBounding as Q, useScrollLock as be } from "@vueuse/core";
import { useFocusTrap as Re } from "@vueuse/integrations/useFocusTrap";
function G(n, a) {
const o = parseFloat(n);
return a * o / 100;
}
function $e(n, a, o) {
const t = C(0), s = _(() => n.value.map((f) => typeof f == "string" ? G(f, o.value) : f)), b = _(() => Math.min(...s.value)), r = _(() => Math.max(...s.value)), p = _(() => {
const f = s.value.reduce(
(h, O) => Math.abs(O - a.value) < Math.abs(h - a.value) ? O : h
);
return s.value.indexOf(f);
});
return {
currentSnapPointIndex: t,
flattenedSnapPoints: s,
minSnapPoint: b,
maxSnapPoint: r,
closestSnapPointIndex: p
};
}
function Ae(n, a, o) {
let t = (s) => n(s, ...a);
return o === void 0 ? t : Object.assign(t, { lazy: o, lazyArgs: a });
}
function We(n, a, o) {
let t = n.length - a.length;
if (t === 0) return n(...a);
if (t === 1) return Ae(n, a, o);
throw new Error("Wrong number of arguments");
}
function je(n, { triggerAt: a = "end", minQuietPeriodMs: o, maxBurstDurationMs: t, minGapMs: s, reducer: b = ze }) {
let r, p, f, h, O = () => {
let i = f;
i !== void 0 && (f = void 0, n(i), s !== void 0 && (p = setTimeout(D, s)));
}, D = () => {
clearTimeout(p), p = void 0, r === void 0 && O();
}, H = () => {
clearTimeout(r), r = void 0, h = void 0, p === void 0 && O();
};
return { call: (...i) => {
let c = r === void 0 && p === void 0;
if ((a !== "start" || c) && (f = b(f, ...i)), !(r === void 0 && !c)) {
if (o !== void 0 || t !== void 0 || s === void 0) {
clearTimeout(r);
let P = Date.now();
h ?? (h = P);
let W = t === void 0 ? o ?? 0 : Math.min(o ?? t, t - (P - h));
r = setTimeout(H, W);
}
a !== "end" && c && O();
}
}, cancel: () => {
clearTimeout(r), r = void 0, h = void 0, clearTimeout(p), p = void 0, f = void 0;
}, flush: () => {
H(), D();
}, get isIdle() {
return r === void 0 && p === void 0;
} };
}
var ze = () => "";
function y(...n) {
return We(Le, n);
}
var Le = (n, { min: a, max: o }) => a !== void 0 && n < a ? a : o !== void 0 && n > o ? o : n;
function qe(n, a, o) {
return Math.max(a, Math.min(n, o));
}
function Ne(n, a) {
return Math.pow(n, a * 5);
}
function Se(n, a, o) {
return a === 0 || Math.abs(a) === 1 / 0 ? Ne(n, o) : n * a * o / (a + o * n);
}
function ue(n, a, o, t = 0.15) {
return t === 0 ? qe(n, a, o) : n < a ? -Se(a - n, o - a, t) + a : n > o ? +Se(n - o, o - a, t) + o : n;
}
const Qe = { "data-vsbs-container": "" }, Ge = /* @__PURE__ */ Be({
__name: "BottomSheet",
props: {
duration: { default: 250 },
snapPoints: {},
initialSnapPoint: {},
blocking: { type: Boolean, default: !0 },
canSwipeClose: { type: Boolean, default: !0 },
swipeCloseThreshold: {},
canBackdropClose: { type: Boolean, default: !0 },
expandOnContentDrag: { type: Boolean, default: !0 },
modelValue: { type: Boolean },
teleportTo: { default: "body" },
teleportDefer: { type: Boolean, default: !1 },
headerClass: {},
contentClass: {},
footerClass: {}
},
emits: ["opened", "opening-started", "closed", "closing-started", "ready", "dragging-up", "dragging-down", "snapped", "instinctHeight", "update:modelValue"],
setup(n, { expose: a, emit: o }) {
const t = n, s = o, b = Ve(t, "modelValue", s, {
passive: !0
});
q(b, (e) => {
e && X();
}), Ie(() => {
b.value && X();
});
const r = C(), p = C(null), f = C(null), h = C(null), O = C(null), D = C(null), H = C(null), i = C(t.expandOnContentDrag), { height: c } = Fe(), { height: P } = Q(r), { height: W } = Q(p), { height: se } = Q(D), { height: re } = Q(f), K = _({
get() {
return y(
Math.ceil(se.value + W.value + re.value),
{
max: c.value
}
);
},
set(e) {
[W.value, se.value, re.value] = e;
}
}), l = C(0), v = C(0), w = me(0), S = me(0), { snapPoints: Ce } = Ee(t), d = _(() => Ce.value ?? [K.value]), {
flattenedSnapPoints: U,
currentSnapPointIndex: I,
closestSnapPointIndex: V,
minSnapPoint: M,
maxSnapPoint: F
} = $e(d, l, c);
let T;
const j = be(document.body), z = be(document.documentElement), Y = Re([r, H], {
immediate: !1,
fallbackFocus: () => {
var e;
return ((e = r.value) == null ? void 0 : e.$el) || document.body;
}
});
function ie(e) {
i.value = !0, ve(e);
}
function ve(e) {
i.value && e.preventDefault();
}
const de = (e) => {
e.key === "Escape" && L();
}, Pe = () => {
t.canBackdropClose && L();
};
let J = !1;
const X = async () => {
if (J) return;
b.value = !0, J = !0, s("opening-started"), t.blocking && (j.value = !0, z.value = !0), await N();
const e = r.value.$el;
P.value = e.getBoundingClientRect().height;
const u = e.querySelector("[data-vsbs-content]"), g = e.querySelector("[data-vsbs-header]"), k = e.querySelector("[data-vsbs-footer]");
if (K.value = [
g.getBoundingClientRect().height,
u.getBoundingClientRect().height,
k.getBoundingClientRect().height
], await N(), I.value = U.value.findIndex(
(x) => x === M.value
), t.initialSnapPoint) {
const x = t.initialSnapPoint;
if (x < 0 || x >= d.value.length) {
console.warn("Index out of bounds");
return;
}
let R;
typeof d.value[x] == "number" ? R = y(d.value[x], {
max: c.value
}) : R = G(d.value[x], c.value), l.value = R;
} else
l.value = y(M.value, {
max: c.value
});
v.value = l.value, w.jump(l.value), S.jump(l.value), requestAnimationFrame(() => {
T = B(w, l.value, {
duration: t.duration / 1e3,
ease: "easeInOut"
}), T = B(S, 0, {
duration: t.duration / 1e3,
ease: "easeInOut",
onComplete: () => {
t.blocking && (s("opened"), Y.activate());
}
});
}), window.addEventListener("keydown", de), J = !1;
};
let Z = !1;
const L = () => {
Z || (b.value = !1, Z = !0, s("closing-started"), t.blocking && (j.value = !1, z.value = !1), window.removeEventListener("keydown", de), t.blocking && Y.deactivate(), setTimeout(() => {
s("closed"), Z = !1;
}, t.duration));
}, ce = (e) => {
if (!d.value) return;
if (e < 0 || e >= d.value.length) {
console.warn("Index out of bounds");
return;
}
I.value = e;
let u;
typeof d.value[e] == "number" ? u = y(d.value[e], {
max: c.value
}) : u = G(d.value[e], c.value), l.value = u, T = B(w, l.value, {
duration: t.duration / 1e3,
ease: "easeInOut",
onComplete: () => s("snapped", d.value.indexOf(d.value[e]))
});
};
function fe(e) {
e > 0 ? s("dragging-down") : e < 0 && s("dragging-up");
}
const pe = () => {
l.value = P.value, v.value = S.get(), w.jump(l.value), S.jump(v.value);
}, he = async (e, u) => {
await N(), r.value && (v.value <= 0 && (l.value -= u.delta.y), l.value <= M.value && (l.value = M.value, v.value += u.delta.y, S.set(
t.canSwipeClose ? y(v.value, { min: 0 }) : y(ue(v.value, -P.value, 0, 0.5), {
min: 0
})
)), w.set(
y(ue(l.value, 0, F.value, 0.25), {
min: 0,
max: c.value
})
), fe(u.delta.y));
}, ee = () => {
if (t.canSwipeClose) {
let u = l.value / 2;
t.swipeCloseThreshold && typeof t.swipeCloseThreshold == "number" && (u = t.swipeCloseThreshold), t.swipeCloseThreshold && typeof t.swipeCloseThreshold == "string" && t.swipeCloseThreshold.includes("%") && (u = l.value * (Number(t.swipeCloseThreshold.replace("%", "")) / 100)), v.value > u && (v.value = l.value);
} else
v.value = 0;
T = B(S, v.value, {
duration: t.duration / 1e3,
ease: "easeInOut"
}), v.value === l.value && (v.value = 0, L()), I.value = V.value;
let e;
typeof d.value[V.value] == "number" ? e = y(d.value[V.value], {
max: c.value
}) : e = G(
d.value[V.value],
c.value
), l.value = e, T = B(w, l.value, {
duration: t.duration / 1e3,
ease: "easeInOut",
onComplete: () => s(
"snapped",
d.value.indexOf(d.value[V.value])
)
}), T = B(S, 0, {
duration: t.duration / 1e3,
ease: "easeInOut"
});
}, we = (e, u) => {
if (l.value = P.value, v.value = S.get(), T && T.stop(), !h.value) return;
const g = h.value.scrollTop === 0, k = u.delta.y > 0, x = U.value.length === 1, R = 0.5 > Math.abs(l.value - F.value);
if (x) {
if (!t.expandOnContentDrag) {
i.value = !1;
return;
}
S.get() === 0 && g && k && (i.value = !0), S.get() === 0 && g && !k && (i.value = !1);
} else {
if (!t.expandOnContentDrag) {
i.value = !1;
return;
}
i.value = !0, R && (k && g && (i.value = !0), !k && g && (i.value = !1), g || (i.value = !1));
}
}, Te = async (e, u) => {
if (await N(), !t.expandOnContentDrag) {
i.value = !1;
return;
}
if (!r.value) return;
v.value === 0 && i.value && t.expandOnContentDrag && (l.value -= u.delta.y), l.value <= M.value && (l.value = M.value, i.value && t.expandOnContentDrag && (v.value += u.delta.y), v.value = y(v.value, { min: 0, max: M.value }), S.set(
t.canSwipeClose ? y(v.value, { min: 0 }) : y(ue(v.value, -P.value, 0, 0.5), {
min: 0
})
)), l.value > F.value && (l.value = F.value), l.value = y(l.value, { max: c.value }), U.value.length === 1 || l.value === F.value && (i.value = !1), w.set(l.value), fe(u.delta.y);
}, ke = () => {
t.blocking || (j.value = !0, z.value = !0);
}, xe = () => {
t.blocking || (j.value = !1, z.value = !1);
}, Oe = () => {
if (!h.value) return;
const e = h.value.scrollTop === 0;
i.value = e;
}, Me = je((e) => ce(e), {
minQuietPeriodMs: t.duration,
reducer: (e, u) => u
});
return q(d, (e, u) => {
if (b.value === !1 || !e || !u) return;
const g = e[I.value], k = u[I.value];
typeof g != "string" && typeof k != "string" && (l.value = y(g, {
max: c.value
}), g !== k && (T = B(w, l.value, {
duration: t.duration / 1e3,
ease: "easeInOut"
})));
}), q(c, () => {
Me.call(I.value);
}), q(K, (e) => {
s("instinctHeight", e);
}), _e(() => {
Y.deactivate();
}), a({ open: X, close: L, snapToPoint: ce }), (e, u) => (ae(), te(De, {
to: e.teleportTo,
defer: e.teleportDefer
}, [
ne("div", Qe, [
$(m(ye), null, {
default: E(() => [
m(b) && e.blocking ? (ae(), te(m(A), {
key: 0,
ref_key: "backdrop",
ref: H,
"data-vsbs-backdrop": "",
onClick: u[0] || (u[0] = (g) => Pe()),
transition: {
ease: "easeInOut",
duration: e.duration / 1e3
},
initial: { opacity: 0 },
animate: { opacity: 1 },
exit: { opacity: 0 }
}, null, 8, ["transition"])) : ge("", !0)
]),
_: 1
}),
$(m(ye), null, {
default: E(() => [
m(b) ? (ae(), te(m(A), {
key: 0,
ref_key: "sheet",
ref: r,
exit: { y: "100%", height: m(P) },
initial: !1,
style: He({ y: m(S), height: m(w) }),
"data-vsbs-shadow": !e.blocking,
"data-vsbs-sheet-show": m(b),
"aria-modal": "true",
"data-vsbs-sheet": "",
tabindex: "-1",
onTouchstart: ke,
onTouchend: xe
}, {
default: E(() => [
$(m(A), {
ref_key: "sheetHeader",
ref: p,
"data-vsbs-header": "",
onPanStart: pe,
onPan: he,
onPanEnd: ee,
onTouchmove: ie,
class: oe(e.headerClass)
}, {
default: E(() => [
le(e.$slots, "header", {}, void 0, !0)
]),
_: 3
}, 8, ["class"]),
ne("div", {
ref_key: "sheetScroll",
ref: h,
"data-vsbs-scroll": "",
onScrollend: Oe
}, [
$(m(A), {
ref_key: "sheetContentWrapper",
ref: O,
"data-vsbs-content-wrapper": "",
onPanStart: we,
onPan: Te,
onPanEnd: ee,
onTouchmove: ve
}, {
default: E(() => [
ne("div", {
ref_key: "sheetContent",
ref: D,
"data-vsbs-content": "",
class: oe(e.contentClass)
}, [
le(e.$slots, "default", {}, void 0, !0)
], 2)
]),
_: 3
}, 512)
], 544),
$(m(A), {
ref_key: "sheetFooter",
ref: f,
"data-vsbs-footer": "",
onPanStart: pe,
onPan: he,
onPanEnd: ee,
onTouchmove: ie,
class: oe(e.footerClass)
}, {
default: E(() => [
le(e.$slots, "footer", {}, void 0, !0)
]),
_: 3
}, 8, ["class"])
]),
_: 3
}, 8, ["exit", "style", "data-vsbs-shadow", "data-vsbs-sheet-show"])) : ge("", !0)
]),
_: 3
})
])
], 8, ["to", "defer"]));
}
}), Ke = (n, a) => {
const o = n.__vccOpts || n;
for (const [t, s] of a)
o[t] = s;
return o;
}, Ze = /* @__PURE__ */ Ke(Ge, [["__scopeId", "data-v-ccadc172"]]);
export {
Ze as default
};