@blossom-carousel/react
Version:
A native-scroll-first carousel component for React.
427 lines (426 loc) • 11.8 kB
JavaScript
import { Children as e, createContext as t, forwardRef as n, isValidElement as r, useContext as i, useEffect as a, useImperativeHandle as o, useRef as s, useState as c } from "react";
import { jsx as l } from "react/jsx-runtime";
//#region src/countMarkedSlides.ts
var u = "data-blossom-slide";
function d(e) {
return !!e && typeof e == "object" && u in e;
}
function f(t) {
let n = 0;
return e.forEach(t, (e) => {
if (!r(e)) return;
d(e.props) && n++;
let t = e.props?.children;
t !== void 0 && (n += f(t));
}), n;
}
function p(e) {
return f(e);
}
//#endregion
//#region src/slideRegistry.ts
var m = /* @__PURE__ */ new Map();
function h(e, t) {
m.set(e, t);
}
function g(e) {
return e ? m.get(e) ?? 0 : 0;
}
//#endregion
//#region src/BlossomCarousel.tsx
var _ = n(({ children: e, as: t = "div", repeat: n = !1, load: r = "conditional", ...i }, c) => {
let u = t, d = s(null), f = s(null);
return i.id && h(i.id, p(e)), a(() => {
if (!d.current || !window.matchMedia("(hover: hover) and (pointer: fine)").matches && r !== "always") return;
let e = !1;
return import("@blossom-carousel/core").then(({ Blossom: t }) => {
if (e || !d.current) return;
let r = t(d.current, { repeat: n });
f.current = r, r.init();
}), () => {
e = !0, f.current?.destroy(), f.current = null;
};
}, [n, r]), o(c, () => ({
prev: (e) => f.current?.prev(e),
next: (e) => f.current?.next(e),
element: d.current
}), []), /* @__PURE__ */ l(u, {
ref: d,
"blossom-carousel": "true",
...i,
children: e
});
});
_.displayName = "BlossomCarousel";
//#endregion
//#region ../navigation/src/snap.ts
function v(e) {
return getComputedStyle(e).direction === "rtl";
}
function y(e, t) {
return t ? -e.scrollLeft : e.scrollLeft;
}
function b(e) {
if (!e || e === "none") return "none";
let t = e.trim().split(/\s+/), n = t.length === 2 ? t[1] : t[0];
return n === "center" || n === "end" ? n : "start";
}
function x(e) {
let t = b(e);
return t === "none" ? "start" : t;
}
function S(e, t, n, r, i, a, o, s = getComputedStyle(t)) {
let c = t.getBoundingClientRect(), l = (o ? r.right - c.right : c.left - r.left) + y(e, o), u = parseFloat(s.scrollMarginInlineStart) || 0, d = parseFloat(s.scrollMarginInlineEnd) || 0, f = l - u, p = l + t.clientWidth + d, m = e.clientWidth;
switch (n) {
case "end": return p - m + a;
case "center": return (f + p) / 2 - m / 2;
default: return f - i;
}
}
function C(e) {
return e.map((e) => ({
el: e,
align: x(getComputedStyle(e).scrollSnapAlign)
}));
}
function w(e) {
let t = e.getBoundingClientRect(), n = getComputedStyle(e), r = n.direction === "rtl", i = parseFloat(n.scrollPaddingInlineStart) || 0, a = parseFloat(n.scrollPaddingInlineEnd) || 0, o = Math.max(e.scrollWidth - e.clientWidth, 0), s = document.createTreeWalker(e, NodeFilter.SHOW_ELEMENT), c = [], l = s.nextNode();
for (; l;) {
let n = l, u = getComputedStyle(n), d = b(u.scrollSnapAlign);
if (d !== "none") {
let s = S(e, n, d, t, i, a, r, u);
c.push({
el: n,
align: d,
x: Math.min(Math.max(s, 0), o)
});
}
l = s.nextNode();
}
return c.sort((e, t) => e.x - t.x), c.reduce((e, t) => ((e.length === 0 || Math.abs(e[e.length - 1].x - t.x) > 1) && e.push(t), e), []);
}
//#endregion
//#region ../navigation/src/cache.ts
var T = /* @__PURE__ */ new WeakMap();
function E(e, t) {
T.set(e, t);
}
function D(e) {
return T.get(e);
}
function ee(e) {
T.delete(e);
}
//#endregion
//#region ../navigation/src/scroll.ts
var te = .85, O = 1;
function k(e, t) {
let n = Math.max(e.scrollWidth - e.clientWidth, 0), r = Math.abs(e.scrollLeft);
return t === "prev" ? r > O : r < n - O;
}
function A(e, t, n) {
if (n === "next") {
for (let n = 0; n < e.length; n++) if (e[n].x > t + O && e[n].el.isConnected) return e[n];
} else for (let n = e.length - 1; n >= 0; n--) if (e[n].x < t - O && e[n].el.isConnected) return e[n];
return null;
}
function j(e, t) {
let n = D(e), r = n?.pages ?? w(e), i = n?.rtl ?? v(e);
if (r.length) {
let n = A(r, y(e, i), t);
if (!n) return;
n.el.scrollIntoView({
block: "nearest",
inline: n.align,
behavior: "smooth"
});
return;
}
let a = e.clientWidth * te * (t === "next" ? 1 : -1);
e.scrollBy({
left: i ? -a : a,
behavior: "smooth"
});
}
var M = "[data-blossom-slide]", N = 1;
function P(e) {
return Array.from(e.querySelectorAll(M));
}
function F(e, t) {
let n = e.clientWidth, r = e.scrollWidth - n, i = e.getBoundingClientRect(), a = getComputedStyle(e), o = a.direction === "rtl", s = parseFloat(a.scrollPaddingInlineStart) || 0, c = parseFloat(a.scrollPaddingInlineEnd) || 0, l = t.map((t) => {
let n = getComputedStyle(t), r = b(n.scrollSnapAlign);
return S(e, t, r === "none" ? "start" : r, i, s, c, o, n);
});
if (r > 0) {
let e = Math.min(n / 8, r / 2), t = l.filter((t) => t < e);
if (t.length > 0) {
let n = Math.min(...t), r = e - n;
if (r > 0) for (let t = 0; t < l.length; t++) l[t] < e && (l[t] = (l[t] - n) / r * e);
}
let i = r - e, a = l.filter((e) => e > i);
if (a.length > 0) {
let t = Math.max(...a) - i;
if (t > 0) for (let n = 0; n < l.length; n++) l[n] > i && (l[n] = (l[n] - i) / t * e + i);
}
}
return l;
}
function ne(e, t, n, r = [...e].sort((e, t) => e - t)) {
if (e.length === 0) return -1;
if (e.length === 1) return 0;
let i = n / 2, a = null;
for (let e = 0; e < r.length; e++) {
let n = r[e], o = e > 0 ? r[e - 1] : -Infinity;
(n <= t + N || o < t - i && n < t + i) && (a = n);
}
a === null && (a = r[0]);
for (let t = 0; t < e.length; t++) if (Math.abs(e[t] - a) <= N) return t;
return 0;
}
function re(e, t, n) {
if (t.length === 0) return -1;
if (t.length === 1) return 0;
let r = e.clientWidth;
if (e.scrollWidth - r <= 0) return 0;
let i = n?.rtl ?? v(e);
return ne(n?.activePositions ?? F(e, t), y(e, i), r, n?.sortedActivePositions);
}
//#endregion
//#region ../navigation/src/commands.ts
var I = "--blossom-", L = {
prev: `${I}prev`,
next: `${I}next`,
gotoPrefix: `${I}goto-`
}, R = Symbol.for("blossom-carousel.navigation.registry");
function z(e, t) {
if (t === L.prev) j(e, "prev");
else if (t === L.next) j(e, "next");
else if (t.startsWith(L.gotoPrefix)) {
let n = Number.parseInt(t.slice(L.gotoPrefix.length), 10);
if (Number.isNaN(n) || n < 0) return;
let r = D(e)?.markers[n], i = r?.el.isConnected ? r : void 0, a = i?.el ?? P(e)[n];
if (!a) return;
let o = i?.align ?? x(getComputedStyle(a).scrollSnapAlign);
a.scrollIntoView({
block: "nearest",
inline: o,
behavior: "smooth"
});
}
}
function B(e) {
U();
let t = e, n = t[R];
if (n) return n.count++, () => V(t);
let r = (t) => {
let n = t.command ?? t.detail?.command;
typeof n == "string" && n.startsWith(I) && z(e, n);
};
return e.addEventListener("command", r), t[R] = {
count: 1,
handler: r
}, () => V(t);
}
function V(e) {
let t = e[R];
t && (t.count--, t.count <= 0 && (e.removeEventListener("command", t.handler), delete e[R]));
}
var H = !1;
function U() {
H || typeof document > "u" || (H = !0, !(typeof HTMLButtonElement < "u" && "commandForElement" in HTMLButtonElement.prototype) && document.addEventListener("click", (e) => {
let t = e.target?.closest?.("button[commandfor]");
if (!t) return;
let n = t.getAttribute("commandfor"), r = t.getAttribute("command");
!n || !r || document.getElementById(n)?.dispatchEvent(new CustomEvent("command", {
bubbles: !1,
detail: { command: r }
}));
}));
}
//#endregion
//#region ../navigation/src/observe.ts
function W(e) {
let t = D(e), n = t?.targets ?? P(e);
return {
count: n.length,
activeIndex: re(e, n, t),
canPrev: k(e, "prev"),
canNext: k(e, "next")
};
}
function G(e) {
let t = P(e), n = F(e, t);
E(e, {
targets: t,
markers: C(t),
pages: w(e),
activePositions: n,
sortedActivePositions: [...n].sort((e, t) => e - t),
rtl: v(e)
});
}
var K = Symbol.for("blossom-carousel.navigation.observers");
function q(e) {
let t = /* @__PURE__ */ new Set(), n = 0, r = !0, i = () => {
cancelAnimationFrame(n), n = requestAnimationFrame(() => {
r && (r = !1, G(e));
let n = W(e);
for (let e of t) e(n);
});
}, a = () => i(), o = () => {
r = !0, i();
};
e.addEventListener("scroll", a, { passive: !0 }), e.addEventListener("scrollend", a, { passive: !0 });
let s = new ResizeObserver(o);
s.observe(e);
let c = new MutationObserver(o);
return c.observe(e, {
childList: !0,
subtree: !0
}), {
subscribers: t,
flush: i,
teardown: () => {
cancelAnimationFrame(n), e.removeEventListener("scroll", a), e.removeEventListener("scrollend", a), s.disconnect(), c.disconnect(), ee(e);
}
};
}
function J(e, t) {
let n = e, r = n[K] ?? q(e);
return n[K] = r, r.subscribers.add(t), r.flush(), () => {
let e = n[K];
e && (e.subscribers.delete(t), e.subscribers.size === 0 && (e.teardown(), delete n[K]));
};
}
//#endregion
//#region src/useNavigation.ts
var Y = {
activeIndex: -1,
count: 0,
canPrev: !1,
canNext: !1
};
function X(e) {
return {
...Y,
count: g(e)
};
}
function Z(e) {
let [t, n] = c(() => X(e));
return a(() => {
if (n(X(e)), !e) return;
let t = document.getElementById(e);
if (!t) return;
let r = B(t), i = J(t, n);
return () => {
r(), i();
};
}, [e]), t;
}
//#endregion
//#region src/BlossomPrev.tsx
function ie({ for: e, children: t = "Previous", disabled: n, ...r }) {
let i = Z(e);
return /* @__PURE__ */ l("button", {
"blossom-prev": "",
type: "button",
command: L.prev,
commandfor: e,
disabled: n ?? !i.canPrev,
"aria-controls": e,
"aria-label": "Previous slide",
...r,
children: t
});
}
//#endregion
//#region src/BlossomNext.tsx
function ae({ for: e, children: t = "Next", disabled: n, ...r }) {
let i = Z(e);
return /* @__PURE__ */ l("button", {
"blossom-next": "",
type: "button",
command: L.next,
commandfor: e,
disabled: n ?? !i.canNext,
"aria-controls": e,
"aria-label": "Next slide",
...r,
children: t
});
}
//#endregion
//#region src/dotContext.ts
var Q = t(null);
//#endregion
//#region src/DotScope.tsx
function oe({ index: e, active: t, forId: n, children: r }) {
return /* @__PURE__ */ l(Q.Provider, {
value: {
index: e,
active: t,
forId: n
},
children: r
});
}
//#endregion
//#region src/BlossomDots.tsx
var $ = L.gotoPrefix;
function se(e) {
if (!e) return !1;
let t = e({
index: 0,
active: !1
});
return t != null && typeof t != "boolean";
}
function ce({ for: e, children: t, ...n }) {
let r = Z(e), i = se(t);
return /* @__PURE__ */ l("div", {
"data-blossom-dots": !0,
role: "group",
"aria-label": "Choose slide to display",
...n,
children: i ? Array.from({ length: r.count }, (n, i) => {
let a = r.activeIndex === i;
return /* @__PURE__ */ l(oe, {
index: i,
active: a,
forId: e,
children: t({
index: i,
active: a
})
}, i);
}) : Array.from({ length: r.count }, (t, n) => /* @__PURE__ */ l("button", {
type: "button",
"data-blossom-dot": "",
command: `${$}${n}`,
commandfor: e,
"aria-controls": e,
"aria-current": r.activeIndex === n,
"aria-label": `Go to slide ${n + 1}`,
children: /* @__PURE__ */ l("span", { "data-blossom-dot-marker": "" })
}, n))
});
}
//#endregion
//#region src/BlossomDot.tsx
function le({ children: e, ...t }) {
let n = i(Q);
return n || console.warn("[BlossomDot] Must be used inside a BlossomDots slot."), /* @__PURE__ */ l("button", {
type: "button",
"data-blossom-dot": !0,
command: `${L.gotoPrefix}${n?.index ?? 0}`,
commandfor: n?.forId ?? "",
"aria-controls": n?.forId,
"aria-current": n?.active ?? !1,
"aria-label": n ? `Go to slide ${n.index + 1}` : void 0,
...t,
children: e
});
}
//#endregion
export { _ as BlossomCarousel, le as BlossomDot, ce as BlossomDots, ae as BlossomNext, ie as BlossomPrev };