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).
471 lines (470 loc) • 17.9 kB
JavaScript
import { defineComponent as k, ref as h, computed as B, openBlock as g, createElementBlock as p, normalizeStyle as w, Fragment as M, renderList as q, createElementVNode as E, unref as r, watch as R, onMounted as $, onBeforeUnmount as Y, onScopeDispose as K, createBlock as V, createCommentVNode as N, renderSlot as z, mergeProps as J } from "vue";
const AA = (A) => {
var o;
const l = [];
(o = A.items) != null && o.length || l.push({
prop: "items",
message: "At least one item is required"
}), A.interval != null && A.interval < 0 && l.push({
prop: "interval",
message: "Interval must be a non-negative number"
}), A.duration != null && A.duration <= 0 && l.push({
prop: "duration",
message: "Duration must be greater than 0"
});
const t = /^(\d+(\.\d+)?)(px|%|rem|em|vh|vw)$/;
return A.width && !t.test(A.width) && l.push({
prop: "width",
message: "Width must be a valid CSS dimension (e.g., 100%, 200px)"
}), A.height && !t.test(A.height) && l.push({
prop: "height",
message: "Height must be a valid CSS dimension (e.g., 300px, 50vh)"
}), l;
}, eA = { style: {
padding: "0px 4px 0px 4px"
} }, tA = ["onClick", "aria-label", "aria-current"], oA = /* @__PURE__ */ k({
__name: "DotButtons",
props: {
currentIndex: { default: 0 },
itemCount: { default: 0 },
showDots: { type: Boolean, default: !1 }
},
emits: ["click-dot-button"],
setup(A, { emit: l }) {
const t = l, o = A, v = h({
display: "flex",
alignItems: "center",
justifyContent: "center",
flexWrap: "wrap"
}), a = (u) => {
t("click-dot-button", u - 1);
}, i = B(
() => new Array(o.itemCount).fill({
border: "none",
borderRadius: "50%",
backgroundColor: "gray",
cursor: "pointer"
}).map((u, e) => {
const s = e === (o.itemCount > o.currentIndex ? o.currentIndex : 0);
return {
...u,
opacity: s ? "1" : "0.5",
height: s ? "8px" : "6px",
paddingInline: s ? "4px" : "3px"
};
})
);
return (u, e) => (g(), p("div", {
style: w(v.value)
}, [
(g(!0), p(M, null, q(u.itemCount, (s) => (g(), p("div", eA, [
E("button", {
style: w(i.value[s - 1]),
onClick: (m) => a(s),
"aria-label": `Go to slide ${s}`,
"aria-current": s - 1 === u.currentIndex
}, null, 12, tA)
]))), 256))
], 4));
}
});
function j() {
const A = h({
zIndex: 1,
display: "flex",
justifyContent: "center",
alignItems: "center",
padding: "5px"
}), l = h({
height: "20px",
border: "none",
color: "silver",
borderRadius: "10px"
});
return {
buttonWrapperStyle: A,
buttonStyle: l,
onMouseDownButton: () => {
l.value.color = "gray";
},
onMouseUpButton: () => {
l.value.color = "silver";
}
};
}
const F = "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", nA = ["src"], lA = /* @__PURE__ */ k({
__name: "PrevButton",
emits: ["click-prev-button"],
setup(A, { emit: l }) {
const t = l, { buttonWrapperStyle: o, buttonStyle: v, onMouseDownButton: a } = j(), i = () => {
t("click-prev-button");
};
return (u, e) => (g(), p("div", {
style: w(r(o))
}, [
E("button", {
class: "prev-button",
style: w(r(v)),
onClick: i,
onMousedown: e[0] || (e[0] = //@ts-ignore
(...s) => r(a) && r(a)(...s)),
"aria-label": "Previous slide"
}, [
E("img", {
alt: "left arrow",
src: r(F),
style: {
width: "10px",
borderRadius: "50%",
transform: "scaleX(-1)"
}
}, null, 8, nA)
], 36)
], 4));
}
}), rA = ["src"], sA = /* @__PURE__ */ k({
__name: "NextButton",
emits: ["click-next-button"],
setup(A, { emit: l }) {
const t = l, { buttonWrapperStyle: o, buttonStyle: v, onMouseUpButton: a } = j(), i = () => {
t("click-next-button");
};
return (u, e) => (g(), p("div", {
style: w(r(o))
}, [
E("button", {
class: "next-button",
style: w(r(v)),
onClick: i,
onMouseup: e[0] || (e[0] = //@ts-ignore
(...s) => r(a) && r(a)(...s)),
"aria-label": "Next slide"
}, [
E("img", {
alt: "left arrow",
src: r(F),
style: {
width: "10px",
borderRadius: "3px"
}
}, null, 8, rA)
], 36)
], 4));
}
}), aA = 1e3, iA = "translate";
function uA(A, l, t, o, v) {
const a = h(!1), i = B(() => {
const f = A.direction;
return {
isHorizontal: ["left", "right"].includes(f),
isReverse: ["up", "left"].includes(f),
flexDirection: {
left: "row",
right: "row-reverse",
up: "column",
down: "column-reverse"
}[f]
};
}), u = B(
() => i.value.isHorizontal ? o.value : v.value
), e = B(() => {
const f = l.value.length, C = t.value % f;
return i.value.isReverse ? C : f - (C + 1 % f);
}), s = B(() => e.value * u.value), m = B(() => {
const f = i.value.isHorizontal ? "X" : "Y";
return `${iA}${f}(-${s.value}px)`;
}), y = h(`transform ${(A.duration ?? aA) / 1e3}s ease`), Q = h(y.value);
return {
itemsTranslate: m,
defaultTransition: y,
transition: Q,
isTransitioning: a,
directionProps: i
};
}
function vA(A, l, t, o, v, a, i) {
const u = h(null), e = h(!1), s = () => {
o.value = ++o.value % t.value.length, a.value = v.value;
}, m = () => {
i.value || (o.value >= t.value.length - 1 ? (i.value = !0, a.value = "", o.value = 0, requestAnimationFrame(() => {
requestAnimationFrame(() => {
a.value = v.value, o.value = 1, setTimeout(() => {
i.value = !1;
}, A.duration || 1e3);
});
})) : s());
}, y = () => {
u.value !== null && (window.clearInterval(u.value), u.value = null);
}, Q = () => {
y(), A.interval && A.items.length > 1 && !e.value && (u.value = window.setInterval(m, A.interval));
}, f = () => {
m(), A.interval && A.items.length > 1 && Q();
}, C = B(
() => (o.value - 1 + t.value.length) % t.value.length
), x = () => {
i.value || (o.value === 0 ? (i.value = !0, a.value = "", o.value = C.value, setTimeout(() => {
o.value = C.value, a.value = v.value, setTimeout(() => {
i.value = !1;
}, A.duration || 1e3);
}, 100)) : o.value = C.value, A.interval && A.items.length > 1 && Q());
}, D = (b) => {
i.value || (o.value = b, A.interval && Q());
};
R(
() => [
A.interval,
A.direction,
A.items.length,
A.pauseAutoplayOnHover
],
() => {
Q();
},
{ deep: !0 }
);
const S = () => {
A.pauseAutoplayOnHover && (e.value = !0, y());
}, P = () => {
A.pauseAutoplayOnHover && A.items.length > 1 && (e.value = !1, Q());
};
return $(() => {
a.value = "", A.interval && A.items.length > 1 && Q();
}), Y(() => {
y();
}), K(() => {
y();
}), {
clickNext: f,
clickPrev: x,
clickDot: D,
pauseSlide: S,
resumeSlide: P
};
}
function cA(A, l) {
const t = h(/* @__PURE__ */ new Map()), o = h(/* @__PURE__ */ new Map()), v = h(/* @__PURE__ */ new Set()), a = (e) => {
t.value.set(e, !1), o.value.set(e, !1), v.value.add(e);
}, i = (e) => {
t.value.set(e, !1), o.value.set(e, !0);
}, u = (e) => {
if (!e || v.value.has(e)) return;
t.value.set(e, !0);
const s = new Image();
s.onload = () => a(e), s.onerror = () => i(e), s.src = e;
};
return A && l && R([A, l], () => {
const e = l.value[A.value], s = (A.value + 1) % l.value.length, m = l.value[s];
e != null && e.src && u(e.src), m != null && m.src && u(m.src);
}, { immediate: !0 }), {
loadingStates: t,
errorStates: o,
loadedImages: v,
handleImageLoad: a,
handleImageError: i,
preloadImage: u
};
}
const dA = ["aria-hidden"], fA = {
class: "carousel-image-container",
style: { width: "100%", height: "100%" }
}, gA = {
key: 0,
class: "carousel-image-loading"
}, mA = {
key: 1,
class: "carousel-image-error"
}, BA = ["src", "alt", "onLoad", "onError"], hA = /* @__PURE__ */ k({
__name: "DirectionalCarousel",
props: {
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 }
},
setup(A, { expose: l }) {
const t = A;
R(
() => ({
items: t.items,
interval: t.interval,
duration: t.duration,
width: t.width,
height: t.height
}),
() => {
const n = AA(t);
if (n.length > 0 && (n.forEach((c) => {
console.error(`DirectionalCarousel: ${c.prop} - ${c.message}`);
}), n.some((c) => c.prop === "items")))
throw new Error("DirectionalCarousel: Invalid props - check console for details");
},
{ immediate: !0 }
);
const v = h(null), a = B(
() => t.items.map((n) => typeof n == "string" ? { src: n } : n)
), i = B(() => t.items.length), u = B(() => [...a.value, a.value[0]]), e = h(0), s = B(() => {
var n;
return ((n = v.value) == null ? void 0 : n.offsetWidth) ?? 0;
}), m = B(() => {
var n;
return ((n = v.value) == null ? void 0 : n.offsetHeight) ?? 0;
}), {
itemsTranslate: y,
defaultTransition: Q,
transition: f,
isTransitioning: C,
directionProps: x
} = uA(t, u, e, s, m), {
clickNext: D,
clickPrev: S,
clickDot: P,
pauseSlide: b,
resumeSlide: I
} = vA(
t,
a,
u,
e,
Q,
f,
C
), U = B(() => ({
overflow: "hidden",
width: s.value + "px",
height: m.value + "px",
display: "flex",
justifyContent: "center",
alignItems: "center"
})), {
loadingStates: X,
errorStates: O,
loadedImages: T,
preloadImage: pA
} = cA(e, a), _ = (n) => {
X.value.set(n, !1), T.value.add(n);
}, G = (n) => {
X.value.set(n, !1), O.value.set(n, !0);
};
return l({
currentIndex: e
}), (n, c) => {
var W, L;
return g(), p("div", {
style: w({
overflow: "hidden",
display: "flex",
flexDirection: "column",
width: t.width,
height: t.height
})
}, [
E("div", {
class: "slider",
style: {
overflow: "hidden",
position: "relative",
display: "flex",
width: "100%",
height: "100%"
},
onMouseenter: c[0] || (c[0] = //@ts-ignore
(...d) => r(b) && r(b)(...d)),
onFocusin: c[1] || (c[1] = //@ts-ignore
(...d) => r(b) && r(b)(...d)),
onMouseleave: c[2] || (c[2] = //@ts-ignore
(...d) => r(I) && r(I)(...d)),
onFocusout: c[3] || (c[3] = //@ts-ignore
(...d) => r(I) && r(I)(...d))
}, [
(W = n.arrowOptions) != null && W.prev.show || n.showPrev ? (g(), V(lA, {
key: 0,
onClickPrevButton: r(S)
}, null, 8, ["onClickPrevButton"])) : N("", !0),
E("div", {
ref_key: "carouselContainer",
ref: v,
style: {
overflow: "hidden",
position: "relative",
flex: 1
}
}, [
E("div", {
class: "wrapper-carousel-items",
style: w({
display: "inline-flex",
position: "relative",
flexDirection: r(x).flexDirection,
transform: r(y),
transition: r(f),
pointerEvents: r(C) ? "none" : "auto"
})
}, [
(g(!0), p(M, null, q(u.value, (d, H) => (g(), p("div", {
key: H,
style: w(U.value),
class: "carousel-item",
"aria-hidden": e.value !== H
}, [
z(n.$slots, "item", J({ ref_for: !0 }, d, {
style: { width: n.width, height: n.height }
}), () => [
E("div", fA, [
r(X).get(d.src) ? (g(), p("div", gA, [
z(n.$slots, "loading", {}, () => [
c[4] || (c[4] = E("div", { class: "loading-spinner" }, null, -1))
], !0)
])) : r(O).get(d.src) ? (g(), p("div", mA, [
z(n.$slots, "error", {}, () => [
c[5] || (c[5] = E("div", { class: "error-fallback" }, [
E("span", null, "Failed to load image")
], -1))
], !0)
])) : (g(), p("img", {
key: 2,
src: d.src,
alt: `Slide image ${H + 1} of ${i.value}`,
style: w({
width: "100%",
opacity: r(T).has(d.src) ? 1 : 0,
transition: "opacity 0.2s"
}),
onLoad: (Z) => _(d.src),
onError: (Z) => G(d.src)
}, null, 44, BA))
])
], !0)
], 12, dA))), 128))
], 4)
], 512),
(L = n.arrowOptions) != null && L.next.show || n.showNext ? (g(), V(sA, {
key: 1,
onClickNextButton: r(D)
}, null, 8, ["onClickNextButton"])) : N("", !0)
], 32),
n.showDots ? (g(), V(oA, {
key: 0,
"current-index": e.value,
"item-count": i.value,
"show-dots": n.showDots,
onClickDotButton: r(P)
}, null, 8, ["current-index", "item-count", "show-dots", "onClickDotButton"])) : N("", !0)
], 4);
};
}
}), EA = (A, l) => {
const t = A.__vccOpts || A;
for (const [o, v] of l)
t[o] = v;
return t;
}, wA = /* @__PURE__ */ EA(hA, [["__scopeId", "data-v-e58f5c3d"]]);
export {
wA as default
};