UNPKG

vue-directional-carousel

Version:

This is a Vue 3 component that allows you to set the direction of image movement not only vertically and horizontally but also in different directions (left, right, up, down).

498 lines (497 loc) 18.8 kB
import { defineComponent as D, ref as h, computed as B, openBlock as f, createElementBlock as p, normalizeStyle as y, Fragment as q, renderList as F, createElementVNode as E, unref as u, watch as I, onMounted as Y, onBeforeUnmount as K, onScopeDispose as J, mergeModels as AA, useModel as eA, createBlock as z, createCommentVNode as R, renderSlot as M, mergeProps as tA } from "vue"; const nA = (A) => { var o; const a = []; (o = A.items) != null && o.length || a.push({ prop: "items", message: "At least one item is required" }), A.interval != null && A.interval < 0 && a.push({ prop: "interval", message: "Interval must be a non-negative number" }), A.duration != null && A.duration <= 0 && a.push({ prop: "duration", message: "Duration must be greater than 0" }); const n = /^(\d+(\.\d+)?)(px|%|rem|em|vh|vw)$/; return A.width && !n.test(A.width) && a.push({ prop: "width", message: "Width must be a valid CSS dimension (e.g., 100%, 200px)" }), A.height && !n.test(A.height) && a.push({ prop: "height", message: "Height must be a valid CSS dimension (e.g., 300px, 50vh)" }), a; }, oA = { style: { padding: "0px 4px 0px 4px" } }, lA = ["onClick", "aria-label", "aria-current"], rA = /* @__PURE__ */ D({ __name: "DotButtons", props: { currentIndex: { default: 0 }, itemCount: { default: 0 }, showDots: { type: Boolean, default: !1 } }, emits: ["click-dot-button"], setup(A, { emit: a }) { const n = a, o = A, d = h({ display: "flex", alignItems: "center", justifyContent: "center", flexWrap: "wrap" }), s = (i) => { n("click-dot-button", i - 1); }, l = B( () => new Array(o.itemCount).fill({ border: "none", borderRadius: "50%", backgroundColor: "gray", cursor: "pointer" }).map((i, e) => { const r = e === (o.itemCount > o.currentIndex ? o.currentIndex : 0); return { ...i, opacity: r ? "1" : "0.5", height: r ? "8px" : "6px", paddingInline: r ? "4px" : "3px" }; }) ); return (i, e) => (f(), p("div", { style: y(d.value) }, [ (f(!0), p(q, null, F(i.itemCount, (r) => (f(), p("div", oA, [ E("button", { type: "button", style: y(l.value[r - 1]), onClick: (g) => s(r), "aria-label": `Go to slide ${r}`, "aria-current": r - 1 === i.currentIndex }, null, 12, lA) ]))), 256)) ], 4)); } }); function j() { const A = h({ zIndex: 1, display: "flex", justifyContent: "center", alignItems: "center", padding: "5px" }), a = h({ height: "20px", border: "none", color: "silver", borderRadius: "10px" }); return { buttonWrapperStyle: A, buttonStyle: a, onMouseDownButton: () => { a.value.color = "gray"; }, onMouseUpButton: () => { a.value.color = "silver"; } }; } const U = "data:image/png;base64,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pr/pb/qb/qa/6W/6m/42U1utv/7666+//vrrr7/++uuvv/7666+//vrrr7/++uuvv/7666+//vrrr7/++uuvv/7666+//vrrr7/++uuvv/7662+vZv8J/fXXX3/99ddff/31119//fXXX3/99ddff/2buDX666+//vrrr7/++uuvv/7666+//vrrr7/++uuvv/7666+//vrrr7/++uuvv/7666+//vrrr7/++uuvv/7666+//qa/6W/6m/6mv+lv+pv+pr/pb/rbjG2t/vrrr7/++uuvv/7666+//vrrr7/++uuvv/7666+//vrrr7/++uuvv/7666+//rOz/z399ddff/31119//fXXX3/99ddff/31119//fXXX3/99ddff/31119//fXXX3/9Z+PW6a+//vrrr7/++uuvv/7666+//vrrr7/++uuvv/7666+//vrrr7/++uuvv/7666+//vrrr7/++uuvv/7666+//vqb/qa/6W/6m/6mv+lv+pv+pr/N6Lr1119//fXXX/+4ef5n9pbc0T9586/qH73T+kfvoP7R63uif/IW39c/esf1j17XlP5+AtA/dque6h+9i/pHr1v/7B3TP3u39I/eCv2z96X+2WvpH723pvSP3hb9s7dH/+wd1j97Q/pn70f9szesf/bG9M/ebf2zd03/7F3SP3sn9M9ev/7Z269/9rbrn71l+odvVP/sDeifvV79s9cxrn/2TumfvR36h78G3NA/e9v0z97cEf2zt0n/8LX0z16P/uHr1z/8+8Dz+mdvwaj+2Vt+V//sbXisf/b2ti1gss/VGvV+0ER7/cdWulmz1tXWr4Vai1ysaVvYxlNjhzrdq3mbd2Sa+R/tc6xmbv20Hhhwwct/c7f1+ktf/XtcqdGvAztf+PjgkY1O1PR17j43+fz64ye995OxNzZ/+9ez9UcHejtcJmgffPrV4Nkrfzy4f7N15uiBXUtdZCb2HyDq9i+SDCuqAAAAAElFTkSuQmCC", aA = ["src"], uA = /* @__PURE__ */ D({ __name: "PrevButton", emits: ["click-prev-button"], setup(A, { emit: a }) { const n = a, { buttonWrapperStyle: o, buttonStyle: d, onMouseDownButton: s } = j(), l = () => { n("click-prev-button"); }; return (i, e) => (f(), p("div", { style: y(u(o)) }, [ E("button", { type: "button", class: "prev-button", style: y(u(d)), onClick: l, onMousedown: e[0] || (e[0] = //@ts-ignore (...r) => u(s) && u(s)(...r)), "aria-label": "Previous slide" }, [ E("img", { alt: "left arrow", src: u(U), style: { width: "10px", borderRadius: "50%", transform: "scaleX(-1)" } }, null, 8, aA) ], 36) ], 4)); } }), sA = ["src"], iA = /* @__PURE__ */ D({ __name: "NextButton", emits: ["click-next-button"], setup(A, { emit: a }) { const n = a, { buttonWrapperStyle: o, buttonStyle: d, onMouseUpButton: s } = j(), l = () => { n("click-next-button"); }; return (i, e) => (f(), p("div", { style: y(u(o)) }, [ E("button", { type: "button", class: "next-button", style: y(u(d)), onClick: l, onMouseup: e[0] || (e[0] = //@ts-ignore (...r) => u(s) && u(s)(...r)), "aria-label": "Next slide" }, [ E("img", { alt: "left arrow", src: u(U), style: { width: "10px", borderRadius: "3px" } }, null, 8, sA) ], 36) ], 4)); } }), vA = 1e3, dA = "translate"; function cA(A, a, n, o, d) { const s = h(!1), l = B(() => { const m = A.direction; return { isHorizontal: ["left", "right"].includes(m), isReverse: ["up", "left"].includes(m), flexDirection: { left: "row", right: "row-reverse", up: "column", down: "column-reverse" }[m] }; }), i = B( () => l.value.isHorizontal ? o.value : d.value ), e = B(() => { const m = a.value.length, C = n.value % m; return l.value.isReverse ? C : m - (C + 1 % m); }), r = B(() => e.value * i.value), g = B(() => { const m = l.value.isHorizontal ? "X" : "Y"; return `${dA}${m}(-${r.value}px)`; }), Q = h(`transform ${(A.duration ?? vA) / 1e3}s ease`), w = h(Q.value); return { itemsTranslate: g, defaultTransition: Q, transition: w, isTransitioning: s, directionProps: l }; } function fA(A, a, n, o, d, s, l) { const i = h(null), e = h(!1), r = () => { o.value = ++o.value % n.value.length, s.value = d.value; }, g = () => { l.value || (o.value >= n.value.length - 1 ? (l.value = !0, s.value = "", o.value = 0, requestAnimationFrame(() => { requestAnimationFrame(() => { s.value = d.value, o.value = 1, setTimeout(() => { l.value = !1; }, A.duration || 1e3); }); })) : r()); }, Q = () => { i.value !== null && (window.clearInterval(i.value), i.value = null); }, w = () => { Q(), A.interval && A.items.length > 1 && !e.value && (i.value = window.setInterval(g, A.interval)); }, m = () => { g(), A.interval && A.items.length > 1 && w(); }, C = B( () => (o.value - 1 + n.value.length) % n.value.length ), b = () => { l.value || (o.value === 0 ? (l.value = !0, s.value = "", o.value = C.value, setTimeout(() => { o.value = C.value, s.value = d.value, setTimeout(() => { l.value = !1; }, A.duration || 1e3); }, 100)) : o.value = C.value, A.interval && A.items.length > 1 && w()); }, S = (H) => { l.value || (o.value = H, A.interval && w()); }; I( () => [ A.interval, A.direction, A.items.length, A.pauseAutoplayOnHover ], () => { w(); }, { deep: !0 } ); const P = () => { A.pauseAutoplayOnHover && (e.value = !0, Q()); }, X = () => { A.pauseAutoplayOnHover && A.items.length > 1 && (e.value = !1, w()); }; return Y(() => { s.value = "", requestAnimationFrame(() => { requestAnimationFrame(() => { s.value = d.value; }); }), A.interval && A.items.length > 1 && w(); }), K(() => { Q(); }), J(() => { Q(); }), { clickNext: m, clickPrev: b, clickDot: S, pauseSlide: P, resumeSlide: X }; } function gA(A, a) { const n = h(/* @__PURE__ */ new Map()), o = h(/* @__PURE__ */ new Map()), d = h(/* @__PURE__ */ new Set()), s = (e) => { n.value.set(e, !1), o.value.set(e, !1), d.value.add(e); }, l = (e) => { n.value.set(e, !1), o.value.set(e, !0); }, i = (e) => { if (!e || d.value.has(e)) return; n.value.set(e, !0); const r = new Image(); r.onload = () => s(e), r.onerror = () => l(e), r.src = e; }; return A && a && I([A, a], () => { const e = a.value[A.value], r = (A.value + 1) % a.value.length, g = a.value[r]; e != null && e.src && i(e.src), g != null && g.src && i(g.src); }, { immediate: !0 }), { loadingStates: n, errorStates: o, loadedImages: d, handleImageLoad: s, handleImageError: l, preloadImage: i }; } const mA = ["aria-hidden"], BA = { class: "carousel-image-container", style: { width: "100%", height: "100%" } }, hA = { key: 0, class: "carousel-image-loading" }, EA = { key: 1, class: "carousel-image-error" }, pA = ["src", "alt", "onLoad", "onError"], QA = /* @__PURE__ */ D({ __name: "DirectionalCarousel", props: /* @__PURE__ */ AA({ width: { default: "100%" }, height: { default: "300px" }, items: {}, direction: { default: "right" }, duration: { default: 1e3 }, showPrev: { type: Boolean, default: !0 }, showNext: { type: Boolean, default: !0 }, showDots: { type: Boolean, default: !0 }, interval: { default: 0 }, arrowOptions: {}, pauseAutoplayOnHover: { type: Boolean, default: !1 } }, { currentIndex: { default: 0 }, currentIndexModifiers: {} }), emits: ["update:currentIndex"], setup(A, { expose: a }) { const n = A; I( () => ({ items: n.items, interval: n.interval, duration: n.duration, width: n.width, height: n.height }), () => { const t = nA(n); if (t.length > 0 && (t.forEach((v) => { console.error(`DirectionalCarousel: ${v.prop} - ${v.message}`); }), t.some((v) => v.prop === "items"))) throw new Error("DirectionalCarousel: Invalid props - check console for details"); }, { immediate: !0 } ); const d = h(null), s = B( () => n.items.map((t) => typeof t == "string" ? { src: t } : t) ), l = B(() => n.items.length), i = B(() => [...s.value, s.value[0]]), e = eA(A, "currentIndex"), r = h(e.value), g = B(() => { var t; return ((t = d.value) == null ? void 0 : t.offsetWidth) ?? 0; }), Q = B(() => { var t; return ((t = d.value) == null ? void 0 : t.offsetHeight) ?? 0; }), { itemsTranslate: w, defaultTransition: m, transition: C, isTransitioning: b, directionProps: S } = cA(n, i, r, g, Q), { clickNext: P, clickPrev: X, clickDot: H, pauseSlide: k, resumeSlide: x } = fA( n, s, i, r, m, C, b ); I(r, (t) => { if (l.value === 0) return; const v = t % l.value; e.value !== v && (e.value = v); }), I(e, (t) => { if (typeof t != "number" || !Number.isInteger(t) || t < 0 || t >= l.value) { console.warn( `DirectionalCarousel: invalid currentIndex "${t}". Must be an integer in [0, ${l.value - 1}].` ); const v = r.value % l.value; e.value !== v && (e.value = v); return; } b.value || r.value % l.value !== t && (r.value = t); }); const $ = B(() => ({ overflow: "hidden", width: g.value + "px", height: Q.value + "px", display: "flex", justifyContent: "center", alignItems: "center" })), { loadingStates: V, errorStates: O, loadedImages: W, preloadImage: yA } = gA(e, s), _ = (t) => { V.value.set(t, !1), W.value.add(t); }, G = (t) => { V.value.set(t, !1), O.value.set(t, !0); }; return a({ currentIndex: e }), (t, v) => { var L, T; return f(), p("div", { style: y({ overflow: "hidden", display: "flex", flexDirection: "column", width: n.width, height: n.height }) }, [ E("div", { class: "slider", style: { overflow: "hidden", position: "relative", display: "flex", width: "100%", height: "100%" }, onMouseenter: v[0] || (v[0] = //@ts-ignore (...c) => u(k) && u(k)(...c)), onFocusin: v[1] || (v[1] = //@ts-ignore (...c) => u(k) && u(k)(...c)), onMouseleave: v[2] || (v[2] = //@ts-ignore (...c) => u(x) && u(x)(...c)), onFocusout: v[3] || (v[3] = //@ts-ignore (...c) => u(x) && u(x)(...c)) }, [ (L = t.arrowOptions) != null && L.prev.show || t.showPrev ? (f(), z(uA, { key: 0, onClickPrevButton: u(X) }, null, 8, ["onClickPrevButton"])) : R("", !0), E("div", { ref_key: "carouselContainer", ref: d, style: { overflow: "hidden", position: "relative", flex: 1 } }, [ E("div", { class: "wrapper-carousel-items", style: y({ display: "inline-flex", position: "relative", flexDirection: u(S).flexDirection, transform: u(w), transition: u(C), pointerEvents: u(b) ? "none" : "auto" }) }, [ (f(!0), p(q, null, F(i.value, (c, N) => (f(), p("div", { key: N, style: y($.value), class: "carousel-item", "aria-hidden": r.value !== N }, [ M(t.$slots, "item", tA({ ref_for: !0 }, c, { style: { width: t.width, height: t.height } }), () => [ E("div", BA, [ u(V).get(c.src) ? (f(), p("div", hA, [ M(t.$slots, "loading", {}, () => [ v[4] || (v[4] = E("div", { class: "loading-spinner" }, null, -1)) ], !0) ])) : u(O).get(c.src) ? (f(), p("div", EA, [ M(t.$slots, "error", {}, () => [ v[5] || (v[5] = E("div", { class: "error-fallback" }, [ E("span", null, "Failed to load image") ], -1)) ], !0) ])) : (f(), p("img", { key: 2, src: c.src, alt: `Slide image ${N + 1} of ${l.value}`, style: y({ width: "100%", opacity: u(W).has(c.src) ? 1 : 0, transition: "opacity 0.2s" }), onLoad: (Z) => _(c.src), onError: (Z) => G(c.src) }, null, 44, pA)) ]) ], !0) ], 12, mA))), 128)) ], 4) ], 512), (T = t.arrowOptions) != null && T.next.show || t.showNext ? (f(), z(iA, { key: 1, onClickNextButton: u(P) }, null, 8, ["onClickNextButton"])) : R("", !0) ], 32), t.showDots ? (f(), z(rA, { key: 0, "current-index": e.value, "item-count": l.value, "show-dots": t.showDots, onClickDotButton: u(H) }, null, 8, ["current-index", "item-count", "show-dots", "onClickDotButton"])) : R("", !0) ], 4); }; } }), wA = (A, a) => { const n = A.__vccOpts || A; for (const [o, d] of a) n[o] = d; return n; }, bA = /* @__PURE__ */ wA(QA, [["__scopeId", "data-v-4929f8ae"]]); export { bA as default };