@rxdrag/website-lib 0.0.151 → 0.0.153

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.
Files changed (51) hide show
  1. package/README.md +2 -2
  2. package/components/AnimationNumber.astro +217 -217
  3. package/components/Background.astro +140 -114
  4. package/components/BackgroundGroup.astro +20 -26
  5. package/components/BaseFooter.astro +24 -39
  6. package/components/BaseHeader.astro +22 -25
  7. package/components/Box.astro +20 -22
  8. package/components/Button.astro +80 -0
  9. package/components/Carousel.astro +456 -315
  10. package/components/CarouselPagination.astro +97 -76
  11. package/components/CarouselSlide.astro +10 -16
  12. package/components/Collapse.astro +125 -125
  13. package/components/CollapseIndicator.astro +20 -20
  14. package/components/Container.astro +27 -32
  15. package/components/Content.astro +20 -0
  16. package/components/CookieConsent.astro +47 -47
  17. package/components/CookieConsent.css +52 -52
  18. package/components/CookieConsentConfig.ts +208 -208
  19. package/components/Dialog.astro +342 -338
  20. package/components/DialogTrigger.astro +32 -32
  21. package/components/Document.astro +240 -225
  22. package/components/Frame.astro +32 -0
  23. package/components/GlassBorder.astro +104 -0
  24. package/components/GlassRefraction.astro +85 -0
  25. package/components/GradientBorder.astro +80 -0
  26. package/components/Grid.astro +32 -34
  27. package/components/GridCell.astro +28 -32
  28. package/components/Heading.astro +24 -24
  29. package/components/Icon.astro +29 -29
  30. package/components/Image.astro +100 -100
  31. package/components/Image.md +14 -14
  32. package/components/Link.astro +89 -99
  33. package/components/Main.astro +17 -17
  34. package/components/Meta.astro +65 -65
  35. package/components/Popover.astro +189 -189
  36. package/components/RichTextView.astro +28 -28
  37. package/components/Section.astro +26 -44
  38. package/components/TabButton.astro +32 -16
  39. package/components/TabPanel.astro +20 -19
  40. package/components/Tabs.astro +220 -147
  41. package/components/Video.astro +6 -4
  42. package/components/YearsSince.astro +20 -20
  43. package/components//344/270/211/345/261/202/346/250/241/345/236/213.md +5 -5
  44. package/components//344/270/273/351/242/230Token.md +198 -198
  45. package/components//345/216/237/345/255/220/345/206/273/347/273/223/346/270/205/345/215/225.md +270 -260
  46. package/components//347/211/251/346/226/231/346/270/205/345/215/225.md +599 -567
  47. package/index.ts +5 -5
  48. package/package.json +7 -7
  49. package/components/BaseBlock.astro +0 -34
  50. package/components/Motion.astro +0 -77
  51. package/components/Stack.astro +0 -31
@@ -1,315 +1,456 @@
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 paginationWrapper = root.querySelector("[data-carousel-pagination]");
100
- const paginationEl = root.querySelector(".carousel-pagination");
101
- const progressBar = root.querySelector(".carousel-progress-bar");
102
- const fractionCurrent = root.querySelector(".carousel-fraction-current");
103
- const fractionTotal = root.querySelector(".carousel-fraction-total");
104
- const bulletClass = paginationWrapper?.dataset.bulletClass || "";
105
- const bulletActiveClass = paginationWrapper?.dataset.bulletActiveClass || "";
106
- let bullets = [];
107
-
108
- const updateUI = () => {
109
- track.style.transform = `translateX(-${currentIndex * 100}%)`;
110
-
111
- bullets.forEach((b, i) => {
112
- const isActive = i === currentIndex;
113
- b.classList.toggle("carousel-bullet-active", isActive);
114
- if (bulletActiveClass) {
115
- bulletActiveClass.split(" ").filter(Boolean).forEach(cls => {
116
- b.classList.toggle(cls, isActive);
117
- });
118
- }
119
- b.setAttribute("aria-current", isActive ? "true" : "false");
120
- b.setAttribute("aria-selected", isActive ? "true" : "false");
121
- });
122
-
123
- if (progressBar) {
124
- const progress = ((currentIndex + 1) / slideCount) * 100;
125
- progressBar.style.width = `${progress}%`;
126
- }
127
-
128
- if (fractionCurrent) fractionCurrent.textContent = String(currentIndex + 1);
129
- if (fractionTotal) fractionTotal.textContent = String(slideCount);
130
-
131
- slides.forEach((slide, i) => {
132
- slide.setAttribute("aria-hidden", i !== currentIndex ? "true" : "false");
133
- slide.inert = i !== currentIndex;
134
- });
135
- };
136
-
137
- const goto = (index, animate = true) => {
138
- if (!config.loop) {
139
- index = Math.max(0, Math.min(index, slideCount - 1));
140
- } else {
141
- if (index < 0) index = slideCount - 1;
142
- if (index >= slideCount) index = 0;
143
- }
144
-
145
- if (!animate) {
146
- track.style.transitionDuration = "0ms";
147
- }
148
-
149
- currentIndex = index;
150
- updateUI();
151
-
152
- if (!animate) {
153
- requestAnimationFrame(() => {
154
- track.style.transitionDuration = `${config.transitionDuration}ms`;
155
- });
156
- }
157
-
158
- root.dispatchEvent(new CustomEvent("carousel:change", {
159
- detail: { index: currentIndex, slideCount },
160
- bubbles: true,
161
- }));
162
- };
163
-
164
- const next = () => goto(currentIndex + 1);
165
- const prev = () => goto(currentIndex - 1);
166
-
167
- const createBullets = () => {
168
- if (!paginationEl || config.paginationType !== "bullets") return;
169
- paginationEl.innerHTML = "";
170
- bullets = [];
171
-
172
- for (let i = 0; i < slideCount; i++) {
173
- const bullet = document.createElement("button");
174
- bullet.className = `carousel-bullet ${bulletClass}`.trim();
175
- bullet.type = "button";
176
- bullet.setAttribute("role", "tab");
177
- bullet.setAttribute("aria-label", `Go to slide ${i + 1}`);
178
- bullet.addEventListener("click", () => goto(i), { signal });
179
- paginationEl.appendChild(bullet);
180
- bullets.push(bullet);
181
- }
182
- };
183
-
184
- const prevBtn = root.querySelector(".carousel-prev");
185
- const nextBtn = root.querySelector(".carousel-next");
186
-
187
- prevBtn?.addEventListener("click", prev, { signal });
188
- nextBtn?.addEventListener("click", next, { signal });
189
-
190
- const stopAutoplay = () => {
191
- if (autoplayTimer) {
192
- clearInterval(autoplayTimer);
193
- autoplayTimer = undefined;
194
- }
195
- };
196
-
197
- const startAutoplay = () => {
198
- if (config.autoplay <= 0) return;
199
- stopAutoplay();
200
- autoplayTimer = setInterval(next, config.autoplay);
201
- };
202
-
203
- if (config.pauseOnHover) {
204
- root.addEventListener("mouseenter", stopAutoplay, { signal });
205
- root.addEventListener("mouseleave", startAutoplay, { signal });
206
- root.addEventListener("focusin", stopAutoplay, { signal });
207
- root.addEventListener("focusout", startAutoplay, { signal });
208
- }
209
-
210
- if (config.touchEnabled) {
211
- const handleTouchStart = (e) => {
212
- touchStartX = e.touches ? e.touches[0].clientX : e.clientX;
213
- touchEndX = touchStartX;
214
- isDragging = true;
215
- stopAutoplay();
216
- };
217
-
218
- const handleTouchMove = (e) => {
219
- if (!isDragging) return;
220
- touchEndX = e.touches ? e.touches[0].clientX : e.clientX;
221
- };
222
-
223
- const handleTouchEnd = () => {
224
- if (!isDragging) return;
225
- isDragging = false;
226
- const diff = touchStartX - touchEndX;
227
- const threshold = 50;
228
-
229
- if (Math.abs(diff) > threshold) {
230
- if (diff > 0) next();
231
- else prev();
232
- }
233
-
234
- startAutoplay();
235
- };
236
-
237
- viewport?.addEventListener("touchstart", handleTouchStart, { signal, passive: true });
238
- viewport?.addEventListener("touchmove", handleTouchMove, { signal, passive: true });
239
- viewport?.addEventListener("touchend", handleTouchEnd, { signal });
240
- viewport?.addEventListener("mousedown", handleTouchStart, { signal });
241
- viewport?.addEventListener("mousemove", handleTouchMove, { signal });
242
- viewport?.addEventListener("mouseup", handleTouchEnd, { signal });
243
- viewport?.addEventListener("mouseleave", handleTouchEnd, { signal });
244
- }
245
-
246
- root.addEventListener("keydown", (e) => {
247
- if (e.key === "ArrowLeft") { prev(); e.preventDefault(); }
248
- if (e.key === "ArrowRight") { next(); e.preventDefault(); }
249
- }, { signal });
250
-
251
- document.addEventListener("visibilitychange", () => {
252
- if (document.hidden) stopAutoplay();
253
- else startAutoplay();
254
- }, { signal });
255
-
256
- createBullets();
257
- goto(currentIndex, false);
258
- startAutoplay();
259
-
260
- const instance = { goto, next, prev, currentIndex: () => currentIndex, slideCount };
261
- getState().instance = instance;
262
-
263
- return () => {
264
- stopAutoplay();
265
- ac.abort();
266
- getState().instance = null;
267
- };
268
- };
269
-
270
- const boot = () => {
271
- const state = getState();
272
- state.cleanup?.();
273
- state.cleanup = initCarousel();
274
- };
275
-
276
- const setupGlobalListeners = () => {
277
- const onPageLoad = () => boot();
278
- const onAfterSwap = () => boot();
279
- const onBeforeSwap = () => getState().cleanup?.();
280
-
281
- document.addEventListener("astro:page-load", onPageLoad);
282
- document.addEventListener("astro:after-swap", onAfterSwap);
283
- window.addEventListener("astro:before-swap", onBeforeSwap);
284
-
285
- return () => {
286
- document.removeEventListener("astro:page-load", onPageLoad);
287
- document.removeEventListener("astro:after-swap", onAfterSwap);
288
- window.removeEventListener("astro:before-swap", onBeforeSwap);
289
- };
290
- };
291
-
292
- if (document.readyState === "loading") {
293
- document.addEventListener("DOMContentLoaded", boot, { once: true });
294
- } else {
295
- boot();
296
- }
297
-
298
- const state = getState();
299
- state.globalCleanup?.();
300
- state.globalCleanup = setupGlobalListeners();
301
- </script>
302
-
303
- <style>
304
- .carousel {
305
- --carousel-nav-color: #fff;
306
- --carousel-nav-bg: rgba(0, 0, 0, 0.3);
307
- --carousel-nav-bg-hover: rgba(0, 0, 0, 0.5);
308
- --carousel-nav-size: 44px;
309
- --carousel-nav-icon-size: 24px;
310
- --carousel-bullet-size: 10px;
311
- --carousel-bullet-gap: 8px;
312
- --carousel-bullet-bg: rgba(255, 255, 255, 0.4);
313
- --carousel-bullet-bg-active: #fff;
314
- }
315
- </style>
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.querySelectorAll("[data-carousel-slide]"));
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
+ const prevIndex = parseInt(root.dataset.carouselCurrent || "", 10);
83
+ let currentIndex = (prevIndex >= 0 && prevIndex < slideCount)
84
+ ? prevIndex
85
+ : Math.max(0, Math.min(config.initialSlide, slideCount - 1));
86
+ let touchStartX = 0;
87
+ let touchEndX = 0;
88
+ let isDragging = false;
89
+ let autoplayPaused = false;
90
+ let autoplayRafId = null;
91
+ let lastAutoplayTime = 0;
92
+
93
+ const ac = new AbortController();
94
+ const { signal } = ac;
95
+
96
+ const getSlideWidth = () => (viewport ? viewport.clientWidth : 0);
97
+
98
+ const syncSlideWidths = () => {
99
+ const w = getSlideWidth();
100
+ slides.forEach((slide) => {
101
+ slide.style.width = `${w}px`;
102
+ slide.style.minWidth = `${w}px`;
103
+ });
104
+ };
105
+
106
+ slides.forEach((slide, i) => {
107
+ slide.style.flexShrink = "0";
108
+ slide.setAttribute("role", "group");
109
+ slide.setAttribute("aria-roledescription", "slide");
110
+ slide.setAttribute("aria-label", `Slide ${i + 1} of ${slideCount}`);
111
+ });
112
+
113
+ syncSlideWidths();
114
+
115
+ const paginationWrapper = root.querySelector("[data-carousel-pagination]");
116
+ const paginationEl = root.querySelector(".carousel-pagination");
117
+ const progressBar = root.querySelector(".carousel-progress-bar");
118
+ const fractionCurrent = root.querySelector(".carousel-fraction-current");
119
+ const fractionTotal = root.querySelector(".carousel-fraction-total");
120
+ const bulletClass = paginationWrapper?.dataset.bulletClass || "";
121
+ const bulletActiveClass = paginationWrapper?.dataset.bulletActiveClass || "";
122
+ let bullets = [];
123
+
124
+ const updateUI = () => {
125
+ const w = getSlideWidth();
126
+ track.style.transform = `translateX(-${currentIndex * w}px)`;
127
+
128
+ bullets.forEach((b, i) => {
129
+ const isActive = i === currentIndex;
130
+ b.classList.toggle("carousel-bullet-active", isActive);
131
+ if (bulletActiveClass) {
132
+ bulletActiveClass.split(" ").filter(Boolean).forEach(cls => {
133
+ b.classList.toggle(cls, isActive);
134
+ });
135
+ }
136
+ b.setAttribute("aria-current", isActive ? "true" : "false");
137
+ b.setAttribute("aria-selected", isActive ? "true" : "false");
138
+ });
139
+
140
+ if (progressBar) {
141
+ const progress = ((currentIndex + 1) / slideCount) * 100;
142
+ progressBar.style.width = `${progress}%`;
143
+ }
144
+
145
+ if (fractionCurrent) fractionCurrent.textContent = String(currentIndex + 1);
146
+ if (fractionTotal) fractionTotal.textContent = String(slideCount);
147
+
148
+ slides.forEach((slide, i) => {
149
+ slide.setAttribute("aria-hidden", i !== currentIndex ? "true" : "false");
150
+ slide.inert = i !== currentIndex;
151
+ });
152
+ };
153
+
154
+ const refreshAos = async () => {
155
+ const w = window;
156
+ const state = w.__aos_state__;
157
+ if (!state) return;
158
+
159
+ const raf = () =>
160
+ new Promise((resolve) => requestAnimationFrame(() => resolve(undefined)));
161
+
162
+ await raf();
163
+ await raf();
164
+
165
+ const aos = state.aos || (await state.loading);
166
+ if (aos && typeof aos.refresh === "function") {
167
+ aos.refresh();
168
+ }
169
+ };
170
+
171
+ const replayAosInSlides = async () => {
172
+ const raf = () =>
173
+ new Promise((resolve) => requestAnimationFrame(() => resolve(undefined)));
174
+
175
+ slides.forEach((slide, i) => {
176
+ const nodes = slide.querySelectorAll("[data-aos]");
177
+ nodes.forEach((el) => {
178
+ el.classList.add("aos-init");
179
+ if (i !== currentIndex) {
180
+ el.classList.remove("aos-animate");
181
+ }
182
+ });
183
+ });
184
+
185
+ await raf();
186
+ await raf();
187
+
188
+ await new Promise((resolve) => setTimeout(resolve, 500));
189
+
190
+ const active = slides[currentIndex];
191
+ if (!active) return;
192
+ const nodes = active.querySelectorAll("[data-aos]");
193
+ nodes.forEach((el) => {
194
+ el.classList.remove("aos-animate");
195
+ // force reflow
196
+ void el.offsetWidth;
197
+
198
+ const delay = el.getAttribute("data-aos-delay");
199
+ if (delay && delay.trim()) {
200
+ el.style.transitionDelay = `${parseInt(delay, 10) || 0}ms`;
201
+ } else {
202
+ el.style.transitionDelay = "";
203
+ }
204
+
205
+ el.classList.add("aos-animate");
206
+ });
207
+ };
208
+
209
+ const goto = (index, animate = true) => {
210
+ if (!config.loop) {
211
+ index = Math.max(0, Math.min(index, slideCount - 1));
212
+ } else {
213
+ if (index < 0) index = slideCount - 1;
214
+ if (index >= slideCount) index = 0;
215
+ }
216
+
217
+ if (!animate) {
218
+ track.style.transitionDuration = "0ms";
219
+ }
220
+
221
+ currentIndex = index;
222
+ root.dataset.carouselCurrent = String(currentIndex);
223
+ updateUI();
224
+ refreshAos();
225
+ replayAosInSlides();
226
+
227
+ if (!animate) {
228
+ requestAnimationFrame(() => {
229
+ track.style.transitionDuration = `${config.transitionDuration}ms`;
230
+ });
231
+ }
232
+
233
+ root.dispatchEvent(new CustomEvent("carousel:change", {
234
+ detail: { index: currentIndex, slideCount },
235
+ bubbles: true,
236
+ }));
237
+ };
238
+
239
+ const next = () => goto(currentIndex + 1);
240
+ const prev = () => goto(currentIndex - 1);
241
+
242
+ const createBullets = () => {
243
+ if (!paginationEl || config.paginationType !== "bullets") return;
244
+
245
+ const existing = Array.from(
246
+ paginationEl.querySelectorAll("button.carousel-bullet"),
247
+ );
248
+
249
+ if (existing.length === slideCount) {
250
+ bullets = existing;
251
+ } else {
252
+ paginationEl.innerHTML = "";
253
+ bullets = [];
254
+
255
+ for (let i = 0; i < slideCount; i++) {
256
+ const bullet = document.createElement("button");
257
+ bullet.className = `carousel-bullet ${bulletClass}`.trim();
258
+ paginationEl.appendChild(bullet);
259
+ bullets.push(bullet);
260
+ }
261
+ }
262
+
263
+ const bulletClasses = bulletClass.split(" ").filter(Boolean);
264
+ bullets.forEach((bullet, i) => {
265
+ bullet.type = "button";
266
+ bullet.setAttribute("role", "tab");
267
+ bullet.setAttribute("aria-label", `Go to slide ${i + 1}`);
268
+
269
+ if (bulletClasses.length) {
270
+ bulletClasses.forEach((cls) => bullet.classList.add(cls));
271
+ }
272
+
273
+ bullet.addEventListener("click", () => goto(i), { signal });
274
+ });
275
+ };
276
+
277
+ const prevBtn = root.querySelector(".carousel-prev");
278
+ const nextBtn = root.querySelector(".carousel-next");
279
+
280
+ prevBtn?.addEventListener("click", prev, { signal });
281
+ nextBtn?.addEventListener("click", next, { signal });
282
+
283
+ const stopAutoplay = () => {
284
+ autoplayPaused = true;
285
+ if (autoplayRafId) {
286
+ cancelAnimationFrame(autoplayRafId);
287
+ autoplayRafId = null;
288
+ }
289
+ };
290
+
291
+ const autoplayLoop = (timestamp) => {
292
+ if (autoplayPaused || config.autoplay <= 0) {
293
+ autoplayRafId = null;
294
+ return;
295
+ }
296
+
297
+ if (!lastAutoplayTime) lastAutoplayTime = timestamp;
298
+
299
+ const elapsed = timestamp - lastAutoplayTime;
300
+ if (elapsed >= config.autoplay) {
301
+ lastAutoplayTime = timestamp;
302
+ next();
303
+ }
304
+
305
+ autoplayRafId = requestAnimationFrame(autoplayLoop);
306
+ };
307
+
308
+ const startAutoplay = () => {
309
+ if (config.autoplay <= 0) return;
310
+ stopAutoplay();
311
+ autoplayPaused = false;
312
+ lastAutoplayTime = 0;
313
+ autoplayRafId = requestAnimationFrame(autoplayLoop);
314
+ };
315
+
316
+ if (config.pauseOnHover) {
317
+ root.addEventListener("mouseenter", stopAutoplay, { signal });
318
+ root.addEventListener("mouseleave", startAutoplay, { signal });
319
+ root.addEventListener("focusin", stopAutoplay, { signal });
320
+ root.addEventListener("focusout", startAutoplay, { signal });
321
+ }
322
+
323
+ const onResize = () => {
324
+ syncSlideWidths();
325
+ track.style.transitionDuration = "0ms";
326
+ updateUI();
327
+ requestAnimationFrame(() => {
328
+ track.style.transitionDuration = `${config.transitionDuration}ms`;
329
+ });
330
+ };
331
+ window.addEventListener("resize", onResize, { signal });
332
+
333
+ if (config.touchEnabled) {
334
+ const handleTouchStart = (e) => {
335
+ touchStartX = e.touches ? e.touches[0].clientX : e.clientX;
336
+ touchEndX = touchStartX;
337
+ isDragging = true;
338
+ stopAutoplay();
339
+ };
340
+
341
+ const handleTouchMove = (e) => {
342
+ if (!isDragging) return;
343
+ touchEndX = e.touches ? e.touches[0].clientX : e.clientX;
344
+ };
345
+
346
+ const handleTouchEnd = () => {
347
+ if (!isDragging) return;
348
+ isDragging = false;
349
+ const diff = touchStartX - touchEndX;
350
+ const threshold = 50;
351
+
352
+ if (Math.abs(diff) > threshold) {
353
+ if (diff > 0) next();
354
+ else prev();
355
+ }
356
+
357
+ startAutoplay();
358
+ };
359
+
360
+ viewport?.addEventListener("touchstart", handleTouchStart, { signal, passive: true });
361
+ viewport?.addEventListener("touchmove", handleTouchMove, { signal, passive: true });
362
+ viewport?.addEventListener("touchend", handleTouchEnd, { signal });
363
+ viewport?.addEventListener("mousedown", handleTouchStart, { signal });
364
+ viewport?.addEventListener("mousemove", handleTouchMove, { signal });
365
+ viewport?.addEventListener("mouseup", handleTouchEnd, { signal });
366
+ viewport?.addEventListener("mouseleave", handleTouchEnd, { signal });
367
+ }
368
+
369
+ root.addEventListener("keydown", (e) => {
370
+ if (e.key === "ArrowLeft") { prev(); e.preventDefault(); }
371
+ if (e.key === "ArrowRight") { next(); e.preventDefault(); }
372
+ }, { signal });
373
+
374
+ document.addEventListener("visibilitychange", () => {
375
+ if (document.hidden) stopAutoplay();
376
+ else startAutoplay();
377
+ }, { signal });
378
+
379
+ let intersectionObserver = null;
380
+ if (typeof IntersectionObserver !== "undefined") {
381
+ intersectionObserver = new IntersectionObserver(
382
+ (entries) => {
383
+ const entry = entries[0];
384
+ if (!entry) return;
385
+ if (entry.isIntersecting) {
386
+ startAutoplay();
387
+ } else {
388
+ stopAutoplay();
389
+ }
390
+ },
391
+ { root: null, threshold: 0 }
392
+ );
393
+ intersectionObserver.observe(root);
394
+ }
395
+
396
+ createBullets();
397
+ goto(currentIndex, false);
398
+ startAutoplay();
399
+
400
+ const instance = { goto, next, prev, currentIndex: () => currentIndex, slideCount };
401
+ getState().instance = instance;
402
+
403
+ return () => {
404
+ stopAutoplay();
405
+ intersectionObserver?.disconnect();
406
+ ac.abort();
407
+ getState().instance = null;
408
+ };
409
+ };
410
+
411
+ const boot = () => {
412
+ const state = getState();
413
+ state.cleanup?.();
414
+ state.cleanup = initCarousel();
415
+ };
416
+
417
+ const setupGlobalListeners = () => {
418
+ const onPageLoad = () => boot();
419
+ const onAfterSwap = () => boot();
420
+ const onBeforeSwap = () => getState().cleanup?.();
421
+
422
+ document.addEventListener("astro:page-load", onPageLoad);
423
+ document.addEventListener("astro:after-swap", onAfterSwap);
424
+ window.addEventListener("astro:before-swap", onBeforeSwap);
425
+
426
+ return () => {
427
+ document.removeEventListener("astro:page-load", onPageLoad);
428
+ document.removeEventListener("astro:after-swap", onAfterSwap);
429
+ window.removeEventListener("astro:before-swap", onBeforeSwap);
430
+ };
431
+ };
432
+
433
+ if (document.readyState === "loading") {
434
+ document.addEventListener("DOMContentLoaded", boot, { once: true });
435
+ } else {
436
+ boot();
437
+ }
438
+
439
+ const state = getState();
440
+ state.globalCleanup?.();
441
+ state.globalCleanup = setupGlobalListeners();
442
+ </script>
443
+
444
+ <style>
445
+ .carousel {
446
+ --carousel-nav-color: #fff;
447
+ --carousel-nav-bg: rgba(0, 0, 0, 0.3);
448
+ --carousel-nav-bg-hover: rgba(0, 0, 0, 0.5);
449
+ --carousel-nav-size: 44px;
450
+ --carousel-nav-icon-size: 24px;
451
+ --carousel-bullet-size: 10px;
452
+ --carousel-bullet-gap: 8px;
453
+ --carousel-bullet-bg: rgba(255, 255, 255, 0.4);
454
+ --carousel-bullet-bg-active: #fff;
455
+ }
456
+ </style>