@douxcode/vue-spring-bottom-sheet
Version:
Modern and Performant Bottom Sheet for Vue.js
418 lines (417 loc) • 14.8 kB
JavaScript
import { ref as P, 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 be, Motion as A } from "motion-v";
import { useVModel as Ve, useWindowSize as Fe, useElementBounding as Q, useScrollLock as ye } 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 = P(0), s = _(() => n.value.map((p) => typeof p == "string" ? G(p, o.value) : p)), y = _(() => Math.min(...s.value)), r = _(() => Math.max(...s.value)), h = _(() => {
const p = s.value.reduce(
(g, M) => Math.abs(M - a.value) < Math.abs(g - a.value) ? M : g
);
return s.value.indexOf(p);
});
return {
currentSnapPointIndex: t,
flattenedSnapPoints: s,
minSnapPoint: y,
maxSnapPoint: r,
closestSnapPointIndex: h
};
}
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: y = ze }) {
let r, h, p, g, M = () => {
let i = p;
i !== void 0 && (p = void 0, n(i), s !== void 0 && (h = setTimeout(D, s)));
}, D = () => {
clearTimeout(h), h = void 0, r === void 0 && M();
}, H = () => {
clearTimeout(r), r = void 0, g = void 0, h === void 0 && M();
};
return { call: (...i) => {
let c = r === void 0 && h === void 0;
if ((a !== "start" || c) && (p = y(p, ...i)), !(r === void 0 && !c)) {
if (o !== void 0 || t !== void 0 || s === void 0) {
clearTimeout(r);
let C = Date.now();
g ?? (g = C);
let W = t === void 0 ? o ?? 0 : Math.min(o ?? t, t - (C - g));
r = setTimeout(H, W);
}
a !== "end" && c && M();
}
}, cancel: () => {
clearTimeout(r), r = void 0, g = void 0, clearTimeout(h), h = void 0, p = void 0;
}, flush: () => {
H(), D();
}, get isIdle() {
return r === void 0 && h === void 0;
} };
}
var ze = () => "";
function b(...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 },
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, y = Ve(t, "modelValue", s, {
passive: !0
});
q(y, (e) => {
e && X();
}), Ie(() => {
y.value && X();
});
const r = P(), h = P(null), p = P(null), g = P(null), M = P(null), D = P(null), H = P(null), i = P(t.expandOnContentDrag), { height: c } = Fe(), { height: C } = Q(r), { height: W } = Q(h), { height: se } = Q(D), { height: re } = Q(p), K = _({
get() {
return b(
Math.ceil(se.value + W.value + re.value),
{
max: c.value
}
);
},
set(e) {
[W.value, se.value, re.value] = e;
}
}), l = P(0), v = P(0), k = me(0), S = me(0), { snapPoints: Pe } = Ee(t), d = _(() => Pe.value ?? [K.value]), {
flattenedSnapPoints: U,
currentSnapPointIndex: I,
closestSnapPointIndex: V,
minSnapPoint: O,
maxSnapPoint: F
} = $e(d, l, c);
let w;
const j = ye(document.body), z = ye(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();
}, Ce = () => {
t.canBackdropClose && L();
};
let J = !1;
const X = async () => {
if (J) return;
y.value = !0, J = !0, s("opening-started"), t.blocking && (j.value = !0, z.value = !0), await N();
const e = r.value.$el;
C.value = e.getBoundingClientRect().height;
const u = e.querySelector("[data-vsbs-content]"), f = e.querySelector("[data-vsbs-header]"), T = e.querySelector("[data-vsbs-footer]");
if (K.value = [
f.getBoundingClientRect().height,
u.getBoundingClientRect().height,
T.getBoundingClientRect().height
], await N(), I.value = U.value.findIndex(
(x) => x === O.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 = b(d.value[x], {
max: c.value
}) : R = G(d.value[x], c.value), l.value = R;
} else
l.value = b(O.value, {
max: c.value
});
v.value = l.value, k.jump(l.value), S.jump(l.value), requestAnimationFrame(() => {
w = B(k, l.value, {
duration: t.duration / 1e3,
ease: "easeInOut"
}), w = 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 || (y.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 = b(d.value[e], {
max: c.value
}) : u = G(d.value[e], c.value), l.value = u, w = B(k, 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 = C.value, v.value = S.get(), k.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 <= O.value && (l.value = O.value, v.value += u.delta.y, S.set(
t.canSwipeClose ? b(v.value, { min: 0 }) : b(ue(v.value, -C.value, 0, 0.5), {
min: 0
})
)), k.set(
b(ue(l.value, 0, F.value, 0.25), {
min: 0,
max: c.value
})
), fe(u.delta.y));
}, ee = () => {
v.value = t.canSwipeClose ? [0, l.value].reduce(
(u, f) => Math.abs(f - v.value) < Math.abs(u - v.value) ? f : u
) : 0, w = 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 = b(d.value[V.value], {
max: c.value
}) : e = G(
d.value[V.value],
c.value
), l.value = e, w = B(k, l.value, {
duration: t.duration / 1e3,
ease: "easeInOut",
onComplete: () => s(
"snapped",
d.value.indexOf(d.value[V.value])
)
}), w = B(S, 0, {
duration: t.duration / 1e3,
ease: "easeInOut"
});
}, ke = (e, u) => {
if (l.value = C.value, v.value = S.get(), w && w.stop(), !g.value) return;
const f = g.value.scrollTop === 0, T = 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 && f && T && (i.value = !0), S.get() === 0 && f && !T && (i.value = !1);
} else {
if (!t.expandOnContentDrag) {
i.value = !1;
return;
}
i.value = !0, R && (T && f && (i.value = !0), !T && f && (i.value = !1), f || (i.value = !1));
}
}, we = 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 <= O.value && (l.value = O.value, i.value && t.expandOnContentDrag && (v.value += u.delta.y), v.value = b(v.value, { min: 0, max: O.value }), S.set(
t.canSwipeClose ? b(v.value, { min: 0 }) : b(ue(v.value, -C.value, 0, 0.5), {
min: 0
})
)), l.value > F.value && (l.value = F.value), l.value = b(l.value, { max: c.value }), U.value.length === 1 || l.value === F.value && (i.value = !1), k.set(l.value), fe(u.delta.y);
}, Te = () => {
t.blocking || (j.value = !0, z.value = !0);
}, xe = () => {
t.blocking || (j.value = !1, z.value = !1);
}, Me = () => {
if (!g.value) return;
const e = g.value.scrollTop === 0;
i.value = e;
}, Oe = je((e) => ce(e), {
minQuietPeriodMs: t.duration,
reducer: (e, u) => u
});
return q(d, (e, u) => {
if (y.value === !1 || !e || !u) return;
const f = e[I.value], T = u[I.value];
typeof f != "string" && typeof T != "string" && (l.value = b(f, {
max: c.value
}), f !== T && (w = B(k, l.value, {
duration: t.duration / 1e3,
ease: "easeInOut"
})));
}), q(c, () => {
Oe.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(be), null, {
default: E(() => [
m(y) && e.blocking ? (ae(), te(m(A), {
key: 0,
ref_key: "backdrop",
ref: H,
"data-vsbs-backdrop": "",
onClick: u[0] || (u[0] = (f) => Ce()),
transition: {
ease: "easeInOut",
duration: e.duration / 1e3
},
initial: { opacity: 0 },
animate: { opacity: 1 },
exit: { opacity: 0 }
}, null, 8, ["transition"])) : ge("", !0)
]),
_: 1
}),
$(m(be), null, {
default: E(() => [
m(y) ? (ae(), te(m(A), {
key: 0,
ref_key: "sheet",
ref: r,
exit: { y: "100%", height: m(C) },
initial: !1,
style: He({ y: m(S), height: m(k) }),
"data-vsbs-shadow": !e.blocking,
"data-vsbs-sheet-show": m(y),
"aria-modal": "true",
"data-vsbs-sheet": "",
tabindex: "-1",
onTouchstart: Te,
onTouchend: xe
}, {
default: E(() => [
$(m(A), {
ref_key: "sheetHeader",
ref: h,
"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: g,
"data-vsbs-scroll": "",
onScrollend: Me
}, [
$(m(A), {
ref_key: "sheetContentWrapper",
ref: M,
"data-vsbs-content-wrapper": "",
onPanStart: ke,
onPan: we,
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: p,
"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-3c41a73f"]]);
export {
Ze as default
};