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