@qwanyx/carousel 0.1.0

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/dist/index.mjs ADDED
@@ -0,0 +1,1079 @@
1
+ // src/components/Carousel.tsx
2
+ import {
3
+ forwardRef,
4
+ useImperativeHandle,
5
+ useRef as useRef3,
6
+ useState as useState2,
7
+ useCallback as useCallback2,
8
+ useEffect as useEffect3
9
+ } from "react";
10
+
11
+ // src/hooks/useCarousel.ts
12
+ import { useState, useCallback, useEffect, useRef } from "react";
13
+ function useCarousel({
14
+ slides,
15
+ initialIndex = 0,
16
+ loop = false,
17
+ autoplay = false,
18
+ onSlideChange
19
+ }) {
20
+ const [currentIndex, setCurrentIndex] = useState(initialIndex);
21
+ const [isPlaying, setIsPlaying] = useState(false);
22
+ const [direction, setDirection] = useState("next");
23
+ const autoplayTimerRef = useRef(null);
24
+ const interactionRef = useRef(false);
25
+ const totalSlides = slides.length;
26
+ const currentSlide = slides[currentIndex];
27
+ const autoplayOptions = typeof autoplay === "boolean" ? { enabled: autoplay, interval: 5e3, pauseOnHover: true, pauseOnInteraction: true } : { interval: 5e3, pauseOnHover: true, pauseOnInteraction: true, ...autoplay };
28
+ const next = useCallback(() => {
29
+ setDirection("next");
30
+ setCurrentIndex((prev2) => {
31
+ if (prev2 >= totalSlides - 1) {
32
+ return loop ? 0 : prev2;
33
+ }
34
+ return prev2 + 1;
35
+ });
36
+ }, [totalSlides, loop]);
37
+ const prev = useCallback(() => {
38
+ setDirection("prev");
39
+ setCurrentIndex((prev2) => {
40
+ if (prev2 <= 0) {
41
+ return loop ? totalSlides - 1 : prev2;
42
+ }
43
+ return prev2 - 1;
44
+ });
45
+ }, [totalSlides, loop]);
46
+ const goTo = useCallback((index) => {
47
+ if (index < 0 || index >= totalSlides) return;
48
+ setDirection(index > currentIndex ? "next" : "prev");
49
+ setCurrentIndex(index);
50
+ }, [currentIndex, totalSlides]);
51
+ const canGoNext = loop || currentIndex < totalSlides - 1;
52
+ const canGoPrev = loop || currentIndex > 0;
53
+ const play = useCallback(() => {
54
+ setIsPlaying(true);
55
+ }, []);
56
+ const pause = useCallback(() => {
57
+ setIsPlaying(false);
58
+ }, []);
59
+ const handleInteraction = useCallback(() => {
60
+ if (autoplayOptions.pauseOnInteraction) {
61
+ interactionRef.current = true;
62
+ setTimeout(() => {
63
+ interactionRef.current = false;
64
+ }, 3e3);
65
+ }
66
+ }, [autoplayOptions.pauseOnInteraction]);
67
+ useEffect(() => {
68
+ onSlideChange?.(currentIndex, slides[currentIndex]);
69
+ }, [currentIndex, slides, onSlideChange]);
70
+ useEffect(() => {
71
+ if (autoplayOptions.enabled) {
72
+ setIsPlaying(true);
73
+ }
74
+ }, [autoplayOptions.enabled]);
75
+ useEffect(() => {
76
+ if (!isPlaying || interactionRef.current) {
77
+ if (autoplayTimerRef.current) {
78
+ clearInterval(autoplayTimerRef.current);
79
+ autoplayTimerRef.current = null;
80
+ }
81
+ return;
82
+ }
83
+ const slideDuration = currentSlide?.duration || autoplayOptions.interval || 5e3;
84
+ autoplayTimerRef.current = setInterval(() => {
85
+ if (!interactionRef.current) {
86
+ next();
87
+ }
88
+ }, slideDuration);
89
+ return () => {
90
+ if (autoplayTimerRef.current) {
91
+ clearInterval(autoplayTimerRef.current);
92
+ }
93
+ };
94
+ }, [isPlaying, currentSlide, autoplayOptions.interval, next]);
95
+ return {
96
+ currentIndex,
97
+ currentSlide,
98
+ totalSlides,
99
+ direction,
100
+ isPlaying,
101
+ canGoNext,
102
+ canGoPrev,
103
+ next,
104
+ prev,
105
+ goTo,
106
+ play,
107
+ pause,
108
+ handleInteraction
109
+ };
110
+ }
111
+
112
+ // src/components/SlideRenderer.tsx
113
+ import { useRef as useRef2, useEffect as useEffect2 } from "react";
114
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
115
+ function getPositionStyle(position) {
116
+ return {
117
+ left: `${position.x}%`,
118
+ top: `${position.y}%`
119
+ };
120
+ }
121
+ function getSizeStyle(size) {
122
+ if (!size) return {};
123
+ const unit = size.unit === "px" ? "px" : "%";
124
+ return {
125
+ width: `${size.width}${unit}`,
126
+ height: `${size.height}${unit}`
127
+ };
128
+ }
129
+ function getTransformStyle(transform) {
130
+ if (!transform) return {};
131
+ const transforms = [];
132
+ if (transform.rotation) {
133
+ transforms.push(`rotate(${transform.rotation}deg)`);
134
+ }
135
+ if (transform.scaleX !== void 0 || transform.scaleY !== void 0) {
136
+ const sx = transform.scaleX ?? 1;
137
+ const sy = transform.scaleY ?? 1;
138
+ transforms.push(`scale(${sx}, ${sy})`);
139
+ }
140
+ if (transform.skewX || transform.skewY) {
141
+ transforms.push(`skew(${transform.skewX || 0}deg, ${transform.skewY || 0}deg)`);
142
+ }
143
+ const style = {};
144
+ if (transforms.length > 0) {
145
+ style.transform = transforms.join(" ");
146
+ }
147
+ if (transform.originX !== void 0 || transform.originY !== void 0) {
148
+ style.transformOrigin = `${transform.originX ?? 50}% ${transform.originY ?? 50}%`;
149
+ }
150
+ return style;
151
+ }
152
+ function getObjectStyle(obj) {
153
+ return {
154
+ position: "absolute",
155
+ ...getPositionStyle(obj.position),
156
+ ...getSizeStyle(obj.size),
157
+ ...getTransformStyle(obj.transform),
158
+ opacity: obj.opacity ?? 1,
159
+ visibility: obj.visible === false ? "hidden" : "visible",
160
+ pointerEvents: obj.locked ? "none" : "auto",
161
+ ...obj.style
162
+ };
163
+ }
164
+ function BackgroundRenderer({ background }) {
165
+ if (!background) return null;
166
+ const style = {
167
+ position: "absolute",
168
+ inset: 0,
169
+ zIndex: 0
170
+ };
171
+ switch (background.type) {
172
+ case "color":
173
+ style.backgroundColor = background.value;
174
+ break;
175
+ case "gradient":
176
+ style.background = background.value;
177
+ break;
178
+ case "image":
179
+ style.backgroundImage = `url(${background.value})`;
180
+ style.backgroundSize = "cover";
181
+ style.backgroundPosition = "center";
182
+ break;
183
+ case "video":
184
+ return /* @__PURE__ */ jsxs("div", { style, children: [
185
+ /* @__PURE__ */ jsx(
186
+ "video",
187
+ {
188
+ src: background.value,
189
+ autoPlay: true,
190
+ muted: true,
191
+ loop: true,
192
+ playsInline: true,
193
+ style: {
194
+ width: "100%",
195
+ height: "100%",
196
+ objectFit: "cover"
197
+ }
198
+ }
199
+ ),
200
+ background.overlay && /* @__PURE__ */ jsx(
201
+ "div",
202
+ {
203
+ style: {
204
+ position: "absolute",
205
+ inset: 0,
206
+ backgroundColor: background.overlay
207
+ }
208
+ }
209
+ )
210
+ ] });
211
+ }
212
+ if (background.opacity !== void 0) {
213
+ style.opacity = background.opacity;
214
+ }
215
+ if (background.blur) {
216
+ style.filter = `blur(${background.blur}px)`;
217
+ }
218
+ if (background.overlay) {
219
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
220
+ /* @__PURE__ */ jsx("div", { style }),
221
+ /* @__PURE__ */ jsx(
222
+ "div",
223
+ {
224
+ style: {
225
+ position: "absolute",
226
+ inset: 0,
227
+ backgroundColor: background.overlay,
228
+ zIndex: 0
229
+ }
230
+ }
231
+ )
232
+ ] });
233
+ }
234
+ return /* @__PURE__ */ jsx("div", { style });
235
+ }
236
+ function ImageObjectRenderer({
237
+ obj,
238
+ isActive
239
+ }) {
240
+ return /* @__PURE__ */ jsx("div", { className: `qc-object qc-object--image ${obj.className || ""}`, style: getObjectStyle(obj), children: /* @__PURE__ */ jsx(
241
+ "img",
242
+ {
243
+ src: obj.src,
244
+ alt: obj.alt || "",
245
+ loading: isActive ? "eager" : "lazy",
246
+ style: {
247
+ width: "100%",
248
+ height: "100%",
249
+ objectFit: obj.objectFit || "contain",
250
+ objectPosition: obj.objectPosition || "center",
251
+ borderRadius: obj.borderRadius,
252
+ border: obj.border,
253
+ boxShadow: obj.shadow,
254
+ filter: obj.filter
255
+ }
256
+ }
257
+ ) });
258
+ }
259
+ function VideoObjectRenderer({
260
+ obj,
261
+ isActive
262
+ }) {
263
+ const videoRef = useRef2(null);
264
+ useEffect2(() => {
265
+ if (videoRef.current) {
266
+ if (isActive && obj.autoplay) {
267
+ videoRef.current.play().catch(() => {
268
+ });
269
+ } else if (!isActive) {
270
+ videoRef.current.pause();
271
+ }
272
+ }
273
+ }, [isActive, obj.autoplay]);
274
+ return /* @__PURE__ */ jsx("div", { className: `qc-object qc-object--video ${obj.className || ""}`, style: getObjectStyle(obj), children: /* @__PURE__ */ jsx(
275
+ "video",
276
+ {
277
+ ref: videoRef,
278
+ src: obj.src,
279
+ poster: obj.poster,
280
+ muted: obj.muted ?? true,
281
+ loop: obj.loop ?? false,
282
+ controls: obj.controls ?? true,
283
+ playsInline: true,
284
+ style: {
285
+ width: "100%",
286
+ height: "100%",
287
+ objectFit: obj.objectFit || "contain"
288
+ }
289
+ }
290
+ ) });
291
+ }
292
+ function TextObjectRenderer({ obj }) {
293
+ const textStyle = {
294
+ fontSize: obj.fontSize ? `${obj.fontSize}px` : void 0,
295
+ fontFamily: obj.fontFamily,
296
+ fontWeight: obj.fontWeight,
297
+ fontStyle: obj.fontStyle,
298
+ textAlign: obj.textAlign,
299
+ color: obj.color,
300
+ backgroundColor: obj.backgroundColor,
301
+ lineHeight: obj.lineHeight,
302
+ letterSpacing: obj.letterSpacing ? `${obj.letterSpacing}px` : void 0,
303
+ textShadow: obj.textShadow,
304
+ padding: obj.padding,
305
+ display: "flex",
306
+ alignItems: obj.verticalAlign === "middle" ? "center" : obj.verticalAlign === "bottom" ? "flex-end" : "flex-start",
307
+ justifyContent: obj.textAlign === "center" ? "center" : obj.textAlign === "right" ? "flex-end" : "flex-start"
308
+ };
309
+ return /* @__PURE__ */ jsx(
310
+ "div",
311
+ {
312
+ className: `qc-object qc-object--text ${obj.className || ""}`,
313
+ style: { ...getObjectStyle(obj), ...textStyle },
314
+ children: obj.content
315
+ }
316
+ );
317
+ }
318
+ function AudioObjectRenderer({
319
+ obj,
320
+ isActive
321
+ }) {
322
+ const audioRef = useRef2(null);
323
+ useEffect2(() => {
324
+ if (audioRef.current) {
325
+ if (isActive && obj.autoplay) {
326
+ audioRef.current.play().catch(() => {
327
+ });
328
+ } else if (!isActive) {
329
+ audioRef.current.pause();
330
+ }
331
+ if (obj.volume !== void 0) {
332
+ audioRef.current.volume = obj.volume;
333
+ }
334
+ }
335
+ }, [isActive, obj.autoplay, obj.volume]);
336
+ if (!obj.showControls && !obj.showWaveform) {
337
+ return /* @__PURE__ */ jsx(
338
+ "audio",
339
+ {
340
+ ref: audioRef,
341
+ src: obj.src,
342
+ loop: obj.loop,
343
+ style: { display: "none" }
344
+ }
345
+ );
346
+ }
347
+ return /* @__PURE__ */ jsx("div", { className: `qc-object qc-object--audio ${obj.className || ""}`, style: getObjectStyle(obj), children: obj.showControls && /* @__PURE__ */ jsx("audio", { ref: audioRef, src: obj.src, loop: obj.loop, controls: true, style: { width: "100%" } }) });
348
+ }
349
+ function ShapeObjectRenderer({ obj }) {
350
+ const style = getObjectStyle(obj);
351
+ const shapeStyle = {
352
+ backgroundColor: obj.fill,
353
+ border: obj.stroke ? `${obj.strokeWidth || 1}px solid ${obj.stroke}` : void 0,
354
+ borderRadius: obj.borderRadius
355
+ };
356
+ switch (obj.shape) {
357
+ case "circle":
358
+ shapeStyle.borderRadius = "50%";
359
+ break;
360
+ case "ellipse":
361
+ shapeStyle.borderRadius = "50%";
362
+ break;
363
+ case "triangle":
364
+ return /* @__PURE__ */ jsx("div", { className: `qc-object qc-object--shape ${obj.className || ""}`, style, children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 100 100", preserveAspectRatio: "none", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsx(
365
+ "polygon",
366
+ {
367
+ points: "50,0 100,100 0,100",
368
+ fill: obj.fill || "transparent",
369
+ stroke: obj.stroke,
370
+ strokeWidth: obj.strokeWidth || 1
371
+ }
372
+ ) }) });
373
+ case "line":
374
+ case "arrow":
375
+ return /* @__PURE__ */ jsx("div", { className: `qc-object qc-object--shape ${obj.className || ""}`, style, children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 100 100", preserveAspectRatio: "none", style: { width: "100%", height: "100%" }, children: [
376
+ /* @__PURE__ */ jsx(
377
+ "line",
378
+ {
379
+ x1: obj.startPoint?.x ?? 0,
380
+ y1: obj.startPoint?.y ?? 50,
381
+ x2: obj.endPoint?.x ?? 100,
382
+ y2: obj.endPoint?.y ?? 50,
383
+ stroke: obj.stroke || "#000",
384
+ strokeWidth: obj.strokeWidth || 2,
385
+ markerEnd: obj.shape === "arrow" ? "url(#arrowhead)" : void 0
386
+ }
387
+ ),
388
+ obj.shape === "arrow" && /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("marker", { id: "arrowhead", markerWidth: "10", markerHeight: "7", refX: "9", refY: "3.5", orient: "auto", children: /* @__PURE__ */ jsx("polygon", { points: "0 0, 10 3.5, 0 7", fill: obj.stroke || "#000" }) }) })
389
+ ] }) });
390
+ }
391
+ return /* @__PURE__ */ jsx(
392
+ "div",
393
+ {
394
+ className: `qc-object qc-object--shape ${obj.className || ""}`,
395
+ style: { ...style, ...shapeStyle }
396
+ }
397
+ );
398
+ }
399
+ function ComponentObjectRenderer({
400
+ obj,
401
+ isActive,
402
+ slideIndex
403
+ }) {
404
+ return /* @__PURE__ */ jsx("div", { className: `qc-object qc-object--component ${obj.className || ""}`, style: getObjectStyle(obj), children: obj.render({ isActive, slideIndex }) });
405
+ }
406
+ function GroupObjectRenderer({
407
+ obj,
408
+ isActive,
409
+ slideIndex
410
+ }) {
411
+ return /* @__PURE__ */ jsx("div", { className: `qc-object qc-object--group ${obj.className || ""}`, style: getObjectStyle(obj), children: obj.children.map((child) => /* @__PURE__ */ jsx(ObjectRenderer, { obj: child, isActive, slideIndex }, child.id)) });
412
+ }
413
+ function ObjectRenderer({
414
+ obj,
415
+ isActive,
416
+ slideIndex
417
+ }) {
418
+ switch (obj.type) {
419
+ case "image":
420
+ return /* @__PURE__ */ jsx(ImageObjectRenderer, { obj, isActive });
421
+ case "video":
422
+ return /* @__PURE__ */ jsx(VideoObjectRenderer, { obj, isActive });
423
+ case "text":
424
+ return /* @__PURE__ */ jsx(TextObjectRenderer, { obj });
425
+ case "audio":
426
+ return /* @__PURE__ */ jsx(AudioObjectRenderer, { obj, isActive });
427
+ case "shape":
428
+ return /* @__PURE__ */ jsx(ShapeObjectRenderer, { obj });
429
+ case "component":
430
+ return /* @__PURE__ */ jsx(ComponentObjectRenderer, { obj, isActive, slideIndex });
431
+ case "group":
432
+ return /* @__PURE__ */ jsx(GroupObjectRenderer, { obj, isActive, slideIndex });
433
+ default:
434
+ return null;
435
+ }
436
+ }
437
+ function LayerRenderer({
438
+ layer,
439
+ isActive,
440
+ slideIndex
441
+ }) {
442
+ if (!layer.visible) return null;
443
+ const style = {
444
+ position: "absolute",
445
+ inset: 0,
446
+ opacity: layer.opacity,
447
+ mixBlendMode: layer.blendMode,
448
+ pointerEvents: layer.locked ? "none" : "auto"
449
+ };
450
+ return /* @__PURE__ */ jsx("div", { className: `qc-layer qc-layer--${layer.id}`, style, children: layer.objects.map((obj) => /* @__PURE__ */ jsx(ObjectRenderer, { obj, isActive, slideIndex }, obj.id)) });
451
+ }
452
+ function SlideRenderer({ slide, isActive, slideIndex }) {
453
+ return /* @__PURE__ */ jsxs(
454
+ "div",
455
+ {
456
+ className: "qc-slide",
457
+ style: {
458
+ position: "relative",
459
+ width: "100%",
460
+ height: "100%",
461
+ overflow: "hidden"
462
+ },
463
+ children: [
464
+ /* @__PURE__ */ jsx(BackgroundRenderer, { background: slide.background }),
465
+ slide.layers.map((layer, index) => /* @__PURE__ */ jsx(
466
+ LayerRenderer,
467
+ {
468
+ layer,
469
+ isActive,
470
+ slideIndex
471
+ },
472
+ layer.id
473
+ ))
474
+ ]
475
+ }
476
+ );
477
+ }
478
+
479
+ // src/components/Carousel.tsx
480
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
481
+ var defaultNavigation = {
482
+ arrows: true,
483
+ dots: true,
484
+ thumbnails: false,
485
+ keyboard: true,
486
+ touch: true,
487
+ mouseWheel: false
488
+ };
489
+ var Carousel = forwardRef(
490
+ ({
491
+ slides,
492
+ initialIndex = 0,
493
+ transition = "fade",
494
+ transitionDuration = 300,
495
+ navigation = true,
496
+ autoplay = false,
497
+ loop = false,
498
+ aspectRatio = "16/9",
499
+ allowFullscreen = true,
500
+ className = "",
501
+ style,
502
+ onSlideChange,
503
+ onFullscreenChange,
504
+ theme = "dark"
505
+ }, ref) => {
506
+ const containerRef = useRef3(null);
507
+ const [isFullscreen, setIsFullscreen] = useState2(false);
508
+ const [touchStart, setTouchStart] = useState2(null);
509
+ const nav = typeof navigation === "boolean" ? navigation ? defaultNavigation : {} : { ...defaultNavigation, ...navigation };
510
+ const {
511
+ currentIndex,
512
+ currentSlide,
513
+ totalSlides,
514
+ direction,
515
+ isPlaying,
516
+ canGoNext,
517
+ canGoPrev,
518
+ next,
519
+ prev,
520
+ goTo,
521
+ play,
522
+ pause,
523
+ handleInteraction
524
+ } = useCarousel({
525
+ slides,
526
+ initialIndex,
527
+ loop,
528
+ autoplay,
529
+ onSlideChange
530
+ });
531
+ const enterFullscreen = useCallback2(() => {
532
+ if (containerRef.current?.requestFullscreen) {
533
+ containerRef.current.requestFullscreen();
534
+ }
535
+ }, []);
536
+ const exitFullscreen = useCallback2(() => {
537
+ if (document.fullscreenElement) {
538
+ document.exitFullscreen();
539
+ }
540
+ }, []);
541
+ const toggleFullscreen = useCallback2(() => {
542
+ if (isFullscreen) {
543
+ exitFullscreen();
544
+ } else {
545
+ enterFullscreen();
546
+ }
547
+ }, [isFullscreen, enterFullscreen, exitFullscreen]);
548
+ useEffect3(() => {
549
+ const handleFullscreenChange = () => {
550
+ const fs = document.fullscreenElement === containerRef.current;
551
+ setIsFullscreen(fs);
552
+ onFullscreenChange?.(fs);
553
+ };
554
+ document.addEventListener("fullscreenchange", handleFullscreenChange);
555
+ return () => {
556
+ document.removeEventListener("fullscreenchange", handleFullscreenChange);
557
+ };
558
+ }, [onFullscreenChange]);
559
+ useEffect3(() => {
560
+ if (!nav.keyboard) return;
561
+ const handleKeyDown = (e) => {
562
+ switch (e.key) {
563
+ case "ArrowRight":
564
+ case "ArrowDown":
565
+ e.preventDefault();
566
+ next();
567
+ handleInteraction();
568
+ break;
569
+ case "ArrowLeft":
570
+ case "ArrowUp":
571
+ e.preventDefault();
572
+ prev();
573
+ handleInteraction();
574
+ break;
575
+ case "Escape":
576
+ if (isFullscreen) {
577
+ exitFullscreen();
578
+ }
579
+ break;
580
+ case "f":
581
+ case "F":
582
+ if (allowFullscreen) {
583
+ toggleFullscreen();
584
+ }
585
+ break;
586
+ case " ":
587
+ e.preventDefault();
588
+ if (isPlaying) {
589
+ pause();
590
+ } else {
591
+ play();
592
+ }
593
+ break;
594
+ }
595
+ };
596
+ window.addEventListener("keydown", handleKeyDown);
597
+ return () => window.removeEventListener("keydown", handleKeyDown);
598
+ }, [
599
+ nav.keyboard,
600
+ next,
601
+ prev,
602
+ handleInteraction,
603
+ isFullscreen,
604
+ exitFullscreen,
605
+ allowFullscreen,
606
+ toggleFullscreen,
607
+ isPlaying,
608
+ play,
609
+ pause
610
+ ]);
611
+ const handleTouchStart = (e) => {
612
+ if (!nav.touch) return;
613
+ setTouchStart(e.touches[0].clientX);
614
+ };
615
+ const handleTouchEnd = (e) => {
616
+ if (!nav.touch || touchStart === null) return;
617
+ const touchEnd = e.changedTouches[0].clientX;
618
+ const diff = touchStart - touchEnd;
619
+ if (Math.abs(diff) > 50) {
620
+ if (diff > 0) {
621
+ next();
622
+ } else {
623
+ prev();
624
+ }
625
+ handleInteraction();
626
+ }
627
+ setTouchStart(null);
628
+ };
629
+ useImperativeHandle(ref, () => ({
630
+ next,
631
+ prev,
632
+ goTo,
633
+ getCurrentIndex: () => currentIndex,
634
+ getCurrentSlide: () => currentSlide,
635
+ enterFullscreen,
636
+ exitFullscreen,
637
+ toggleFullscreen,
638
+ play,
639
+ pause,
640
+ isPlaying: () => isPlaying
641
+ }));
642
+ const getTransitionStyle = () => {
643
+ const base = {
644
+ transition: `all ${transitionDuration}ms ease-in-out`
645
+ };
646
+ switch (transition) {
647
+ case "slide":
648
+ return {
649
+ ...base,
650
+ transform: `translateX(-${currentIndex * 100}%)`
651
+ };
652
+ case "fade":
653
+ default:
654
+ return base;
655
+ }
656
+ };
657
+ return /* @__PURE__ */ jsxs2(
658
+ "div",
659
+ {
660
+ ref: containerRef,
661
+ className: `qc-carousel qc-carousel--${theme} ${isFullscreen ? "qc-carousel--fullscreen" : ""} ${className}`,
662
+ style: {
663
+ position: "relative",
664
+ width: "100%",
665
+ aspectRatio: isFullscreen ? "auto" : aspectRatio,
666
+ overflow: "hidden",
667
+ backgroundColor: theme === "dark" ? "#1a1a1a" : "#ffffff",
668
+ ...style
669
+ },
670
+ onTouchStart: handleTouchStart,
671
+ onTouchEnd: handleTouchEnd,
672
+ children: [
673
+ /* @__PURE__ */ jsx2(
674
+ "div",
675
+ {
676
+ className: "qc-carousel__slides",
677
+ style: {
678
+ display: transition === "slide" ? "flex" : "block",
679
+ width: transition === "slide" ? `${totalSlides * 100}%` : "100%",
680
+ height: "100%",
681
+ ...getTransitionStyle()
682
+ },
683
+ children: slides.map((slide, index) => /* @__PURE__ */ jsx2(
684
+ "div",
685
+ {
686
+ className: `qc-carousel__slide ${index === currentIndex ? "qc-carousel__slide--active" : ""}`,
687
+ style: {
688
+ width: transition === "slide" ? `${100 / totalSlides}%` : "100%",
689
+ height: "100%",
690
+ position: transition === "slide" ? "relative" : "absolute",
691
+ top: 0,
692
+ left: 0,
693
+ opacity: transition === "fade" ? index === currentIndex ? 1 : 0 : 1,
694
+ transition: `opacity ${transitionDuration}ms ease-in-out`,
695
+ pointerEvents: index === currentIndex ? "auto" : "none"
696
+ },
697
+ children: /* @__PURE__ */ jsx2(SlideRenderer, { slide, isActive: index === currentIndex, slideIndex: index })
698
+ },
699
+ slide.id
700
+ ))
701
+ }
702
+ ),
703
+ nav.arrows && totalSlides > 1 && /* @__PURE__ */ jsxs2(Fragment2, { children: [
704
+ /* @__PURE__ */ jsx2(
705
+ "button",
706
+ {
707
+ className: "qc-carousel__arrow qc-carousel__arrow--prev",
708
+ onClick: () => {
709
+ prev();
710
+ handleInteraction();
711
+ },
712
+ disabled: !canGoPrev,
713
+ "aria-label": "Previous slide",
714
+ style: {
715
+ position: "absolute",
716
+ left: "16px",
717
+ top: "50%",
718
+ transform: "translateY(-50%)",
719
+ width: "48px",
720
+ height: "48px",
721
+ borderRadius: "50%",
722
+ border: "none",
723
+ backgroundColor: "rgba(0,0,0,0.5)",
724
+ color: "white",
725
+ cursor: canGoPrev ? "pointer" : "not-allowed",
726
+ opacity: canGoPrev ? 1 : 0.3,
727
+ display: "flex",
728
+ alignItems: "center",
729
+ justifyContent: "center",
730
+ fontSize: "24px",
731
+ zIndex: 10
732
+ },
733
+ children: "\u2039"
734
+ }
735
+ ),
736
+ /* @__PURE__ */ jsx2(
737
+ "button",
738
+ {
739
+ className: "qc-carousel__arrow qc-carousel__arrow--next",
740
+ onClick: () => {
741
+ next();
742
+ handleInteraction();
743
+ },
744
+ disabled: !canGoNext,
745
+ "aria-label": "Next slide",
746
+ style: {
747
+ position: "absolute",
748
+ right: "16px",
749
+ top: "50%",
750
+ transform: "translateY(-50%)",
751
+ width: "48px",
752
+ height: "48px",
753
+ borderRadius: "50%",
754
+ border: "none",
755
+ backgroundColor: "rgba(0,0,0,0.5)",
756
+ color: "white",
757
+ cursor: canGoNext ? "pointer" : "not-allowed",
758
+ opacity: canGoNext ? 1 : 0.3,
759
+ display: "flex",
760
+ alignItems: "center",
761
+ justifyContent: "center",
762
+ fontSize: "24px",
763
+ zIndex: 10
764
+ },
765
+ children: "\u203A"
766
+ }
767
+ )
768
+ ] }),
769
+ nav.dots && totalSlides > 1 && /* @__PURE__ */ jsx2(
770
+ "div",
771
+ {
772
+ className: "qc-carousel__dots",
773
+ style: {
774
+ position: "absolute",
775
+ bottom: "16px",
776
+ left: "50%",
777
+ transform: "translateX(-50%)",
778
+ display: "flex",
779
+ gap: "8px",
780
+ zIndex: 10
781
+ },
782
+ children: slides.map((_, index) => /* @__PURE__ */ jsx2(
783
+ "button",
784
+ {
785
+ className: `qc-carousel__dot ${index === currentIndex ? "qc-carousel__dot--active" : ""}`,
786
+ onClick: () => {
787
+ goTo(index);
788
+ handleInteraction();
789
+ },
790
+ "aria-label": `Go to slide ${index + 1}`,
791
+ style: {
792
+ width: "10px",
793
+ height: "10px",
794
+ borderRadius: "50%",
795
+ border: "none",
796
+ backgroundColor: index === currentIndex ? "#E67E22" : "rgba(255,255,255,0.5)",
797
+ cursor: "pointer",
798
+ transition: "all 0.2s ease"
799
+ }
800
+ },
801
+ index
802
+ ))
803
+ }
804
+ ),
805
+ allowFullscreen && /* @__PURE__ */ jsx2(
806
+ "button",
807
+ {
808
+ className: "qc-carousel__fullscreen",
809
+ onClick: toggleFullscreen,
810
+ "aria-label": isFullscreen ? "Exit fullscreen" : "Enter fullscreen",
811
+ style: {
812
+ position: "absolute",
813
+ top: "16px",
814
+ right: "16px",
815
+ width: "40px",
816
+ height: "40px",
817
+ borderRadius: "8px",
818
+ border: "none",
819
+ backgroundColor: "rgba(0,0,0,0.5)",
820
+ color: "white",
821
+ cursor: "pointer",
822
+ display: "flex",
823
+ alignItems: "center",
824
+ justifyContent: "center",
825
+ fontSize: "18px",
826
+ zIndex: 10
827
+ },
828
+ children: isFullscreen ? "\u22A0" : "\u229E"
829
+ }
830
+ ),
831
+ /* @__PURE__ */ jsxs2(
832
+ "div",
833
+ {
834
+ className: "qc-carousel__counter",
835
+ style: {
836
+ position: "absolute",
837
+ top: "16px",
838
+ left: "16px",
839
+ padding: "4px 12px",
840
+ borderRadius: "4px",
841
+ backgroundColor: "rgba(0,0,0,0.5)",
842
+ color: "white",
843
+ fontSize: "14px",
844
+ zIndex: 10
845
+ },
846
+ children: [
847
+ currentIndex + 1,
848
+ " / ",
849
+ totalSlides
850
+ ]
851
+ }
852
+ )
853
+ ]
854
+ }
855
+ );
856
+ }
857
+ );
858
+ Carousel.displayName = "Carousel";
859
+
860
+ // src/components/Thumbnails.tsx
861
+ import { jsx as jsx3 } from "react/jsx-runtime";
862
+ function getThumbnail(slide) {
863
+ if (slide.thumbnail) return slide.thumbnail;
864
+ for (const layer of slide.layers) {
865
+ for (const obj of layer.objects) {
866
+ if (obj.type === "image") {
867
+ return obj.src;
868
+ }
869
+ if (obj.type === "video" && obj.poster) {
870
+ return obj.poster || null;
871
+ }
872
+ }
873
+ }
874
+ if (slide.background?.type === "image") {
875
+ return slide.background.value;
876
+ }
877
+ return null;
878
+ }
879
+ function getSlideIcon(slide) {
880
+ for (const layer of slide.layers) {
881
+ for (const obj of layer.objects) {
882
+ switch (obj.type) {
883
+ case "video":
884
+ return "\u25B6";
885
+ case "audio":
886
+ return "\u{1F3B5}";
887
+ case "text":
888
+ return "\u{1F4DD}";
889
+ case "component":
890
+ return "\u26A1";
891
+ case "shape":
892
+ return "\u25FC";
893
+ case "group":
894
+ return "\u{1F4C1}";
895
+ }
896
+ }
897
+ }
898
+ return "\u{1F5BC}";
899
+ }
900
+ function Thumbnails({
901
+ slides,
902
+ currentIndex,
903
+ onSelect,
904
+ position = "bottom",
905
+ size = "medium",
906
+ theme = "dark"
907
+ }) {
908
+ const isVertical = position === "left" || position === "right";
909
+ const sizeMap = {
910
+ small: { width: 48, height: 36 },
911
+ medium: { width: 80, height: 60 },
912
+ large: { width: 120, height: 90 }
913
+ };
914
+ const { width, height } = sizeMap[size];
915
+ return /* @__PURE__ */ jsx3(
916
+ "div",
917
+ {
918
+ className: `qc-thumbnails qc-thumbnails--${position} qc-thumbnails--${theme}`,
919
+ style: {
920
+ display: "flex",
921
+ flexDirection: isVertical ? "column" : "row",
922
+ gap: "8px",
923
+ padding: "8px",
924
+ overflowX: isVertical ? "hidden" : "auto",
925
+ overflowY: isVertical ? "auto" : "hidden",
926
+ backgroundColor: theme === "dark" ? "rgba(0,0,0,0.8)" : "rgba(255,255,255,0.9)"
927
+ },
928
+ children: slides.map((slide, index) => {
929
+ const thumbnailUrl = getThumbnail(slide);
930
+ const isActive = index === currentIndex;
931
+ return /* @__PURE__ */ jsx3(
932
+ "button",
933
+ {
934
+ className: `qc-thumbnail ${isActive ? "qc-thumbnail--active" : ""}`,
935
+ onClick: () => onSelect(index),
936
+ style: {
937
+ width: `${width}px`,
938
+ height: `${height}px`,
939
+ minWidth: `${width}px`,
940
+ minHeight: `${height}px`,
941
+ border: isActive ? "2px solid #E67E22" : "2px solid transparent",
942
+ borderRadius: "4px",
943
+ overflow: "hidden",
944
+ cursor: "pointer",
945
+ opacity: isActive ? 1 : 0.6,
946
+ transition: "all 0.2s ease",
947
+ backgroundColor: theme === "dark" ? "#333" : "#eee",
948
+ padding: 0
949
+ },
950
+ "aria-label": `Go to slide ${index + 1}: ${slide.name || ""}`,
951
+ children: thumbnailUrl ? /* @__PURE__ */ jsx3(
952
+ "img",
953
+ {
954
+ src: thumbnailUrl,
955
+ alt: slide.name || `Slide ${index + 1}`,
956
+ style: {
957
+ width: "100%",
958
+ height: "100%",
959
+ objectFit: "cover"
960
+ }
961
+ }
962
+ ) : /* @__PURE__ */ jsx3(
963
+ "div",
964
+ {
965
+ style: {
966
+ width: "100%",
967
+ height: "100%",
968
+ display: "flex",
969
+ alignItems: "center",
970
+ justifyContent: "center",
971
+ color: theme === "dark" ? "#888" : "#666",
972
+ fontSize: "12px"
973
+ },
974
+ children: getSlideIcon(slide)
975
+ }
976
+ )
977
+ },
978
+ slide.id
979
+ );
980
+ })
981
+ }
982
+ );
983
+ }
984
+
985
+ // src/types.ts
986
+ function createSimpleSlide(id, objects, options) {
987
+ return {
988
+ id,
989
+ layers: [{
990
+ id: "default",
991
+ name: "Default",
992
+ visible: true,
993
+ locked: false,
994
+ opacity: 1,
995
+ objects
996
+ }],
997
+ ...options
998
+ };
999
+ }
1000
+ function createImageSlide(id, src, options) {
1001
+ const objects = [
1002
+ {
1003
+ id: `${id}-image`,
1004
+ type: "image",
1005
+ src,
1006
+ position: { x: 0, y: 0 },
1007
+ size: { width: 100, height: 100, unit: "percent" },
1008
+ objectFit: options?.objectFit || "contain"
1009
+ }
1010
+ ];
1011
+ if (options?.title) {
1012
+ objects.push({
1013
+ id: `${id}-title`,
1014
+ type: "text",
1015
+ content: options.title,
1016
+ position: { x: 50, y: 90 },
1017
+ fontSize: 24,
1018
+ color: "white",
1019
+ textAlign: "center",
1020
+ textShadow: "0 2px 4px rgba(0,0,0,0.5)"
1021
+ });
1022
+ }
1023
+ return createSimpleSlide(id, objects, { background: options?.background });
1024
+ }
1025
+
1026
+ // src/index.ts
1027
+ var createSlide = {
1028
+ /**
1029
+ * Create a simple image slide (single image, full slide)
1030
+ */
1031
+ image: (id, src, options) => {
1032
+ return createImageSlide(id, src, options);
1033
+ },
1034
+ /**
1035
+ * Create a video slide
1036
+ */
1037
+ video: (id, src, options) => {
1038
+ const videoObj = {
1039
+ id: `${id}-video`,
1040
+ type: "video",
1041
+ src,
1042
+ position: { x: 0, y: 0 },
1043
+ size: { width: 100, height: 100, unit: "percent" },
1044
+ autoplay: options?.autoplay ?? false,
1045
+ muted: options?.muted ?? true,
1046
+ loop: options?.loop ?? false,
1047
+ controls: options?.controls ?? true,
1048
+ poster: options?.poster
1049
+ };
1050
+ return createSimpleSlide(id, [videoObj]);
1051
+ },
1052
+ /**
1053
+ * Create a slide with custom objects
1054
+ */
1055
+ custom: (id, objects, options) => {
1056
+ return createSimpleSlide(id, objects, options);
1057
+ },
1058
+ /**
1059
+ * Create a slide with a React component
1060
+ */
1061
+ component: (id, render, options) => {
1062
+ return createSimpleSlide(id, [{
1063
+ id: `${id}-component`,
1064
+ type: "component",
1065
+ render,
1066
+ position: { x: 0, y: 0 },
1067
+ size: { width: 100, height: 100, unit: "percent" }
1068
+ }], options);
1069
+ }
1070
+ };
1071
+ export {
1072
+ Carousel,
1073
+ SlideRenderer,
1074
+ Thumbnails,
1075
+ createImageSlide,
1076
+ createSimpleSlide,
1077
+ createSlide,
1078
+ useCarousel
1079
+ };