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