UNPKG

@blossom-carousel/react

Version:

A native-scroll-first carousel component for React.

427 lines (426 loc) 11.8 kB
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 };