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
JavaScript
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
};