@rxdrag/website-lib 0.0.145 → 0.0.146
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/Carousel.astro +306 -0
- package/components/CarouselNavButton.astro +77 -0
- package/components/CarouselPagination.astro +148 -0
- package/components/CarouselSlide.astro +69 -0
- package/components/CarouselSlides.astro +26 -0
- package/components/CookieConsent.astro +31 -25
- package/components/Dialog.astro +0 -36
- package/package.json +7 -9
- package/components/HeroCarousel.astro +0 -204
- package/components/HeroCarouselPanel.astro +0 -24
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
---
|
|
2
|
+
const rootId = crypto.randomUUID();
|
|
3
|
+
|
|
4
|
+
export interface Props {
|
|
5
|
+
class?: string;
|
|
6
|
+
className?: string;
|
|
7
|
+
/** 自动播放间隔(毫秒),设为 0 禁用自动播放 */
|
|
8
|
+
autoplay?: number;
|
|
9
|
+
/** 是否循环播放 */
|
|
10
|
+
loop?: boolean;
|
|
11
|
+
/** 初始显示的 slide 索引 */
|
|
12
|
+
initialSlide?: number;
|
|
13
|
+
/** 切换动画时长(毫秒) */
|
|
14
|
+
transitionDuration?: number;
|
|
15
|
+
/** 是否启用触摸滑动 */
|
|
16
|
+
touchEnabled?: boolean;
|
|
17
|
+
/** 鼠标悬停时是否暂停自动播放 */
|
|
18
|
+
pauseOnHover?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const {
|
|
22
|
+
class: className,
|
|
23
|
+
className: className2,
|
|
24
|
+
autoplay = 3500,
|
|
25
|
+
loop = true,
|
|
26
|
+
initialSlide = 0,
|
|
27
|
+
transitionDuration = 500,
|
|
28
|
+
touchEnabled = true,
|
|
29
|
+
pauseOnHover = true,
|
|
30
|
+
...rest
|
|
31
|
+
} = Astro.props;
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
<div
|
|
35
|
+
data-carousel
|
|
36
|
+
data-carousel-root
|
|
37
|
+
id={rootId}
|
|
38
|
+
data-autoplay={autoplay}
|
|
39
|
+
data-loop={loop}
|
|
40
|
+
data-initial-slide={initialSlide}
|
|
41
|
+
data-transition-duration={transitionDuration}
|
|
42
|
+
data-touch-enabled={touchEnabled}
|
|
43
|
+
data-pause-on-hover={pauseOnHover}
|
|
44
|
+
class:list={["carousel relative", className, className2]}
|
|
45
|
+
role="region"
|
|
46
|
+
aria-roledescription="carousel"
|
|
47
|
+
aria-label="Image carousel"
|
|
48
|
+
{...rest}
|
|
49
|
+
>
|
|
50
|
+
<slot />
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<script is:inline define:vars={{ rootId }} data-astro-rerun>
|
|
54
|
+
const getState = () => {
|
|
55
|
+
const w = window;
|
|
56
|
+
const all = (w.__carousels__ ||= {});
|
|
57
|
+
return (all[rootId] ||= { cleanup: null, instance: null, globalCleanup: null });
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const initCarousel = () => {
|
|
61
|
+
const root = document.getElementById(rootId);
|
|
62
|
+
if (!root) return () => {};
|
|
63
|
+
|
|
64
|
+
const viewport = root.querySelector(".carousel-viewport");
|
|
65
|
+
const track = root.querySelector(".carousel-track");
|
|
66
|
+
if (!track) return () => {};
|
|
67
|
+
|
|
68
|
+
const slides = Array.from(track.children);
|
|
69
|
+
const slideCount = slides.length;
|
|
70
|
+
if (slideCount === 0) return () => {};
|
|
71
|
+
|
|
72
|
+
const config = {
|
|
73
|
+
autoplay: parseInt(root.dataset.autoplay || "3500", 10),
|
|
74
|
+
loop: root.dataset.loop !== "false",
|
|
75
|
+
initialSlide: parseInt(root.dataset.initialSlide || "0", 10),
|
|
76
|
+
transitionDuration: parseInt(root.dataset.transitionDuration || "500", 10),
|
|
77
|
+
touchEnabled: root.dataset.touchEnabled !== "false",
|
|
78
|
+
pauseOnHover: root.dataset.pauseOnHover !== "false",
|
|
79
|
+
paginationType: root.dataset.paginationType || "bullets",
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
let currentIndex = Math.max(0, Math.min(config.initialSlide, slideCount - 1));
|
|
83
|
+
let autoplayTimer;
|
|
84
|
+
let touchStartX = 0;
|
|
85
|
+
let touchEndX = 0;
|
|
86
|
+
let isDragging = false;
|
|
87
|
+
|
|
88
|
+
const ac = new AbortController();
|
|
89
|
+
const { signal } = ac;
|
|
90
|
+
|
|
91
|
+
slides.forEach((slide, i) => {
|
|
92
|
+
slide.style.minWidth = "100%";
|
|
93
|
+
slide.style.flexShrink = "0";
|
|
94
|
+
slide.setAttribute("role", "group");
|
|
95
|
+
slide.setAttribute("aria-roledescription", "slide");
|
|
96
|
+
slide.setAttribute("aria-label", `Slide ${i + 1} of ${slideCount}`);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const paginationEl = root.querySelector(".carousel-pagination");
|
|
100
|
+
const progressBar = root.querySelector(".carousel-progress-bar");
|
|
101
|
+
const fractionCurrent = root.querySelector(".carousel-fraction-current");
|
|
102
|
+
const fractionTotal = root.querySelector(".carousel-fraction-total");
|
|
103
|
+
let bullets = [];
|
|
104
|
+
|
|
105
|
+
const updateUI = () => {
|
|
106
|
+
track.style.transform = `translateX(-${currentIndex * 100}%)`;
|
|
107
|
+
|
|
108
|
+
bullets.forEach((b, i) => {
|
|
109
|
+
const isActive = i === currentIndex;
|
|
110
|
+
b.classList.toggle("carousel-bullet-active", isActive);
|
|
111
|
+
b.setAttribute("aria-current", isActive ? "true" : "false");
|
|
112
|
+
b.setAttribute("aria-selected", isActive ? "true" : "false");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
if (progressBar) {
|
|
116
|
+
const progress = ((currentIndex + 1) / slideCount) * 100;
|
|
117
|
+
progressBar.style.width = `${progress}%`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (fractionCurrent) fractionCurrent.textContent = String(currentIndex + 1);
|
|
121
|
+
if (fractionTotal) fractionTotal.textContent = String(slideCount);
|
|
122
|
+
|
|
123
|
+
slides.forEach((slide, i) => {
|
|
124
|
+
slide.setAttribute("aria-hidden", i !== currentIndex ? "true" : "false");
|
|
125
|
+
slide.inert = i !== currentIndex;
|
|
126
|
+
});
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const goto = (index, animate = true) => {
|
|
130
|
+
if (!config.loop) {
|
|
131
|
+
index = Math.max(0, Math.min(index, slideCount - 1));
|
|
132
|
+
} else {
|
|
133
|
+
if (index < 0) index = slideCount - 1;
|
|
134
|
+
if (index >= slideCount) index = 0;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!animate) {
|
|
138
|
+
track.style.transitionDuration = "0ms";
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
currentIndex = index;
|
|
142
|
+
updateUI();
|
|
143
|
+
|
|
144
|
+
if (!animate) {
|
|
145
|
+
requestAnimationFrame(() => {
|
|
146
|
+
track.style.transitionDuration = `${config.transitionDuration}ms`;
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
root.dispatchEvent(new CustomEvent("carousel:change", {
|
|
151
|
+
detail: { index: currentIndex, slideCount },
|
|
152
|
+
bubbles: true,
|
|
153
|
+
}));
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const next = () => goto(currentIndex + 1);
|
|
157
|
+
const prev = () => goto(currentIndex - 1);
|
|
158
|
+
|
|
159
|
+
const createBullets = () => {
|
|
160
|
+
if (!paginationEl || config.paginationType !== "bullets") return;
|
|
161
|
+
paginationEl.innerHTML = "";
|
|
162
|
+
bullets = [];
|
|
163
|
+
|
|
164
|
+
for (let i = 0; i < slideCount; i++) {
|
|
165
|
+
const bullet = document.createElement("button");
|
|
166
|
+
bullet.className = "carousel-bullet";
|
|
167
|
+
bullet.type = "button";
|
|
168
|
+
bullet.setAttribute("role", "tab");
|
|
169
|
+
bullet.setAttribute("aria-label", `Go to slide ${i + 1}`);
|
|
170
|
+
bullet.addEventListener("click", () => goto(i), { signal });
|
|
171
|
+
paginationEl.appendChild(bullet);
|
|
172
|
+
bullets.push(bullet);
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const prevBtn = root.querySelector(".carousel-prev");
|
|
177
|
+
const nextBtn = root.querySelector(".carousel-next");
|
|
178
|
+
|
|
179
|
+
prevBtn?.addEventListener("click", prev, { signal });
|
|
180
|
+
nextBtn?.addEventListener("click", next, { signal });
|
|
181
|
+
|
|
182
|
+
const stopAutoplay = () => {
|
|
183
|
+
if (autoplayTimer) {
|
|
184
|
+
clearInterval(autoplayTimer);
|
|
185
|
+
autoplayTimer = undefined;
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const startAutoplay = () => {
|
|
190
|
+
if (config.autoplay <= 0) return;
|
|
191
|
+
stopAutoplay();
|
|
192
|
+
autoplayTimer = setInterval(next, config.autoplay);
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
if (config.pauseOnHover) {
|
|
196
|
+
root.addEventListener("mouseenter", stopAutoplay, { signal });
|
|
197
|
+
root.addEventListener("mouseleave", startAutoplay, { signal });
|
|
198
|
+
root.addEventListener("focusin", stopAutoplay, { signal });
|
|
199
|
+
root.addEventListener("focusout", startAutoplay, { signal });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (config.touchEnabled) {
|
|
203
|
+
const handleTouchStart = (e) => {
|
|
204
|
+
touchStartX = e.touches ? e.touches[0].clientX : e.clientX;
|
|
205
|
+
isDragging = true;
|
|
206
|
+
stopAutoplay();
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const handleTouchMove = (e) => {
|
|
210
|
+
if (!isDragging) return;
|
|
211
|
+
touchEndX = e.touches ? e.touches[0].clientX : e.clientX;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const handleTouchEnd = () => {
|
|
215
|
+
if (!isDragging) return;
|
|
216
|
+
isDragging = false;
|
|
217
|
+
const diff = touchStartX - touchEndX;
|
|
218
|
+
const threshold = 50;
|
|
219
|
+
|
|
220
|
+
if (Math.abs(diff) > threshold) {
|
|
221
|
+
if (diff > 0) next();
|
|
222
|
+
else prev();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
startAutoplay();
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
viewport?.addEventListener("touchstart", handleTouchStart, { signal, passive: true });
|
|
229
|
+
viewport?.addEventListener("touchmove", handleTouchMove, { signal, passive: true });
|
|
230
|
+
viewport?.addEventListener("touchend", handleTouchEnd, { signal });
|
|
231
|
+
viewport?.addEventListener("mousedown", handleTouchStart, { signal });
|
|
232
|
+
viewport?.addEventListener("mousemove", handleTouchMove, { signal });
|
|
233
|
+
viewport?.addEventListener("mouseup", handleTouchEnd, { signal });
|
|
234
|
+
viewport?.addEventListener("mouseleave", handleTouchEnd, { signal });
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
root.addEventListener("keydown", (e) => {
|
|
238
|
+
if (e.key === "ArrowLeft") { prev(); e.preventDefault(); }
|
|
239
|
+
if (e.key === "ArrowRight") { next(); e.preventDefault(); }
|
|
240
|
+
}, { signal });
|
|
241
|
+
|
|
242
|
+
document.addEventListener("visibilitychange", () => {
|
|
243
|
+
if (document.hidden) stopAutoplay();
|
|
244
|
+
else startAutoplay();
|
|
245
|
+
}, { signal });
|
|
246
|
+
|
|
247
|
+
createBullets();
|
|
248
|
+
goto(currentIndex, false);
|
|
249
|
+
startAutoplay();
|
|
250
|
+
|
|
251
|
+
const instance = { goto, next, prev, currentIndex: () => currentIndex, slideCount };
|
|
252
|
+
getState().instance = instance;
|
|
253
|
+
|
|
254
|
+
return () => {
|
|
255
|
+
stopAutoplay();
|
|
256
|
+
ac.abort();
|
|
257
|
+
getState().instance = null;
|
|
258
|
+
};
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const boot = () => {
|
|
262
|
+
const state = getState();
|
|
263
|
+
state.cleanup?.();
|
|
264
|
+
state.cleanup = initCarousel();
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const setupGlobalListeners = () => {
|
|
268
|
+
const onPageLoad = () => boot();
|
|
269
|
+
const onAfterSwap = () => boot();
|
|
270
|
+
const onBeforeSwap = () => getState().cleanup?.();
|
|
271
|
+
|
|
272
|
+
document.addEventListener("astro:page-load", onPageLoad);
|
|
273
|
+
document.addEventListener("astro:after-swap", onAfterSwap);
|
|
274
|
+
window.addEventListener("astro:before-swap", onBeforeSwap);
|
|
275
|
+
|
|
276
|
+
return () => {
|
|
277
|
+
document.removeEventListener("astro:page-load", onPageLoad);
|
|
278
|
+
document.removeEventListener("astro:after-swap", onAfterSwap);
|
|
279
|
+
window.removeEventListener("astro:before-swap", onBeforeSwap);
|
|
280
|
+
};
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
if (document.readyState === "loading") {
|
|
284
|
+
document.addEventListener("DOMContentLoaded", boot, { once: true });
|
|
285
|
+
} else {
|
|
286
|
+
boot();
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const state = getState();
|
|
290
|
+
state.globalCleanup?.();
|
|
291
|
+
state.globalCleanup = setupGlobalListeners();
|
|
292
|
+
</script>
|
|
293
|
+
|
|
294
|
+
<style>
|
|
295
|
+
.carousel {
|
|
296
|
+
--carousel-nav-color: #fff;
|
|
297
|
+
--carousel-nav-bg: rgba(0, 0, 0, 0.3);
|
|
298
|
+
--carousel-nav-bg-hover: rgba(0, 0, 0, 0.5);
|
|
299
|
+
--carousel-nav-size: 44px;
|
|
300
|
+
--carousel-nav-icon-size: 24px;
|
|
301
|
+
--carousel-bullet-size: 10px;
|
|
302
|
+
--carousel-bullet-gap: 8px;
|
|
303
|
+
--carousel-bullet-bg: rgba(255, 255, 255, 0.4);
|
|
304
|
+
--carousel-bullet-bg-active: #fff;
|
|
305
|
+
}
|
|
306
|
+
</style>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
export interface Props {
|
|
3
|
+
class?: string;
|
|
4
|
+
direction: "prev" | "next";
|
|
5
|
+
/** 按钮尺寸 */
|
|
6
|
+
size?: "sm" | "md" | "lg";
|
|
7
|
+
/** 样式变体 */
|
|
8
|
+
variant?: "circle" | "square" | "minimal";
|
|
9
|
+
/** 自定义 aria-label */
|
|
10
|
+
ariaLabel?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const {
|
|
14
|
+
class: className,
|
|
15
|
+
direction,
|
|
16
|
+
size = "md",
|
|
17
|
+
variant = "circle",
|
|
18
|
+
ariaLabel,
|
|
19
|
+
...rest
|
|
20
|
+
} = Astro.props;
|
|
21
|
+
|
|
22
|
+
const sizeClasses = {
|
|
23
|
+
sm: "w-8 h-8",
|
|
24
|
+
md: "w-11 h-11",
|
|
25
|
+
lg: "w-14 h-14",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const iconSizes = {
|
|
29
|
+
sm: "w-4 h-4",
|
|
30
|
+
md: "w-6 h-6",
|
|
31
|
+
lg: "w-8 h-8",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const variantClasses = {
|
|
35
|
+
circle: "rounded-full",
|
|
36
|
+
square: "rounded-lg",
|
|
37
|
+
minimal: "rounded-lg bg-transparent hover:bg-white/10",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const defaultLabels = {
|
|
41
|
+
prev: "上一张",
|
|
42
|
+
next: "下一张",
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const points = direction === "prev" ? "15 18 9 12 15 6" : "9 18 15 12 9 6";
|
|
46
|
+
const baseClass = direction === "prev" ? "carousel-prev" : "carousel-next";
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
<button
|
|
50
|
+
type="button"
|
|
51
|
+
class:list={[
|
|
52
|
+
"carousel-nav-btn inline-flex items-center justify-center border-0 pointer-events-auto text-white bg-black/30 transition duration-200 ease-out opacity-80 hover:bg-black/50 hover:opacity-100 active:scale-95 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white",
|
|
53
|
+
baseClass,
|
|
54
|
+
sizeClasses[size],
|
|
55
|
+
variantClasses[variant],
|
|
56
|
+
className,
|
|
57
|
+
]}
|
|
58
|
+
aria-label={ariaLabel ?? defaultLabels[direction]}
|
|
59
|
+
{...rest}
|
|
60
|
+
>
|
|
61
|
+
{Astro.slots.has("default") ? (
|
|
62
|
+
<slot />
|
|
63
|
+
) : (
|
|
64
|
+
<svg
|
|
65
|
+
class:list={[iconSizes[size]]}
|
|
66
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
67
|
+
viewBox="0 0 24 24"
|
|
68
|
+
fill="none"
|
|
69
|
+
stroke="currentColor"
|
|
70
|
+
stroke-width="2"
|
|
71
|
+
stroke-linecap="round"
|
|
72
|
+
stroke-linejoin="round"
|
|
73
|
+
>
|
|
74
|
+
<polyline points={points}></polyline>
|
|
75
|
+
</svg>
|
|
76
|
+
)}
|
|
77
|
+
</button>
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
---
|
|
2
|
+
export interface Props {
|
|
3
|
+
class?: string;
|
|
4
|
+
/** 分页类型 */
|
|
5
|
+
type?: "bullets" | "fraction" | "progressbar";
|
|
6
|
+
/** 主体形式 */
|
|
7
|
+
variant?: "dots" | "numbers" | "lines";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
class: className,
|
|
12
|
+
type = "bullets",
|
|
13
|
+
variant = "dots",
|
|
14
|
+
} = Astro.props;
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
<div
|
|
18
|
+
class:list={[
|
|
19
|
+
"carousel-pagination-wrapper",
|
|
20
|
+
`carousel-pagination--${variant}`,
|
|
21
|
+
className,
|
|
22
|
+
]}
|
|
23
|
+
data-carousel-pagination
|
|
24
|
+
>
|
|
25
|
+
{type === "bullets" && (
|
|
26
|
+
<div class="carousel-pagination" role="tablist" aria-label="Slide navigation"></div>
|
|
27
|
+
)}
|
|
28
|
+
|
|
29
|
+
{type === "fraction" && (
|
|
30
|
+
<div class="carousel-fraction">
|
|
31
|
+
<span class="carousel-fraction-current">1</span>
|
|
32
|
+
<span class="carousel-fraction-separator">/</span>
|
|
33
|
+
<span class="carousel-fraction-total">1</span>
|
|
34
|
+
</div>
|
|
35
|
+
)}
|
|
36
|
+
|
|
37
|
+
{type === "progressbar" && (
|
|
38
|
+
<div class="carousel-progressbar">
|
|
39
|
+
<div class="carousel-progress-bar"></div>
|
|
40
|
+
</div>
|
|
41
|
+
)}
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<style>
|
|
45
|
+
.carousel-pagination-wrapper {
|
|
46
|
+
display: flex;
|
|
47
|
+
align-items: center;
|
|
48
|
+
gap: var(--pagination-gap, 8px);
|
|
49
|
+
pointer-events: auto;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.carousel-pagination {
|
|
53
|
+
display: flex;
|
|
54
|
+
align-items: center;
|
|
55
|
+
gap: var(--pagination-gap, 8px);
|
|
56
|
+
counter-reset: carouselPage;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.carousel-pagination :global(.carousel-bullet) {
|
|
60
|
+
width: var(--bullet-size, 10px);
|
|
61
|
+
height: var(--bullet-size, 10px);
|
|
62
|
+
min-width: var(--bullet-min-width, auto);
|
|
63
|
+
padding: var(--bullet-padding, 0);
|
|
64
|
+
border-radius: var(--bullet-radius, 9999px);
|
|
65
|
+
border: none;
|
|
66
|
+
cursor: pointer;
|
|
67
|
+
background: var(--bullet-bg, rgba(255, 255, 255, 0.4));
|
|
68
|
+
color: var(--bullet-color, rgba(255, 255, 255, 0.6));
|
|
69
|
+
font-size: var(--bullet-font-size, 0.75rem);
|
|
70
|
+
font-weight: var(--bullet-font-weight, 500);
|
|
71
|
+
letter-spacing: var(--bullet-letter-spacing, 0);
|
|
72
|
+
display: inline-flex;
|
|
73
|
+
align-items: center;
|
|
74
|
+
justify-content: center;
|
|
75
|
+
transition: var(--bullet-transition, background 0.2s, color 0.2s, transform 0.2s);
|
|
76
|
+
counter-increment: carouselPage;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.carousel-pagination :global(.carousel-bullet:hover) {
|
|
80
|
+
color: var(--bullet-color-hover, #ffffff);
|
|
81
|
+
transform: var(--bullet-hover-transform, scale(1.1));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.carousel-pagination :global(.carousel-bullet:focus-visible) {
|
|
85
|
+
outline: 2px solid var(--bullet-bg-active, #fff);
|
|
86
|
+
outline-offset: 2px;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.carousel-pagination :global(.carousel-bullet-active) {
|
|
90
|
+
background: var(--bullet-bg-active, #fff);
|
|
91
|
+
color: var(--bullet-color-active, #fff);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* Variant: dots (default) */
|
|
95
|
+
.carousel-pagination--dots .carousel-pagination :global(.carousel-bullet) {
|
|
96
|
+
width: var(--bullet-size, 10px);
|
|
97
|
+
height: var(--bullet-size, 10px);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* Variant: lines */
|
|
101
|
+
.carousel-pagination--lines .carousel-pagination :global(.carousel-bullet) {
|
|
102
|
+
width: 24px;
|
|
103
|
+
height: 4px;
|
|
104
|
+
border-radius: 2px;
|
|
105
|
+
}
|
|
106
|
+
.carousel-pagination--lines .carousel-pagination :global(.carousel-bullet-active) {
|
|
107
|
+
width: 32px;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* Variant: numbers */
|
|
111
|
+
.carousel-pagination--numbers .carousel-pagination :global(.carousel-bullet) {
|
|
112
|
+
width: auto;
|
|
113
|
+
min-width: 2rem;
|
|
114
|
+
height: 2rem;
|
|
115
|
+
padding: 0 0.5rem;
|
|
116
|
+
}
|
|
117
|
+
.carousel-pagination--numbers .carousel-pagination :global(.carousel-bullet::before) {
|
|
118
|
+
content: counter(carouselPage, decimal-leading-zero);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* Fraction */
|
|
122
|
+
.carousel-fraction {
|
|
123
|
+
display: flex;
|
|
124
|
+
align-items: center;
|
|
125
|
+
gap: 0.25rem;
|
|
126
|
+
font-size: 0.875rem;
|
|
127
|
+
font-weight: 500;
|
|
128
|
+
color: #fff;
|
|
129
|
+
}
|
|
130
|
+
.carousel-fraction-separator {
|
|
131
|
+
opacity: 0.5;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* Progressbar */
|
|
135
|
+
.carousel-progressbar {
|
|
136
|
+
width: 100px;
|
|
137
|
+
height: 4px;
|
|
138
|
+
background: rgba(255, 255, 255, 0.2);
|
|
139
|
+
border-radius: 2px;
|
|
140
|
+
overflow: hidden;
|
|
141
|
+
}
|
|
142
|
+
.carousel-progress-bar {
|
|
143
|
+
height: 100%;
|
|
144
|
+
background: var(--bullet-bg-active, #fff);
|
|
145
|
+
transition: width 0.3s ease;
|
|
146
|
+
width: 0;
|
|
147
|
+
}
|
|
148
|
+
</style>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { BackgroundConfig } from "@rxdrag/website-lib-core";
|
|
3
|
+
import Box from "@rxdrag/website-lib/components/Box.astro";
|
|
4
|
+
import Container from "@rxdrag/website-lib/components/Container.astro";
|
|
5
|
+
|
|
6
|
+
export interface Props {
|
|
7
|
+
class?: string;
|
|
8
|
+
className?: string;
|
|
9
|
+
/** 背景配置 */
|
|
10
|
+
backgrounds?: BackgroundConfig[];
|
|
11
|
+
/** 是否使用 Container 包裹内容 */
|
|
12
|
+
useContainer?: boolean;
|
|
13
|
+
/** 内容垂直对齐:start | center | end */
|
|
14
|
+
align?: "start" | "center" | "end";
|
|
15
|
+
/** 内容水平对齐:start | center | end */
|
|
16
|
+
justify?: "start" | "center" | "end";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const {
|
|
20
|
+
class: className,
|
|
21
|
+
className: className2,
|
|
22
|
+
backgrounds,
|
|
23
|
+
useContainer = true,
|
|
24
|
+
align = "center",
|
|
25
|
+
justify = "start",
|
|
26
|
+
} = Astro.props;
|
|
27
|
+
|
|
28
|
+
const alignClasses = {
|
|
29
|
+
start: "items-start",
|
|
30
|
+
center: "items-center",
|
|
31
|
+
end: "items-end",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const justifyClasses = {
|
|
35
|
+
start: "justify-start",
|
|
36
|
+
center: "justify-center",
|
|
37
|
+
end: "justify-end",
|
|
38
|
+
};
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
<div class="carousel-slide h-full" data-carousel-slide>
|
|
42
|
+
<Box
|
|
43
|
+
class:list={[
|
|
44
|
+
"w-full h-full flex flex-col",
|
|
45
|
+
justifyClasses[justify],
|
|
46
|
+
alignClasses[align],
|
|
47
|
+
]}
|
|
48
|
+
backgrounds={backgrounds}
|
|
49
|
+
>
|
|
50
|
+
{
|
|
51
|
+
useContainer ? (
|
|
52
|
+
<Container class:list={["h-full w-full slider-container relative overflow-hidden", className, className2]}>
|
|
53
|
+
<slot />
|
|
54
|
+
</Container>
|
|
55
|
+
) : (
|
|
56
|
+
<div class:list={["w-full h-full slider-div relative overflow-hidden", className, className2]}>
|
|
57
|
+
<slot />
|
|
58
|
+
</div>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
</Box>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<style>
|
|
65
|
+
.carousel-slide {
|
|
66
|
+
flex-shrink: 0;
|
|
67
|
+
min-width: 100%;
|
|
68
|
+
}
|
|
69
|
+
</style>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
export interface Props {
|
|
3
|
+
class?: string;
|
|
4
|
+
className?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const { class: className, className: className2 } = Astro.props;
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
<div class="carousel-viewport h-full relative overflow-hidden">
|
|
11
|
+
<div
|
|
12
|
+
class:list={["carousel-track h-full flex", className, className2]}
|
|
13
|
+
aria-live="polite"
|
|
14
|
+
data-carousel-track
|
|
15
|
+
>
|
|
16
|
+
<slot />
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<style>
|
|
21
|
+
.carousel-track {
|
|
22
|
+
will-change: transform;
|
|
23
|
+
transition-property: transform;
|
|
24
|
+
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
|
25
|
+
}
|
|
26
|
+
</style>
|
|
@@ -7,35 +7,41 @@ import "./CookieConsent.css";
|
|
|
7
7
|
import { run } from "vanilla-cookieconsent";
|
|
8
8
|
import { config } from "./CookieConsentConfig";
|
|
9
9
|
|
|
10
|
-
//
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
// 设计模式下跳过执行(sandbox 环境 document.cookie 不可用)
|
|
11
|
+
const isDesignMode = typeof window !== 'undefined' && window.top !== window && (window as any).__ASTRO_SANDBOX_MODULES__;
|
|
12
|
+
if (isDesignMode) {
|
|
13
|
+
//console.log('[CookieConsent] skip in design mode');
|
|
14
|
+
} else {
|
|
15
|
+
// 异步初始化Cookie同意逻辑
|
|
16
|
+
async function initializeCookieConsent() {
|
|
17
|
+
try {
|
|
18
|
+
// 始终显示Cookie同意UI
|
|
19
|
+
document.body.classList.add("cc--elegant-black");
|
|
20
|
+
run(config);
|
|
16
21
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
// 显示偏好设置按钮
|
|
23
|
+
const preferencesBtn = document.getElementById("show-preferences-btn");
|
|
24
|
+
if (preferencesBtn) {
|
|
25
|
+
preferencesBtn.style.display = "block";
|
|
26
|
+
}
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error("Cookie同意初始化失败:", error);
|
|
29
|
+
// 出错时默认显示同意UI(安全策略)
|
|
30
|
+
document.body.classList.add("cc--elegant-black");
|
|
31
|
+
run(config);
|
|
27
32
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
const preferencesBtn = document.getElementById("show-preferences-btn");
|
|
34
|
+
if (preferencesBtn) {
|
|
35
|
+
preferencesBtn.style.display = "block";
|
|
36
|
+
}
|
|
31
37
|
}
|
|
32
38
|
}
|
|
33
|
-
}
|
|
34
39
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
// 页面加载完成后初始化
|
|
41
|
+
if (document.readyState === "loading") {
|
|
42
|
+
document.addEventListener("DOMContentLoaded", initializeCookieConsent);
|
|
43
|
+
} else {
|
|
44
|
+
initializeCookieConsent();
|
|
45
|
+
}
|
|
40
46
|
}
|
|
41
47
|
</script>
|
package/components/Dialog.astro
CHANGED
|
@@ -179,8 +179,6 @@ const resolvedExitTransform =
|
|
|
179
179
|
const w = window;
|
|
180
180
|
if (!w[globalKey]) w[globalKey] = new Set();
|
|
181
181
|
|
|
182
|
-
const logPrefix = `[Dialog:${openableKey}]`;
|
|
183
|
-
|
|
184
182
|
const backdropClass = backdropClassString || "";
|
|
185
183
|
|
|
186
184
|
const isDialogEl = (el) => {
|
|
@@ -191,7 +189,6 @@ const resolvedExitTransform =
|
|
|
191
189
|
const getDialog = () => {
|
|
192
190
|
const el = document.getElementById(openableKey);
|
|
193
191
|
if (!isDialogEl(el)) {
|
|
194
|
-
console.log(logPrefix, "getDialog: dialog not found", openableKey);
|
|
195
192
|
return null;
|
|
196
193
|
}
|
|
197
194
|
return el;
|
|
@@ -213,12 +210,6 @@ const resolvedExitTransform =
|
|
|
213
210
|
? `blur(${backdropBlurPx}px)`
|
|
214
211
|
: "";
|
|
215
212
|
backdrop.style.animation = `genericBackdropIn ${openAnimMs}ms ease-out`;
|
|
216
|
-
console.log(logPrefix, "ensureBackdrop: create", {
|
|
217
|
-
backdropClass,
|
|
218
|
-
backdropOpacity,
|
|
219
|
-
backdropBlurPx,
|
|
220
|
-
openAnimMs,
|
|
221
|
-
});
|
|
222
213
|
backdrop.addEventListener("click", () =>
|
|
223
214
|
requestClose(dialog, "backdrop")
|
|
224
215
|
);
|
|
@@ -234,13 +225,11 @@ const resolvedExitTransform =
|
|
|
234
225
|
const removeBackdrop = (dialog) => {
|
|
235
226
|
const next = dialog.nextElementSibling;
|
|
236
227
|
if (next && next.classList?.contains("backdrop")) {
|
|
237
|
-
console.log(logPrefix, "removeBackdrop");
|
|
238
228
|
next.remove();
|
|
239
229
|
}
|
|
240
230
|
};
|
|
241
231
|
|
|
242
232
|
const closeNow = (dialog) => {
|
|
243
|
-
console.log(logPrefix, "closeNow");
|
|
244
233
|
if (typeof dialog.close === "function") {
|
|
245
234
|
dialog.close();
|
|
246
235
|
} else {
|
|
@@ -252,11 +241,6 @@ const resolvedExitTransform =
|
|
|
252
241
|
};
|
|
253
242
|
|
|
254
243
|
const requestClose = (dialog, reason = "unknown") => {
|
|
255
|
-
console.log(logPrefix, "requestClose", {
|
|
256
|
-
reason,
|
|
257
|
-
open: isOpen(dialog),
|
|
258
|
-
state: dialog.getAttribute("data-dialog-state"),
|
|
259
|
-
});
|
|
260
244
|
if (!isOpen(dialog)) return;
|
|
261
245
|
if (dialog.getAttribute("data-dialog-state") === "closing") return;
|
|
262
246
|
dialog.setAttribute("data-dialog-state", "closing");
|
|
@@ -275,10 +259,6 @@ const resolvedExitTransform =
|
|
|
275
259
|
if (dialog.dataset.genericDialogBound) return;
|
|
276
260
|
dialog.dataset.genericDialogBound = "1";
|
|
277
261
|
|
|
278
|
-
console.log(logPrefix, "ensureDialogBound", {
|
|
279
|
-
closeSelector,
|
|
280
|
-
});
|
|
281
|
-
|
|
282
262
|
dialog.addEventListener("click", (e) => {
|
|
283
263
|
if (e.target === dialog) requestClose(dialog, "dialog_blank_area");
|
|
284
264
|
});
|
|
@@ -304,11 +284,6 @@ const resolvedExitTransform =
|
|
|
304
284
|
if (isOpen(dialog)) return;
|
|
305
285
|
|
|
306
286
|
try {
|
|
307
|
-
console.log(logPrefix, "open: start", {
|
|
308
|
-
forceFallback,
|
|
309
|
-
__force_dialog_fallback__: window.__force_dialog_fallback__,
|
|
310
|
-
hasShowModal: typeof dialog.showModal === "function",
|
|
311
|
-
});
|
|
312
287
|
const { scrollX, scrollY } = window;
|
|
313
288
|
dialog.removeAttribute("data-dialog-state");
|
|
314
289
|
|
|
@@ -316,10 +291,8 @@ const resolvedExitTransform =
|
|
|
316
291
|
forceFallback === true || window.__force_dialog_fallback__ === true;
|
|
317
292
|
|
|
318
293
|
if (!effectiveForceFallback && typeof dialog.showModal === "function") {
|
|
319
|
-
console.log(logPrefix, "open: native showModal");
|
|
320
294
|
dialog.showModal();
|
|
321
295
|
} else {
|
|
322
|
-
console.log(logPrefix, "open: fallback open attr + backdrop + esc");
|
|
323
296
|
ensureBackdrop(dialog);
|
|
324
297
|
dialog.setAttribute("open", "");
|
|
325
298
|
dialog.__genericDialogEscHandler = (e) => {
|
|
@@ -338,17 +311,12 @@ const resolvedExitTransform =
|
|
|
338
311
|
} catch {
|
|
339
312
|
dialog.focus();
|
|
340
313
|
}
|
|
341
|
-
console.log(logPrefix, "open: focus done");
|
|
342
314
|
});
|
|
343
315
|
} catch (err) {
|
|
344
|
-
console.log(logPrefix, "open failed", err);
|
|
345
316
|
}
|
|
346
317
|
};
|
|
347
318
|
|
|
348
319
|
const init = () => {
|
|
349
|
-
console.log(logPrefix, "init", {
|
|
350
|
-
alreadyBound: w[globalKey].has(openableKey),
|
|
351
|
-
});
|
|
352
320
|
if (!w[globalKey].has(openableKey)) {
|
|
353
321
|
w[globalKey].add(openableKey);
|
|
354
322
|
document.addEventListener("click", (e) => {
|
|
@@ -358,10 +326,6 @@ const resolvedExitTransform =
|
|
|
358
326
|
if (!opener) return;
|
|
359
327
|
const cta = opener.getAttribute("data-call-to-action") || "";
|
|
360
328
|
window.lastCta = cta;
|
|
361
|
-
console.log(logPrefix, "opener click -> open", {
|
|
362
|
-
cta,
|
|
363
|
-
openerTag: opener.tagName,
|
|
364
|
-
});
|
|
365
329
|
open();
|
|
366
330
|
});
|
|
367
331
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rxdrag/website-lib",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.146",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./index.ts",
|
|
@@ -27,13 +27,12 @@
|
|
|
27
27
|
"astro": "^5.16.6",
|
|
28
28
|
"eslint": "^9.39.2",
|
|
29
29
|
"gsap": "^3.12.7",
|
|
30
|
-
"swiper": "^12.0.3",
|
|
31
30
|
"typescript": "^5",
|
|
32
|
-
"@rxdrag/rxcms-models": "0.3.106",
|
|
33
|
-
"@rxdrag/tiptap-preview": "0.0.3",
|
|
34
31
|
"@rxdrag/entify-hooks": "0.2.79",
|
|
32
|
+
"@rxdrag/eslint-config-custom": "0.2.13",
|
|
33
|
+
"@rxdrag/tiptap-preview": "0.0.3",
|
|
35
34
|
"@rxdrag/tsconfig": "0.2.1",
|
|
36
|
-
"@rxdrag/
|
|
35
|
+
"@rxdrag/rxcms-models": "0.3.106"
|
|
37
36
|
},
|
|
38
37
|
"dependencies": {
|
|
39
38
|
"aos": "3.0.0-beta.6",
|
|
@@ -41,12 +40,11 @@
|
|
|
41
40
|
"react": "^19.1.0",
|
|
42
41
|
"react-dom": "^19.1.0",
|
|
43
42
|
"vanilla-cookieconsent": "3.1.0",
|
|
44
|
-
"@rxdrag/
|
|
45
|
-
"@rxdrag/
|
|
43
|
+
"@rxdrag/website-lib-core": "0.0.125",
|
|
44
|
+
"@rxdrag/rxcms-models": "0.3.106"
|
|
46
45
|
},
|
|
47
46
|
"peerDependencies": {
|
|
48
|
-
"astro": "^5.16.6"
|
|
49
|
-
"swiper": "^12.0.3"
|
|
47
|
+
"astro": "^5.16.6"
|
|
50
48
|
},
|
|
51
49
|
"scripts": {
|
|
52
50
|
"lint": "eslint . --ext .ts,tsx",
|
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import Section from "./Section.astro";
|
|
3
|
-
import clsx from "clsx";
|
|
4
|
-
import "swiper/css";
|
|
5
|
-
import "swiper/css/pagination";
|
|
6
|
-
import "swiper/css/navigation";
|
|
7
|
-
|
|
8
|
-
interface Props {
|
|
9
|
-
class?: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const { class: className, ...rest } = Astro.props;
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
<Section
|
|
16
|
-
className={clsx(
|
|
17
|
-
"w-full h-[300px] sm:h-[400px] md:h-screen flex flex-col mt-0 bg-black relative overflow-hidden z-10 py-0",
|
|
18
|
-
className,
|
|
19
|
-
)}
|
|
20
|
-
disableContainer
|
|
21
|
-
{...rest}
|
|
22
|
-
>
|
|
23
|
-
<div class="hero-carousel w-full h-full relative" data-hero-carousel>
|
|
24
|
-
<div class="swiper h-full relative">
|
|
25
|
-
<div class="swiper-wrapper h-full">
|
|
26
|
-
<slot />
|
|
27
|
-
</div>
|
|
28
|
-
<slot name="navigation" />
|
|
29
|
-
<slot name="pagination" />
|
|
30
|
-
</div>
|
|
31
|
-
</div>
|
|
32
|
-
</Section>
|
|
33
|
-
|
|
34
|
-
<script>
|
|
35
|
-
type SwiperRootEl = HTMLElement & { __swiperInstance?: any };
|
|
36
|
-
type SwiperType = typeof import("swiper").default;
|
|
37
|
-
type SwiperModulesType = typeof import("swiper/modules");
|
|
38
|
-
|
|
39
|
-
type BootWindow = Window & {
|
|
40
|
-
__heroCarouselBootstrapped?: boolean;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const destroyRoot = (root: SwiperRootEl) => {
|
|
44
|
-
const existing = root.__swiperInstance;
|
|
45
|
-
if (existing && typeof existing.destroy === "function") {
|
|
46
|
-
existing.destroy(true, true);
|
|
47
|
-
root.__swiperInstance = null;
|
|
48
|
-
}
|
|
49
|
-
root.dataset.heroCarouselInited = "";
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
const loadSwiperResources = async () => {
|
|
53
|
-
const [{ default: Swiper }, { Autoplay, Navigation, Pagination }] =
|
|
54
|
-
await Promise.all([import("swiper"), import("swiper/modules")]);
|
|
55
|
-
return { Swiper, Autoplay, Navigation, Pagination };
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
let swiperCache: {
|
|
59
|
-
Swiper: SwiperType;
|
|
60
|
-
Autoplay: SwiperModulesType["Autoplay"];
|
|
61
|
-
Navigation: SwiperModulesType["Navigation"];
|
|
62
|
-
Pagination: SwiperModulesType["Pagination"];
|
|
63
|
-
} | null = null;
|
|
64
|
-
|
|
65
|
-
const getSwiperModules = async () => {
|
|
66
|
-
if (!swiperCache) {
|
|
67
|
-
swiperCache = await loadSwiperResources();
|
|
68
|
-
}
|
|
69
|
-
return swiperCache;
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
const initRoot = async (root: SwiperRootEl) => {
|
|
73
|
-
if (root.dataset.heroCarouselInited === "1") {
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const el = root.querySelector(".swiper") as HTMLElement | null;
|
|
78
|
-
if (!el) return;
|
|
79
|
-
|
|
80
|
-
const paginationEl = root.querySelector(
|
|
81
|
-
".swiper-pagination",
|
|
82
|
-
) as HTMLElement | null;
|
|
83
|
-
const prevEl = root.querySelector(
|
|
84
|
-
".swiper-button-prev",
|
|
85
|
-
) as HTMLElement | null;
|
|
86
|
-
const nextEl = root.querySelector(
|
|
87
|
-
".swiper-button-next",
|
|
88
|
-
) as HTMLElement | null;
|
|
89
|
-
|
|
90
|
-
const wantPagination = !!paginationEl;
|
|
91
|
-
const wantNavigation = !!prevEl && !!nextEl;
|
|
92
|
-
|
|
93
|
-
destroyRoot(root);
|
|
94
|
-
|
|
95
|
-
const { Swiper, Autoplay, Navigation, Pagination } =
|
|
96
|
-
await getSwiperModules();
|
|
97
|
-
|
|
98
|
-
const modules = [Autoplay] as any[];
|
|
99
|
-
if (wantPagination) {
|
|
100
|
-
modules.push(Pagination);
|
|
101
|
-
}
|
|
102
|
-
if (wantNavigation) {
|
|
103
|
-
modules.push(Navigation);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const instance = new Swiper(el, {
|
|
107
|
-
modules,
|
|
108
|
-
slidesPerView: 1,
|
|
109
|
-
spaceBetween: 0,
|
|
110
|
-
rewind: true,
|
|
111
|
-
observer: true,
|
|
112
|
-
observeParents: true,
|
|
113
|
-
autoplay: {
|
|
114
|
-
delay: 3500,
|
|
115
|
-
disableOnInteraction: false,
|
|
116
|
-
pauseOnMouseEnter: false,
|
|
117
|
-
},
|
|
118
|
-
...(wantPagination && paginationEl
|
|
119
|
-
? {
|
|
120
|
-
pagination: {
|
|
121
|
-
el: paginationEl,
|
|
122
|
-
clickable: true,
|
|
123
|
-
type: "bullets",
|
|
124
|
-
bulletActiveClass: "swiper-pagination-bullet-active",
|
|
125
|
-
},
|
|
126
|
-
}
|
|
127
|
-
: {}),
|
|
128
|
-
...(wantNavigation && prevEl && nextEl
|
|
129
|
-
? {
|
|
130
|
-
navigation: {
|
|
131
|
-
prevEl,
|
|
132
|
-
nextEl,
|
|
133
|
-
},
|
|
134
|
-
}
|
|
135
|
-
: {}),
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
try {
|
|
139
|
-
instance.autoplay?.start?.();
|
|
140
|
-
} catch {}
|
|
141
|
-
|
|
142
|
-
root.__swiperInstance = instance;
|
|
143
|
-
root.dataset.heroCarouselInited = "1";
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
const initAll = async () => {
|
|
147
|
-
const roots = document.querySelectorAll(
|
|
148
|
-
"[data-hero-carousel]",
|
|
149
|
-
) as NodeListOf<SwiperRootEl>;
|
|
150
|
-
if (roots.length === 0) return;
|
|
151
|
-
await Promise.all(Array.from(roots).map((root) => initRoot(root)));
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
const destroyAll = () => {
|
|
155
|
-
const roots = document.querySelectorAll(
|
|
156
|
-
"[data-hero-carousel]",
|
|
157
|
-
) as NodeListOf<SwiperRootEl>;
|
|
158
|
-
roots.forEach((root) => destroyRoot(root));
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
const ensureAutoplayAll = () => {
|
|
162
|
-
const roots = document.querySelectorAll(
|
|
163
|
-
"[data-hero-carousel]",
|
|
164
|
-
) as NodeListOf<SwiperRootEl>;
|
|
165
|
-
roots.forEach((root) => {
|
|
166
|
-
const inst = root.__swiperInstance;
|
|
167
|
-
try {
|
|
168
|
-
inst?.autoplay?.start?.();
|
|
169
|
-
} catch {}
|
|
170
|
-
});
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
const boot = async () => {
|
|
174
|
-
try {
|
|
175
|
-
await initAll();
|
|
176
|
-
} catch (e) {
|
|
177
|
-
console.log("HeroCarousel init error:", e);
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
const w = window as BootWindow;
|
|
182
|
-
if (!w.__heroCarouselBootstrapped) {
|
|
183
|
-
w.__heroCarouselBootstrapped = true;
|
|
184
|
-
|
|
185
|
-
if (document.readyState === "loading") {
|
|
186
|
-
document.addEventListener("DOMContentLoaded", boot);
|
|
187
|
-
} else {
|
|
188
|
-
boot();
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
document.addEventListener("astro:page-load", boot);
|
|
192
|
-
document.addEventListener("astro:after-swap", () => {
|
|
193
|
-
boot();
|
|
194
|
-
ensureAutoplayAll();
|
|
195
|
-
});
|
|
196
|
-
window.addEventListener("astro:before-swap", destroyAll);
|
|
197
|
-
|
|
198
|
-
document.addEventListener("visibilitychange", () => {
|
|
199
|
-
if (!document.hidden) {
|
|
200
|
-
ensureAutoplayAll();
|
|
201
|
-
}
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
</script>
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import Box from "./Box.astro";
|
|
3
|
-
import Container from "./Container.astro";
|
|
4
|
-
import clsx from "clsx";
|
|
5
|
-
import type { BackgroundConfig } from "@rxdrag/website-lib-core";
|
|
6
|
-
|
|
7
|
-
interface Props {
|
|
8
|
-
class?: string;
|
|
9
|
-
backgrounds?: BackgroundConfig[];
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const { class: className, backgrounds } = Astro.props;
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
<div class={clsx("swiper-slide h-full")}>
|
|
16
|
-
<Box
|
|
17
|
-
class={clsx("w-full h-full flex flex-col justify-center")}
|
|
18
|
-
backgrounds={backgrounds}
|
|
19
|
-
>
|
|
20
|
-
<Container class={className}>
|
|
21
|
-
<slot />
|
|
22
|
-
</Container>
|
|
23
|
-
</Box>
|
|
24
|
-
</div>
|