@qwanyx/carousel 0.1.0 → 0.1.2

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 CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
@@ -31,7 +41,7 @@ __export(index_exports, {
31
41
  module.exports = __toCommonJS(index_exports);
32
42
 
33
43
  // src/components/Carousel.tsx
34
- var import_react3 = require("react");
44
+ var import_react4 = require("react");
35
45
 
36
46
  // src/hooks/useCarousel.ts
37
47
  var import_react = require("react");
@@ -501,8 +511,252 @@ function SlideRenderer({ slide, isActive, slideIndex }) {
501
511
  );
502
512
  }
503
513
 
504
- // src/components/Carousel.tsx
514
+ // src/components/Thumbnails.tsx
515
+ var import_react3 = require("react");
505
516
  var import_jsx_runtime2 = require("react/jsx-runtime");
517
+ function getThumbnail(slide) {
518
+ if (slide.thumbnail) return slide.thumbnail;
519
+ for (const layer of slide.layers) {
520
+ for (const obj of layer.objects) {
521
+ if (obj.type === "image") {
522
+ return obj.src;
523
+ }
524
+ if (obj.type === "video" && obj.poster) {
525
+ return obj.poster || null;
526
+ }
527
+ }
528
+ }
529
+ if (slide.background?.type === "image") {
530
+ return slide.background.value;
531
+ }
532
+ return null;
533
+ }
534
+ function getSlideIcon(slide) {
535
+ for (const layer of slide.layers) {
536
+ for (const obj of layer.objects) {
537
+ switch (obj.type) {
538
+ case "video":
539
+ return "\u25B6";
540
+ case "audio":
541
+ return "\u{1F3B5}";
542
+ case "text":
543
+ return "\u{1F4DD}";
544
+ case "component":
545
+ return "\u26A1";
546
+ case "shape":
547
+ return "\u25FC";
548
+ case "group":
549
+ return "\u{1F4C1}";
550
+ }
551
+ }
552
+ }
553
+ return "\u{1F5BC}";
554
+ }
555
+ function Thumbnails({
556
+ slides,
557
+ currentIndex,
558
+ onSelect,
559
+ onReorder,
560
+ onDelete,
561
+ position = "bottom",
562
+ size = "medium",
563
+ theme = "light"
564
+ }) {
565
+ const isVertical = position === "left" || position === "right";
566
+ const [draggedIndex, setDraggedIndex] = (0, import_react3.useState)(null);
567
+ const [dragOverIndex, setDragOverIndex] = (0, import_react3.useState)(null);
568
+ const sizeMap = {
569
+ small: { width: 48, height: 48 },
570
+ medium: { width: 64, height: 64 },
571
+ large: { width: 80, height: 80 }
572
+ };
573
+ const { width, height } = sizeMap[size];
574
+ const handleDragStart = (e, index, thumbnailUrl) => {
575
+ setDraggedIndex(index);
576
+ e.dataTransfer.effectAllowed = "copyMove";
577
+ e.dataTransfer.setData("text/plain", index.toString());
578
+ if (thumbnailUrl) {
579
+ e.dataTransfer.setData("text/uri-list", thumbnailUrl);
580
+ e.dataTransfer.setData("application/x-thumbnail-url", thumbnailUrl);
581
+ }
582
+ const target = e.target;
583
+ requestAnimationFrame(() => {
584
+ target.style.opacity = "0.5";
585
+ });
586
+ };
587
+ const handleDragEnd = (e) => {
588
+ const target = e.target;
589
+ target.style.opacity = "1";
590
+ setDraggedIndex(null);
591
+ setDragOverIndex(null);
592
+ };
593
+ const handleDragOver = (e, index) => {
594
+ if (!onReorder || draggedIndex === null) return;
595
+ e.preventDefault();
596
+ e.dataTransfer.dropEffect = "move";
597
+ if (index !== draggedIndex) {
598
+ setDragOverIndex(index);
599
+ }
600
+ };
601
+ const handleDragLeave = () => {
602
+ setDragOverIndex(null);
603
+ };
604
+ const handleDrop = (e, toIndex) => {
605
+ e.preventDefault();
606
+ if (!onReorder || draggedIndex === null || draggedIndex === toIndex) return;
607
+ onReorder(draggedIndex, toIndex);
608
+ setDraggedIndex(null);
609
+ setDragOverIndex(null);
610
+ };
611
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
612
+ "div",
613
+ {
614
+ className: `qc-thumbnails qc-thumbnails--${position} qc-thumbnails--${theme}`,
615
+ style: {
616
+ display: "flex",
617
+ flexDirection: isVertical ? "column" : "row",
618
+ flexWrap: "wrap",
619
+ gap: "8px",
620
+ marginTop: "8px"
621
+ },
622
+ children: slides.map((slide, index) => {
623
+ const thumbnailUrl = getThumbnail(slide);
624
+ const isActive = index === currentIndex;
625
+ const isDragging = draggedIndex === index;
626
+ const isDragOver = dragOverIndex === index;
627
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
628
+ "div",
629
+ {
630
+ className: "qc-thumbnail-wrapper",
631
+ style: {
632
+ position: "relative"
633
+ },
634
+ onDragOver: (e) => handleDragOver(e, index),
635
+ onDragLeave: handleDragLeave,
636
+ onDrop: (e) => handleDrop(e, index),
637
+ children: [
638
+ isDragOver && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
639
+ "div",
640
+ {
641
+ style: {
642
+ position: "absolute",
643
+ left: "-4px",
644
+ top: 0,
645
+ bottom: 0,
646
+ width: "3px",
647
+ backgroundColor: "#3B82F6",
648
+ borderRadius: "2px"
649
+ }
650
+ }
651
+ ),
652
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
653
+ "button",
654
+ {
655
+ className: `qc-thumbnail ${isActive ? "qc-thumbnail--active" : ""}`,
656
+ draggable: true,
657
+ onDragStart: (e) => handleDragStart(e, index, thumbnailUrl),
658
+ onDragEnd: handleDragEnd,
659
+ onClick: () => onSelect(index),
660
+ style: {
661
+ position: "relative",
662
+ width: `${width}px`,
663
+ height: `${height}px`,
664
+ minWidth: `${width}px`,
665
+ minHeight: `${height}px`,
666
+ border: "none",
667
+ borderRadius: "5px",
668
+ overflow: "visible",
669
+ cursor: onReorder ? "grab" : "pointer",
670
+ opacity: isDragging ? 0.5 : 1,
671
+ transform: isActive ? "scale(1)" : "scale(0.95)",
672
+ transition: "all 0.2s ease",
673
+ backgroundColor: theme === "dark" ? "#333" : "#f5f5f5",
674
+ padding: 0,
675
+ boxShadow: isActive ? "0 0 0 2px #3B82F6, 0 2px 8px rgba(0,0,0,0.15)" : "0 1px 3px rgba(0,0,0,0.1)"
676
+ },
677
+ "aria-label": `Go to slide ${index + 1}: ${slide.name || ""}`,
678
+ children: [
679
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { width: "100%", height: "100%", borderRadius: "5px", overflow: "hidden" }, children: thumbnailUrl ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
680
+ "img",
681
+ {
682
+ src: thumbnailUrl,
683
+ alt: slide.name || `Slide ${index + 1}`,
684
+ draggable: false,
685
+ style: {
686
+ width: "100%",
687
+ height: "100%",
688
+ objectFit: "cover"
689
+ }
690
+ }
691
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
692
+ "div",
693
+ {
694
+ style: {
695
+ width: "100%",
696
+ height: "100%",
697
+ display: "flex",
698
+ alignItems: "center",
699
+ justifyContent: "center",
700
+ color: theme === "dark" ? "#888" : "#666",
701
+ fontSize: "20px"
702
+ },
703
+ children: getSlideIcon(slide)
704
+ }
705
+ ) }),
706
+ onDelete && isActive && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
707
+ "div",
708
+ {
709
+ onClick: (e) => {
710
+ e.stopPropagation();
711
+ onDelete(index);
712
+ },
713
+ style: {
714
+ position: "absolute",
715
+ top: "-6px",
716
+ right: "-6px",
717
+ width: "20px",
718
+ height: "20px",
719
+ borderRadius: "50%",
720
+ backgroundColor: "#EF4444",
721
+ color: "white",
722
+ display: "flex",
723
+ alignItems: "center",
724
+ justifyContent: "center",
725
+ fontSize: "14px",
726
+ fontWeight: "bold",
727
+ cursor: "pointer",
728
+ boxShadow: "0 1px 3px rgba(0,0,0,0.3)",
729
+ transition: "transform 0.15s ease"
730
+ },
731
+ onMouseEnter: (e) => e.currentTarget.style.transform = "scale(1.1)",
732
+ onMouseLeave: (e) => e.currentTarget.style.transform = "scale(1)",
733
+ title: "Delete",
734
+ children: "\xD7"
735
+ }
736
+ )
737
+ ]
738
+ }
739
+ )
740
+ ]
741
+ },
742
+ slide.id
743
+ );
744
+ })
745
+ }
746
+ );
747
+ }
748
+
749
+ // src/components/Carousel.tsx
750
+ var import_jsx_runtime3 = require("react/jsx-runtime");
751
+ function extractSlideUrl(slide) {
752
+ const layer = slide.layers?.[0];
753
+ if (!layer) return void 0;
754
+ const imageObj = layer.objects?.find((obj) => obj.type === "image");
755
+ if (imageObj && "src" in imageObj) {
756
+ return imageObj.src;
757
+ }
758
+ return void 0;
759
+ }
506
760
  var defaultNavigation = {
507
761
  arrows: true,
508
762
  dots: true,
@@ -511,7 +765,7 @@ var defaultNavigation = {
511
765
  touch: true,
512
766
  mouseWheel: false
513
767
  };
514
- var Carousel = (0, import_react3.forwardRef)(
768
+ var Carousel = (0, import_react4.forwardRef)(
515
769
  ({
516
770
  slides,
517
771
  initialIndex = 0,
@@ -526,11 +780,41 @@ var Carousel = (0, import_react3.forwardRef)(
526
780
  style,
527
781
  onSlideChange,
528
782
  onFullscreenChange,
529
- theme = "dark"
783
+ theme = "dark",
784
+ // Copy functionality
785
+ carouselInfo,
786
+ getSlideUrl,
787
+ onCopyCarousel,
788
+ onCopySlide,
789
+ // Thumbnail callbacks
790
+ onThumbnailReorder,
791
+ onThumbnailDelete
530
792
  }, 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);
793
+ const containerRef = (0, import_react4.useRef)(null);
794
+ const [isFullscreen, setIsFullscreen] = (0, import_react4.useState)(false);
795
+ const [touchStart, setTouchStart] = (0, import_react4.useState)(null);
796
+ const [copiedState, setCopiedState] = (0, import_react4.useState)(null);
797
+ const copyToClipboard = (0, import_react4.useCallback)(async (text, type) => {
798
+ try {
799
+ await navigator.clipboard.writeText(text);
800
+ setCopiedState(type);
801
+ setTimeout(() => setCopiedState(null), 2e3);
802
+ } catch (err) {
803
+ console.error("Failed to copy:", err);
804
+ }
805
+ }, []);
806
+ const handleCopyCarousel = (0, import_react4.useCallback)(() => {
807
+ if (!carouselInfo) return;
808
+ const jsonStr = JSON.stringify(carouselInfo);
809
+ copyToClipboard(jsonStr, "carousel");
810
+ onCopyCarousel?.(carouselInfo);
811
+ }, [carouselInfo, copyToClipboard, onCopyCarousel]);
812
+ const handleCopySlide = (0, import_react4.useCallback)((slide) => {
813
+ const url = getSlideUrl ? getSlideUrl(slide) : extractSlideUrl(slide);
814
+ if (!url) return;
815
+ copyToClipboard(url, "slide");
816
+ onCopySlide?.(slide, url);
817
+ }, [getSlideUrl, copyToClipboard, onCopySlide]);
534
818
  const nav = typeof navigation === "boolean" ? navigation ? defaultNavigation : {} : { ...defaultNavigation, ...navigation };
535
819
  const {
536
820
  currentIndex,
@@ -553,24 +837,24 @@ var Carousel = (0, import_react3.forwardRef)(
553
837
  autoplay,
554
838
  onSlideChange
555
839
  });
556
- const enterFullscreen = (0, import_react3.useCallback)(() => {
840
+ const enterFullscreen = (0, import_react4.useCallback)(() => {
557
841
  if (containerRef.current?.requestFullscreen) {
558
842
  containerRef.current.requestFullscreen();
559
843
  }
560
844
  }, []);
561
- const exitFullscreen = (0, import_react3.useCallback)(() => {
845
+ const exitFullscreen = (0, import_react4.useCallback)(() => {
562
846
  if (document.fullscreenElement) {
563
847
  document.exitFullscreen();
564
848
  }
565
849
  }, []);
566
- const toggleFullscreen = (0, import_react3.useCallback)(() => {
850
+ const toggleFullscreen = (0, import_react4.useCallback)(() => {
567
851
  if (isFullscreen) {
568
852
  exitFullscreen();
569
853
  } else {
570
854
  enterFullscreen();
571
855
  }
572
856
  }, [isFullscreen, enterFullscreen, exitFullscreen]);
573
- (0, import_react3.useEffect)(() => {
857
+ (0, import_react4.useEffect)(() => {
574
858
  const handleFullscreenChange = () => {
575
859
  const fs = document.fullscreenElement === containerRef.current;
576
860
  setIsFullscreen(fs);
@@ -581,7 +865,7 @@ var Carousel = (0, import_react3.forwardRef)(
581
865
  document.removeEventListener("fullscreenchange", handleFullscreenChange);
582
866
  };
583
867
  }, [onFullscreenChange]);
584
- (0, import_react3.useEffect)(() => {
868
+ (0, import_react4.useEffect)(() => {
585
869
  if (!nav.keyboard) return;
586
870
  const handleKeyDown = (e) => {
587
871
  switch (e.key) {
@@ -651,7 +935,7 @@ var Carousel = (0, import_react3.forwardRef)(
651
935
  }
652
936
  setTouchStart(null);
653
937
  };
654
- (0, import_react3.useImperativeHandle)(ref, () => ({
938
+ (0, import_react4.useImperativeHandle)(ref, () => ({
655
939
  next,
656
940
  prev,
657
941
  goTo,
@@ -679,200 +963,291 @@ var Carousel = (0, import_react3.forwardRef)(
679
963
  return base;
680
964
  }
681
965
  };
682
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
966
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
683
967
  "div",
684
968
  {
685
- ref: containerRef,
686
- className: `qc-carousel qc-carousel--${theme} ${isFullscreen ? "qc-carousel--fullscreen" : ""} ${className}`,
969
+ className: "qc-carousel-wrapper",
687
970
  style: {
688
- position: "relative",
689
971
  width: "100%",
690
- aspectRatio: isFullscreen ? "auto" : aspectRatio,
691
- overflow: "hidden",
692
- backgroundColor: theme === "dark" ? "#1a1a1a" : "#ffffff",
693
972
  ...style
694
973
  },
695
- onTouchStart: handleTouchStart,
696
- onTouchEnd: handleTouchEnd,
697
974
  children: [
698
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
975
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
699
976
  "div",
700
977
  {
701
- className: "qc-carousel__slides",
978
+ ref: containerRef,
979
+ className: `qc-carousel qc-carousel--${theme} ${isFullscreen ? "qc-carousel--fullscreen" : ""} ${className}`,
702
980
  style: {
703
- display: transition === "slide" ? "flex" : "block",
704
- width: transition === "slide" ? `${totalSlides * 100}%` : "100%",
705
- height: "100%",
706
- ...getTransitionStyle()
981
+ position: "relative",
982
+ width: "100%",
983
+ aspectRatio: isFullscreen ? "auto" : aspectRatio,
984
+ overflow: "hidden",
985
+ backgroundColor: theme === "dark" ? "#1a1a1a" : "#ffffff"
707
986
  },
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"
987
+ onTouchStart: handleTouchStart,
988
+ onTouchEnd: handleTouchEnd,
989
+ children: [
990
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
991
+ "div",
992
+ {
993
+ className: "qc-carousel__slides",
994
+ style: {
995
+ display: transition === "slide" ? "flex" : "block",
996
+ width: transition === "slide" ? `${totalSlides * 100}%` : "100%",
997
+ height: "100%",
998
+ ...getTransitionStyle()
999
+ },
1000
+ children: slides.map((slide, index) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1001
+ "div",
1002
+ {
1003
+ className: `qc-carousel__slide ${index === currentIndex ? "qc-carousel__slide--active" : ""}`,
1004
+ style: {
1005
+ width: transition === "slide" ? `${100 / totalSlides}%` : "100%",
1006
+ height: "100%",
1007
+ position: transition === "slide" ? "relative" : "absolute",
1008
+ top: 0,
1009
+ left: 0,
1010
+ opacity: transition === "fade" ? index === currentIndex ? 1 : 0 : 1,
1011
+ transition: `opacity ${transitionDuration}ms ease-in-out`,
1012
+ pointerEvents: index === currentIndex ? "auto" : "none"
1013
+ },
1014
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SlideRenderer, { slide, isActive: index === currentIndex, slideIndex: index })
1015
+ },
1016
+ slide.id
1017
+ ))
824
1018
  }
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"
1019
+ ),
1020
+ nav.arrows && totalSlides > 1 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1021
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1022
+ "button",
1023
+ {
1024
+ className: "qc-carousel__arrow qc-carousel__arrow--prev",
1025
+ onClick: () => {
1026
+ prev();
1027
+ handleInteraction();
1028
+ },
1029
+ disabled: !canGoPrev,
1030
+ "aria-label": "Previous slide",
1031
+ style: {
1032
+ position: "absolute",
1033
+ left: "16px",
1034
+ top: "50%",
1035
+ transform: "translateY(-50%)",
1036
+ width: "48px",
1037
+ height: "48px",
1038
+ borderRadius: "50%",
1039
+ border: "none",
1040
+ backgroundColor: "rgba(0,0,0,0.5)",
1041
+ color: "white",
1042
+ cursor: canGoPrev ? "pointer" : "not-allowed",
1043
+ opacity: canGoPrev ? 1 : 0.3,
1044
+ display: "flex",
1045
+ alignItems: "center",
1046
+ justifyContent: "center",
1047
+ fontSize: "24px",
1048
+ zIndex: 10
1049
+ },
1050
+ children: "\u2039"
1051
+ }
1052
+ ),
1053
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1054
+ "button",
1055
+ {
1056
+ className: "qc-carousel__arrow qc-carousel__arrow--next",
1057
+ onClick: () => {
1058
+ next();
1059
+ handleInteraction();
1060
+ },
1061
+ disabled: !canGoNext,
1062
+ "aria-label": "Next slide",
1063
+ style: {
1064
+ position: "absolute",
1065
+ right: "16px",
1066
+ top: "50%",
1067
+ transform: "translateY(-50%)",
1068
+ width: "48px",
1069
+ height: "48px",
1070
+ borderRadius: "50%",
1071
+ border: "none",
1072
+ backgroundColor: "rgba(0,0,0,0.5)",
1073
+ color: "white",
1074
+ cursor: canGoNext ? "pointer" : "not-allowed",
1075
+ opacity: canGoNext ? 1 : 0.3,
1076
+ display: "flex",
1077
+ alignItems: "center",
1078
+ justifyContent: "center",
1079
+ fontSize: "24px",
1080
+ zIndex: 10
1081
+ },
1082
+ children: "\u203A"
1083
+ }
1084
+ )
1085
+ ] }),
1086
+ nav.dots && totalSlides > 1 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1087
+ "div",
1088
+ {
1089
+ className: "qc-carousel__dots",
1090
+ style: {
1091
+ position: "absolute",
1092
+ bottom: "16px",
1093
+ left: "50%",
1094
+ transform: "translateX(-50%)",
1095
+ display: "flex",
1096
+ gap: "8px",
1097
+ zIndex: 10
1098
+ },
1099
+ children: slides.map((_, index) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1100
+ "button",
1101
+ {
1102
+ className: `qc-carousel__dot ${index === currentIndex ? "qc-carousel__dot--active" : ""}`,
1103
+ onClick: () => {
1104
+ goTo(index);
1105
+ handleInteraction();
1106
+ },
1107
+ "aria-label": `Go to slide ${index + 1}`,
1108
+ style: {
1109
+ width: "10px",
1110
+ height: "10px",
1111
+ borderRadius: "50%",
1112
+ border: "none",
1113
+ backgroundColor: index === currentIndex ? "#E67E22" : "rgba(255,255,255,0.5)",
1114
+ cursor: "pointer",
1115
+ transition: "all 0.2s ease"
1116
+ }
1117
+ },
1118
+ index
1119
+ ))
1120
+ }
1121
+ ),
1122
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1123
+ "div",
1124
+ {
1125
+ className: "qc-carousel__top-right-buttons",
1126
+ style: {
1127
+ position: "absolute",
1128
+ top: "16px",
1129
+ right: "16px",
1130
+ display: "flex",
1131
+ flexDirection: "column",
1132
+ alignItems: "center",
1133
+ gap: "8px",
1134
+ zIndex: 10
1135
+ },
1136
+ children: [
1137
+ allowFullscreen && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1138
+ "button",
1139
+ {
1140
+ className: "qc-carousel__fullscreen",
1141
+ onClick: toggleFullscreen,
1142
+ "aria-label": isFullscreen ? "Exit fullscreen" : "Enter fullscreen",
1143
+ style: {
1144
+ width: "40px",
1145
+ height: "40px",
1146
+ borderRadius: "8px",
1147
+ border: "none",
1148
+ backgroundColor: "rgba(0,0,0,0.5)",
1149
+ color: "white",
1150
+ cursor: "pointer",
1151
+ display: "flex",
1152
+ alignItems: "center",
1153
+ justifyContent: "center",
1154
+ fontSize: "18px"
1155
+ },
1156
+ children: isFullscreen ? "\u22A0" : "\u229E"
1157
+ }
1158
+ ),
1159
+ currentSlide && (getSlideUrl || extractSlideUrl(currentSlide)) && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1160
+ "button",
1161
+ {
1162
+ className: "qc-carousel__copy-slide",
1163
+ onClick: () => handleCopySlide(currentSlide),
1164
+ "aria-label": "Copy image URL",
1165
+ title: "Copy image URL",
1166
+ style: {
1167
+ width: "40px",
1168
+ height: "40px",
1169
+ borderRadius: "8px",
1170
+ border: "none",
1171
+ backgroundColor: copiedState === "slide" ? "rgba(34,197,94,0.8)" : "rgba(0,0,0,0.5)",
1172
+ color: "white",
1173
+ cursor: "pointer",
1174
+ display: "flex",
1175
+ alignItems: "center",
1176
+ justifyContent: "center",
1177
+ fontSize: "16px",
1178
+ transition: "background-color 0.2s ease"
1179
+ },
1180
+ children: copiedState === "slide" ? "\u2713" : "\u{1F517}"
1181
+ }
1182
+ )
1183
+ ]
1184
+ }
1185
+ ),
1186
+ carouselInfo && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1187
+ "button",
1188
+ {
1189
+ className: "qc-carousel__copy-carousel",
1190
+ onClick: handleCopyCarousel,
1191
+ "aria-label": "Copy carousel link",
1192
+ title: "Copy carousel link",
1193
+ style: {
1194
+ position: "absolute",
1195
+ top: "16px",
1196
+ right: allowFullscreen ? "72px" : "16px",
1197
+ width: "40px",
1198
+ height: "40px",
1199
+ borderRadius: "8px",
1200
+ border: "none",
1201
+ backgroundColor: copiedState === "carousel" ? "rgba(34,197,94,0.8)" : "rgba(0,0,0,0.5)",
1202
+ color: "white",
1203
+ cursor: "pointer",
1204
+ display: "flex",
1205
+ alignItems: "center",
1206
+ justifyContent: "center",
1207
+ fontSize: "16px",
1208
+ transition: "background-color 0.2s ease",
1209
+ zIndex: 10
1210
+ },
1211
+ children: copiedState === "carousel" ? "\u2713" : "\u{1F4CB}"
1212
+ }
1213
+ ),
1214
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1215
+ "div",
1216
+ {
1217
+ className: "qc-carousel__counter",
1218
+ style: {
1219
+ position: "absolute",
1220
+ top: "16px",
1221
+ left: "16px",
1222
+ padding: "4px 12px",
1223
+ borderRadius: "4px",
1224
+ backgroundColor: "rgba(0,0,0,0.5)",
1225
+ color: "white",
1226
+ fontSize: "14px",
1227
+ zIndex: 10
1228
+ },
1229
+ children: [
1230
+ currentIndex + 1,
1231
+ " / ",
1232
+ totalSlides
1233
+ ]
1234
+ }
1235
+ )
1236
+ ]
854
1237
  }
855
1238
  ),
856
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
857
- "div",
1239
+ nav.thumbnails && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1240
+ Thumbnails,
858
1241
  {
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
1242
+ slides,
1243
+ currentIndex,
1244
+ onSelect: (index) => {
1245
+ goTo(index);
1246
+ handleInteraction();
870
1247
  },
871
- children: [
872
- currentIndex + 1,
873
- " / ",
874
- totalSlides
875
- ]
1248
+ onReorder: onThumbnailReorder,
1249
+ onDelete: onThumbnailDelete,
1250
+ theme: theme === "auto" ? "light" : theme
876
1251
  }
877
1252
  )
878
1253
  ]
@@ -882,131 +1257,6 @@ var Carousel = (0, import_react3.forwardRef)(
882
1257
  );
883
1258
  Carousel.displayName = "Carousel";
884
1259
 
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
1260
  // src/types.ts
1011
1261
  function createSimpleSlide(id, objects, options) {
1012
1262
  return {
@@ -1049,6 +1299,81 @@ function createImageSlide(id, src, options) {
1049
1299
  }
1050
1300
 
1051
1301
  // src/index.ts
1302
+ var import_react5 = __toESM(require("react"));
1303
+ function MarkdownContent({ url, filename }) {
1304
+ const [content, setContent] = (0, import_react5.useState)("");
1305
+ const [loading, setLoading] = (0, import_react5.useState)(true);
1306
+ const [error, setError] = (0, import_react5.useState)(null);
1307
+ (0, import_react5.useEffect)(() => {
1308
+ fetch(url).then((res) => {
1309
+ if (!res.ok) throw new Error("Failed to load");
1310
+ return res.text();
1311
+ }).then((text) => {
1312
+ setContent(text);
1313
+ setLoading(false);
1314
+ }).catch((err) => {
1315
+ setError(err.message);
1316
+ setLoading(false);
1317
+ });
1318
+ }, [url]);
1319
+ const containerStyle = {
1320
+ width: "100%",
1321
+ height: "100%",
1322
+ display: "flex",
1323
+ flexDirection: "column",
1324
+ backgroundColor: "#f0f9ff",
1325
+ padding: "16px",
1326
+ overflow: "auto"
1327
+ };
1328
+ const centeredStyle = {
1329
+ ...containerStyle,
1330
+ alignItems: "center",
1331
+ justifyContent: "center"
1332
+ };
1333
+ const headerStyle = {
1334
+ display: "flex",
1335
+ alignItems: "center",
1336
+ gap: "8px",
1337
+ marginBottom: "12px",
1338
+ paddingBottom: "8px",
1339
+ borderBottom: "1px solid #bae6fd"
1340
+ };
1341
+ const preStyle = {
1342
+ flex: 1,
1343
+ fontSize: "14px",
1344
+ color: "#374151",
1345
+ whiteSpace: "pre-wrap",
1346
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
1347
+ overflow: "auto",
1348
+ margin: 0
1349
+ };
1350
+ if (loading) {
1351
+ return import_react5.default.createElement(
1352
+ "div",
1353
+ { style: centeredStyle },
1354
+ import_react5.default.createElement("span", { style: { color: "#9ca3af" } }, "Loading...")
1355
+ );
1356
+ }
1357
+ if (error) {
1358
+ return import_react5.default.createElement(
1359
+ "div",
1360
+ { style: centeredStyle },
1361
+ import_react5.default.createElement("span", { style: { color: "#ef4444" } }, "Failed to load markdown")
1362
+ );
1363
+ }
1364
+ const children = [];
1365
+ if (filename) {
1366
+ children.push(
1367
+ import_react5.default.createElement(
1368
+ "div",
1369
+ { key: "header", style: headerStyle },
1370
+ import_react5.default.createElement("span", { style: { color: "#0284c7", fontWeight: 500, fontSize: "14px" } }, "\u{1F4C4} " + filename)
1371
+ )
1372
+ );
1373
+ }
1374
+ children.push(import_react5.default.createElement("pre", { key: "content", style: preStyle }, content));
1375
+ return import_react5.default.createElement("div", { style: containerStyle }, ...children);
1376
+ }
1052
1377
  var createSlide = {
1053
1378
  /**
1054
1379
  * Create a simple image slide (single image, full slide)
@@ -1091,8 +1416,77 @@ var createSlide = {
1091
1416
  position: { x: 0, y: 0 },
1092
1417
  size: { width: 100, height: 100, unit: "percent" }
1093
1418
  }], options);
1419
+ },
1420
+ /**
1421
+ * Create a slide to display a markdown file
1422
+ */
1423
+ markdown: (id, url, options) => {
1424
+ const thumbnail = options?.thumbnail ?? markdownThumbnailSvg;
1425
+ return createSimpleSlide(id, [{
1426
+ id: `${id}-markdown`,
1427
+ type: "component",
1428
+ render: () => import_react5.default.createElement(MarkdownContent, { url, filename: options?.filename }),
1429
+ position: { x: 0, y: 0 },
1430
+ size: { width: 100, height: 100, unit: "percent" }
1431
+ }], { thumbnail });
1432
+ },
1433
+ /**
1434
+ * Create a slide to display an audio file
1435
+ */
1436
+ audio: (id, url, options) => {
1437
+ const thumbnail = options?.thumbnail ?? audioThumbnailSvg;
1438
+ return createSimpleSlide(id, [{
1439
+ id: `${id}-audio`,
1440
+ type: "component",
1441
+ render: () => import_react5.default.createElement(AudioContent, { url, filename: options?.filename }),
1442
+ position: { x: 0, y: 0 },
1443
+ size: { width: 100, height: 100, unit: "percent" }
1444
+ }], { thumbnail });
1094
1445
  }
1095
1446
  };
1447
+ var markdownThumbnailSvg = "data:image/svg+xml," + encodeURIComponent(
1448
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect fill="#e0f2fe" width="64" height="64"/><path fill="#0284c7" d="M12 20v24h40V20H12zm4 20V24h4l4 6 4-6h4v16h-4v-9l-4 6-4-6v9h-4zm24-8v8h-4l6 8 6-8h-4v-8h-4z"/></svg>'
1449
+ );
1450
+ var audioThumbnailSvg = "data:image/svg+xml," + encodeURIComponent(
1451
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect fill="#171717" width="64" height="64"/><path fill="#34d399" d="M20 24v16h4V24zm8-4v24h4V20zm8 8v8h4v-8zm8-4v16h4V24z"/></svg>'
1452
+ );
1453
+ function AudioContent({ url, filename }) {
1454
+ const containerStyle = {
1455
+ width: "100%",
1456
+ height: "100%",
1457
+ display: "flex",
1458
+ flexDirection: "column",
1459
+ alignItems: "center",
1460
+ justifyContent: "center",
1461
+ backgroundColor: "#171717",
1462
+ padding: "16px"
1463
+ };
1464
+ const iconStyle = {
1465
+ width: "48px",
1466
+ height: "48px",
1467
+ color: "#34d399",
1468
+ marginBottom: "12px"
1469
+ };
1470
+ const filenameStyle = {
1471
+ color: "#d1d5db",
1472
+ fontSize: "14px",
1473
+ marginBottom: "16px"
1474
+ };
1475
+ const children = [
1476
+ import_react5.default.createElement(
1477
+ "svg",
1478
+ { key: "icon", style: iconStyle, viewBox: "0 0 24 24", fill: "currentColor" },
1479
+ import_react5.default.createElement("path", { d: "M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z" })
1480
+ )
1481
+ ];
1482
+ if (filename) {
1483
+ children.push(import_react5.default.createElement("span", { key: "filename", style: filenameStyle }, filename));
1484
+ }
1485
+ children.push(
1486
+ import_react5.default.createElement("audio", { key: "audio", src: url, controls: true, style: { width: "100%", maxWidth: "280px" } })
1487
+ );
1488
+ return import_react5.default.createElement("div", { style: containerStyle }, ...children);
1489
+ }
1096
1490
  // Annotate the CommonJS export names for ESM import in node:
1097
1491
  0 && (module.exports = {
1098
1492
  Carousel,