@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.mjs CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  forwardRef,
4
4
  useImperativeHandle,
5
5
  useRef as useRef3,
6
- useState as useState2,
6
+ useState as useState3,
7
7
  useCallback as useCallback2,
8
8
  useEffect as useEffect3
9
9
  } from "react";
@@ -476,8 +476,252 @@ function SlideRenderer({ slide, isActive, slideIndex }) {
476
476
  );
477
477
  }
478
478
 
479
+ // src/components/Thumbnails.tsx
480
+ import { useState as useState2 } from "react";
481
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
482
+ function getThumbnail(slide) {
483
+ if (slide.thumbnail) return slide.thumbnail;
484
+ for (const layer of slide.layers) {
485
+ for (const obj of layer.objects) {
486
+ if (obj.type === "image") {
487
+ return obj.src;
488
+ }
489
+ if (obj.type === "video" && obj.poster) {
490
+ return obj.poster || null;
491
+ }
492
+ }
493
+ }
494
+ if (slide.background?.type === "image") {
495
+ return slide.background.value;
496
+ }
497
+ return null;
498
+ }
499
+ function getSlideIcon(slide) {
500
+ for (const layer of slide.layers) {
501
+ for (const obj of layer.objects) {
502
+ switch (obj.type) {
503
+ case "video":
504
+ return "\u25B6";
505
+ case "audio":
506
+ return "\u{1F3B5}";
507
+ case "text":
508
+ return "\u{1F4DD}";
509
+ case "component":
510
+ return "\u26A1";
511
+ case "shape":
512
+ return "\u25FC";
513
+ case "group":
514
+ return "\u{1F4C1}";
515
+ }
516
+ }
517
+ }
518
+ return "\u{1F5BC}";
519
+ }
520
+ function Thumbnails({
521
+ slides,
522
+ currentIndex,
523
+ onSelect,
524
+ onReorder,
525
+ onDelete,
526
+ position = "bottom",
527
+ size = "medium",
528
+ theme = "light"
529
+ }) {
530
+ const isVertical = position === "left" || position === "right";
531
+ const [draggedIndex, setDraggedIndex] = useState2(null);
532
+ const [dragOverIndex, setDragOverIndex] = useState2(null);
533
+ const sizeMap = {
534
+ small: { width: 48, height: 48 },
535
+ medium: { width: 64, height: 64 },
536
+ large: { width: 80, height: 80 }
537
+ };
538
+ const { width, height } = sizeMap[size];
539
+ const handleDragStart = (e, index, thumbnailUrl) => {
540
+ setDraggedIndex(index);
541
+ e.dataTransfer.effectAllowed = "copyMove";
542
+ e.dataTransfer.setData("text/plain", index.toString());
543
+ if (thumbnailUrl) {
544
+ e.dataTransfer.setData("text/uri-list", thumbnailUrl);
545
+ e.dataTransfer.setData("application/x-thumbnail-url", thumbnailUrl);
546
+ }
547
+ const target = e.target;
548
+ requestAnimationFrame(() => {
549
+ target.style.opacity = "0.5";
550
+ });
551
+ };
552
+ const handleDragEnd = (e) => {
553
+ const target = e.target;
554
+ target.style.opacity = "1";
555
+ setDraggedIndex(null);
556
+ setDragOverIndex(null);
557
+ };
558
+ const handleDragOver = (e, index) => {
559
+ if (!onReorder || draggedIndex === null) return;
560
+ e.preventDefault();
561
+ e.dataTransfer.dropEffect = "move";
562
+ if (index !== draggedIndex) {
563
+ setDragOverIndex(index);
564
+ }
565
+ };
566
+ const handleDragLeave = () => {
567
+ setDragOverIndex(null);
568
+ };
569
+ const handleDrop = (e, toIndex) => {
570
+ e.preventDefault();
571
+ if (!onReorder || draggedIndex === null || draggedIndex === toIndex) return;
572
+ onReorder(draggedIndex, toIndex);
573
+ setDraggedIndex(null);
574
+ setDragOverIndex(null);
575
+ };
576
+ return /* @__PURE__ */ jsx2(
577
+ "div",
578
+ {
579
+ className: `qc-thumbnails qc-thumbnails--${position} qc-thumbnails--${theme}`,
580
+ style: {
581
+ display: "flex",
582
+ flexDirection: isVertical ? "column" : "row",
583
+ flexWrap: "wrap",
584
+ gap: "8px",
585
+ marginTop: "8px"
586
+ },
587
+ children: slides.map((slide, index) => {
588
+ const thumbnailUrl = getThumbnail(slide);
589
+ const isActive = index === currentIndex;
590
+ const isDragging = draggedIndex === index;
591
+ const isDragOver = dragOverIndex === index;
592
+ return /* @__PURE__ */ jsxs2(
593
+ "div",
594
+ {
595
+ className: "qc-thumbnail-wrapper",
596
+ style: {
597
+ position: "relative"
598
+ },
599
+ onDragOver: (e) => handleDragOver(e, index),
600
+ onDragLeave: handleDragLeave,
601
+ onDrop: (e) => handleDrop(e, index),
602
+ children: [
603
+ isDragOver && /* @__PURE__ */ jsx2(
604
+ "div",
605
+ {
606
+ style: {
607
+ position: "absolute",
608
+ left: "-4px",
609
+ top: 0,
610
+ bottom: 0,
611
+ width: "3px",
612
+ backgroundColor: "#3B82F6",
613
+ borderRadius: "2px"
614
+ }
615
+ }
616
+ ),
617
+ /* @__PURE__ */ jsxs2(
618
+ "button",
619
+ {
620
+ className: `qc-thumbnail ${isActive ? "qc-thumbnail--active" : ""}`,
621
+ draggable: true,
622
+ onDragStart: (e) => handleDragStart(e, index, thumbnailUrl),
623
+ onDragEnd: handleDragEnd,
624
+ onClick: () => onSelect(index),
625
+ style: {
626
+ position: "relative",
627
+ width: `${width}px`,
628
+ height: `${height}px`,
629
+ minWidth: `${width}px`,
630
+ minHeight: `${height}px`,
631
+ border: "none",
632
+ borderRadius: "5px",
633
+ overflow: "visible",
634
+ cursor: onReorder ? "grab" : "pointer",
635
+ opacity: isDragging ? 0.5 : 1,
636
+ transform: isActive ? "scale(1)" : "scale(0.95)",
637
+ transition: "all 0.2s ease",
638
+ backgroundColor: theme === "dark" ? "#333" : "#f5f5f5",
639
+ padding: 0,
640
+ 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)"
641
+ },
642
+ "aria-label": `Go to slide ${index + 1}: ${slide.name || ""}`,
643
+ children: [
644
+ /* @__PURE__ */ jsx2("div", { style: { width: "100%", height: "100%", borderRadius: "5px", overflow: "hidden" }, children: thumbnailUrl ? /* @__PURE__ */ jsx2(
645
+ "img",
646
+ {
647
+ src: thumbnailUrl,
648
+ alt: slide.name || `Slide ${index + 1}`,
649
+ draggable: false,
650
+ style: {
651
+ width: "100%",
652
+ height: "100%",
653
+ objectFit: "cover"
654
+ }
655
+ }
656
+ ) : /* @__PURE__ */ jsx2(
657
+ "div",
658
+ {
659
+ style: {
660
+ width: "100%",
661
+ height: "100%",
662
+ display: "flex",
663
+ alignItems: "center",
664
+ justifyContent: "center",
665
+ color: theme === "dark" ? "#888" : "#666",
666
+ fontSize: "20px"
667
+ },
668
+ children: getSlideIcon(slide)
669
+ }
670
+ ) }),
671
+ onDelete && isActive && /* @__PURE__ */ jsx2(
672
+ "div",
673
+ {
674
+ onClick: (e) => {
675
+ e.stopPropagation();
676
+ onDelete(index);
677
+ },
678
+ style: {
679
+ position: "absolute",
680
+ top: "-6px",
681
+ right: "-6px",
682
+ width: "20px",
683
+ height: "20px",
684
+ borderRadius: "50%",
685
+ backgroundColor: "#EF4444",
686
+ color: "white",
687
+ display: "flex",
688
+ alignItems: "center",
689
+ justifyContent: "center",
690
+ fontSize: "14px",
691
+ fontWeight: "bold",
692
+ cursor: "pointer",
693
+ boxShadow: "0 1px 3px rgba(0,0,0,0.3)",
694
+ transition: "transform 0.15s ease"
695
+ },
696
+ onMouseEnter: (e) => e.currentTarget.style.transform = "scale(1.1)",
697
+ onMouseLeave: (e) => e.currentTarget.style.transform = "scale(1)",
698
+ title: "Delete",
699
+ children: "\xD7"
700
+ }
701
+ )
702
+ ]
703
+ }
704
+ )
705
+ ]
706
+ },
707
+ slide.id
708
+ );
709
+ })
710
+ }
711
+ );
712
+ }
713
+
479
714
  // src/components/Carousel.tsx
480
- import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
715
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
716
+ function extractSlideUrl(slide) {
717
+ const layer = slide.layers?.[0];
718
+ if (!layer) return void 0;
719
+ const imageObj = layer.objects?.find((obj) => obj.type === "image");
720
+ if (imageObj && "src" in imageObj) {
721
+ return imageObj.src;
722
+ }
723
+ return void 0;
724
+ }
481
725
  var defaultNavigation = {
482
726
  arrows: true,
483
727
  dots: true,
@@ -501,11 +745,41 @@ var Carousel = forwardRef(
501
745
  style,
502
746
  onSlideChange,
503
747
  onFullscreenChange,
504
- theme = "dark"
748
+ theme = "dark",
749
+ // Copy functionality
750
+ carouselInfo,
751
+ getSlideUrl,
752
+ onCopyCarousel,
753
+ onCopySlide,
754
+ // Thumbnail callbacks
755
+ onThumbnailReorder,
756
+ onThumbnailDelete
505
757
  }, ref) => {
506
758
  const containerRef = useRef3(null);
507
- const [isFullscreen, setIsFullscreen] = useState2(false);
508
- const [touchStart, setTouchStart] = useState2(null);
759
+ const [isFullscreen, setIsFullscreen] = useState3(false);
760
+ const [touchStart, setTouchStart] = useState3(null);
761
+ const [copiedState, setCopiedState] = useState3(null);
762
+ const copyToClipboard = useCallback2(async (text, type) => {
763
+ try {
764
+ await navigator.clipboard.writeText(text);
765
+ setCopiedState(type);
766
+ setTimeout(() => setCopiedState(null), 2e3);
767
+ } catch (err) {
768
+ console.error("Failed to copy:", err);
769
+ }
770
+ }, []);
771
+ const handleCopyCarousel = useCallback2(() => {
772
+ if (!carouselInfo) return;
773
+ const jsonStr = JSON.stringify(carouselInfo);
774
+ copyToClipboard(jsonStr, "carousel");
775
+ onCopyCarousel?.(carouselInfo);
776
+ }, [carouselInfo, copyToClipboard, onCopyCarousel]);
777
+ const handleCopySlide = useCallback2((slide) => {
778
+ const url = getSlideUrl ? getSlideUrl(slide) : extractSlideUrl(slide);
779
+ if (!url) return;
780
+ copyToClipboard(url, "slide");
781
+ onCopySlide?.(slide, url);
782
+ }, [getSlideUrl, copyToClipboard, onCopySlide]);
509
783
  const nav = typeof navigation === "boolean" ? navigation ? defaultNavigation : {} : { ...defaultNavigation, ...navigation };
510
784
  const {
511
785
  currentIndex,
@@ -654,200 +928,291 @@ var Carousel = forwardRef(
654
928
  return base;
655
929
  }
656
930
  };
657
- return /* @__PURE__ */ jsxs2(
931
+ return /* @__PURE__ */ jsxs3(
658
932
  "div",
659
933
  {
660
- ref: containerRef,
661
- className: `qc-carousel qc-carousel--${theme} ${isFullscreen ? "qc-carousel--fullscreen" : ""} ${className}`,
934
+ className: "qc-carousel-wrapper",
662
935
  style: {
663
- position: "relative",
664
936
  width: "100%",
665
- aspectRatio: isFullscreen ? "auto" : aspectRatio,
666
- overflow: "hidden",
667
- backgroundColor: theme === "dark" ? "#1a1a1a" : "#ffffff",
668
937
  ...style
669
938
  },
670
- onTouchStart: handleTouchStart,
671
- onTouchEnd: handleTouchEnd,
672
939
  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(
940
+ /* @__PURE__ */ jsxs3(
770
941
  "div",
771
942
  {
772
- className: "qc-carousel__dots",
943
+ ref: containerRef,
944
+ className: `qc-carousel qc-carousel--${theme} ${isFullscreen ? "qc-carousel--fullscreen" : ""} ${className}`,
773
945
  style: {
774
- position: "absolute",
775
- bottom: "16px",
776
- left: "50%",
777
- transform: "translateX(-50%)",
778
- display: "flex",
779
- gap: "8px",
780
- zIndex: 10
946
+ position: "relative",
947
+ width: "100%",
948
+ aspectRatio: isFullscreen ? "auto" : aspectRatio,
949
+ overflow: "hidden",
950
+ backgroundColor: theme === "dark" ? "#1a1a1a" : "#ffffff"
781
951
  },
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"
952
+ onTouchStart: handleTouchStart,
953
+ onTouchEnd: handleTouchEnd,
954
+ children: [
955
+ /* @__PURE__ */ jsx3(
956
+ "div",
957
+ {
958
+ className: "qc-carousel__slides",
959
+ style: {
960
+ display: transition === "slide" ? "flex" : "block",
961
+ width: transition === "slide" ? `${totalSlides * 100}%` : "100%",
962
+ height: "100%",
963
+ ...getTransitionStyle()
964
+ },
965
+ children: slides.map((slide, index) => /* @__PURE__ */ jsx3(
966
+ "div",
967
+ {
968
+ className: `qc-carousel__slide ${index === currentIndex ? "qc-carousel__slide--active" : ""}`,
969
+ style: {
970
+ width: transition === "slide" ? `${100 / totalSlides}%` : "100%",
971
+ height: "100%",
972
+ position: transition === "slide" ? "relative" : "absolute",
973
+ top: 0,
974
+ left: 0,
975
+ opacity: transition === "fade" ? index === currentIndex ? 1 : 0 : 1,
976
+ transition: `opacity ${transitionDuration}ms ease-in-out`,
977
+ pointerEvents: index === currentIndex ? "auto" : "none"
978
+ },
979
+ children: /* @__PURE__ */ jsx3(SlideRenderer, { slide, isActive: index === currentIndex, slideIndex: index })
980
+ },
981
+ slide.id
982
+ ))
799
983
  }
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"
984
+ ),
985
+ nav.arrows && totalSlides > 1 && /* @__PURE__ */ jsxs3(Fragment2, { children: [
986
+ /* @__PURE__ */ jsx3(
987
+ "button",
988
+ {
989
+ className: "qc-carousel__arrow qc-carousel__arrow--prev",
990
+ onClick: () => {
991
+ prev();
992
+ handleInteraction();
993
+ },
994
+ disabled: !canGoPrev,
995
+ "aria-label": "Previous slide",
996
+ style: {
997
+ position: "absolute",
998
+ left: "16px",
999
+ top: "50%",
1000
+ transform: "translateY(-50%)",
1001
+ width: "48px",
1002
+ height: "48px",
1003
+ borderRadius: "50%",
1004
+ border: "none",
1005
+ backgroundColor: "rgba(0,0,0,0.5)",
1006
+ color: "white",
1007
+ cursor: canGoPrev ? "pointer" : "not-allowed",
1008
+ opacity: canGoPrev ? 1 : 0.3,
1009
+ display: "flex",
1010
+ alignItems: "center",
1011
+ justifyContent: "center",
1012
+ fontSize: "24px",
1013
+ zIndex: 10
1014
+ },
1015
+ children: "\u2039"
1016
+ }
1017
+ ),
1018
+ /* @__PURE__ */ jsx3(
1019
+ "button",
1020
+ {
1021
+ className: "qc-carousel__arrow qc-carousel__arrow--next",
1022
+ onClick: () => {
1023
+ next();
1024
+ handleInteraction();
1025
+ },
1026
+ disabled: !canGoNext,
1027
+ "aria-label": "Next slide",
1028
+ style: {
1029
+ position: "absolute",
1030
+ right: "16px",
1031
+ top: "50%",
1032
+ transform: "translateY(-50%)",
1033
+ width: "48px",
1034
+ height: "48px",
1035
+ borderRadius: "50%",
1036
+ border: "none",
1037
+ backgroundColor: "rgba(0,0,0,0.5)",
1038
+ color: "white",
1039
+ cursor: canGoNext ? "pointer" : "not-allowed",
1040
+ opacity: canGoNext ? 1 : 0.3,
1041
+ display: "flex",
1042
+ alignItems: "center",
1043
+ justifyContent: "center",
1044
+ fontSize: "24px",
1045
+ zIndex: 10
1046
+ },
1047
+ children: "\u203A"
1048
+ }
1049
+ )
1050
+ ] }),
1051
+ nav.dots && totalSlides > 1 && /* @__PURE__ */ jsx3(
1052
+ "div",
1053
+ {
1054
+ className: "qc-carousel__dots",
1055
+ style: {
1056
+ position: "absolute",
1057
+ bottom: "16px",
1058
+ left: "50%",
1059
+ transform: "translateX(-50%)",
1060
+ display: "flex",
1061
+ gap: "8px",
1062
+ zIndex: 10
1063
+ },
1064
+ children: slides.map((_, index) => /* @__PURE__ */ jsx3(
1065
+ "button",
1066
+ {
1067
+ className: `qc-carousel__dot ${index === currentIndex ? "qc-carousel__dot--active" : ""}`,
1068
+ onClick: () => {
1069
+ goTo(index);
1070
+ handleInteraction();
1071
+ },
1072
+ "aria-label": `Go to slide ${index + 1}`,
1073
+ style: {
1074
+ width: "10px",
1075
+ height: "10px",
1076
+ borderRadius: "50%",
1077
+ border: "none",
1078
+ backgroundColor: index === currentIndex ? "#E67E22" : "rgba(255,255,255,0.5)",
1079
+ cursor: "pointer",
1080
+ transition: "all 0.2s ease"
1081
+ }
1082
+ },
1083
+ index
1084
+ ))
1085
+ }
1086
+ ),
1087
+ /* @__PURE__ */ jsxs3(
1088
+ "div",
1089
+ {
1090
+ className: "qc-carousel__top-right-buttons",
1091
+ style: {
1092
+ position: "absolute",
1093
+ top: "16px",
1094
+ right: "16px",
1095
+ display: "flex",
1096
+ flexDirection: "column",
1097
+ alignItems: "center",
1098
+ gap: "8px",
1099
+ zIndex: 10
1100
+ },
1101
+ children: [
1102
+ allowFullscreen && /* @__PURE__ */ jsx3(
1103
+ "button",
1104
+ {
1105
+ className: "qc-carousel__fullscreen",
1106
+ onClick: toggleFullscreen,
1107
+ "aria-label": isFullscreen ? "Exit fullscreen" : "Enter fullscreen",
1108
+ style: {
1109
+ width: "40px",
1110
+ height: "40px",
1111
+ borderRadius: "8px",
1112
+ border: "none",
1113
+ backgroundColor: "rgba(0,0,0,0.5)",
1114
+ color: "white",
1115
+ cursor: "pointer",
1116
+ display: "flex",
1117
+ alignItems: "center",
1118
+ justifyContent: "center",
1119
+ fontSize: "18px"
1120
+ },
1121
+ children: isFullscreen ? "\u22A0" : "\u229E"
1122
+ }
1123
+ ),
1124
+ currentSlide && (getSlideUrl || extractSlideUrl(currentSlide)) && /* @__PURE__ */ jsx3(
1125
+ "button",
1126
+ {
1127
+ className: "qc-carousel__copy-slide",
1128
+ onClick: () => handleCopySlide(currentSlide),
1129
+ "aria-label": "Copy image URL",
1130
+ title: "Copy image URL",
1131
+ style: {
1132
+ width: "40px",
1133
+ height: "40px",
1134
+ borderRadius: "8px",
1135
+ border: "none",
1136
+ backgroundColor: copiedState === "slide" ? "rgba(34,197,94,0.8)" : "rgba(0,0,0,0.5)",
1137
+ color: "white",
1138
+ cursor: "pointer",
1139
+ display: "flex",
1140
+ alignItems: "center",
1141
+ justifyContent: "center",
1142
+ fontSize: "16px",
1143
+ transition: "background-color 0.2s ease"
1144
+ },
1145
+ children: copiedState === "slide" ? "\u2713" : "\u{1F517}"
1146
+ }
1147
+ )
1148
+ ]
1149
+ }
1150
+ ),
1151
+ carouselInfo && /* @__PURE__ */ jsx3(
1152
+ "button",
1153
+ {
1154
+ className: "qc-carousel__copy-carousel",
1155
+ onClick: handleCopyCarousel,
1156
+ "aria-label": "Copy carousel link",
1157
+ title: "Copy carousel link",
1158
+ style: {
1159
+ position: "absolute",
1160
+ top: "16px",
1161
+ right: allowFullscreen ? "72px" : "16px",
1162
+ width: "40px",
1163
+ height: "40px",
1164
+ borderRadius: "8px",
1165
+ border: "none",
1166
+ backgroundColor: copiedState === "carousel" ? "rgba(34,197,94,0.8)" : "rgba(0,0,0,0.5)",
1167
+ color: "white",
1168
+ cursor: "pointer",
1169
+ display: "flex",
1170
+ alignItems: "center",
1171
+ justifyContent: "center",
1172
+ fontSize: "16px",
1173
+ transition: "background-color 0.2s ease",
1174
+ zIndex: 10
1175
+ },
1176
+ children: copiedState === "carousel" ? "\u2713" : "\u{1F4CB}"
1177
+ }
1178
+ ),
1179
+ /* @__PURE__ */ jsxs3(
1180
+ "div",
1181
+ {
1182
+ className: "qc-carousel__counter",
1183
+ style: {
1184
+ position: "absolute",
1185
+ top: "16px",
1186
+ left: "16px",
1187
+ padding: "4px 12px",
1188
+ borderRadius: "4px",
1189
+ backgroundColor: "rgba(0,0,0,0.5)",
1190
+ color: "white",
1191
+ fontSize: "14px",
1192
+ zIndex: 10
1193
+ },
1194
+ children: [
1195
+ currentIndex + 1,
1196
+ " / ",
1197
+ totalSlides
1198
+ ]
1199
+ }
1200
+ )
1201
+ ]
829
1202
  }
830
1203
  ),
831
- /* @__PURE__ */ jsxs2(
832
- "div",
1204
+ nav.thumbnails && /* @__PURE__ */ jsx3(
1205
+ Thumbnails,
833
1206
  {
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
1207
+ slides,
1208
+ currentIndex,
1209
+ onSelect: (index) => {
1210
+ goTo(index);
1211
+ handleInteraction();
845
1212
  },
846
- children: [
847
- currentIndex + 1,
848
- " / ",
849
- totalSlides
850
- ]
1213
+ onReorder: onThumbnailReorder,
1214
+ onDelete: onThumbnailDelete,
1215
+ theme: theme === "auto" ? "light" : theme
851
1216
  }
852
1217
  )
853
1218
  ]
@@ -857,131 +1222,6 @@ var Carousel = forwardRef(
857
1222
  );
858
1223
  Carousel.displayName = "Carousel";
859
1224
 
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
1225
  // src/types.ts
986
1226
  function createSimpleSlide(id, objects, options) {
987
1227
  return {
@@ -1024,6 +1264,81 @@ function createImageSlide(id, src, options) {
1024
1264
  }
1025
1265
 
1026
1266
  // src/index.ts
1267
+ import React4, { useState as useState4, useEffect as useEffect4 } from "react";
1268
+ function MarkdownContent({ url, filename }) {
1269
+ const [content, setContent] = useState4("");
1270
+ const [loading, setLoading] = useState4(true);
1271
+ const [error, setError] = useState4(null);
1272
+ useEffect4(() => {
1273
+ fetch(url).then((res) => {
1274
+ if (!res.ok) throw new Error("Failed to load");
1275
+ return res.text();
1276
+ }).then((text) => {
1277
+ setContent(text);
1278
+ setLoading(false);
1279
+ }).catch((err) => {
1280
+ setError(err.message);
1281
+ setLoading(false);
1282
+ });
1283
+ }, [url]);
1284
+ const containerStyle = {
1285
+ width: "100%",
1286
+ height: "100%",
1287
+ display: "flex",
1288
+ flexDirection: "column",
1289
+ backgroundColor: "#f0f9ff",
1290
+ padding: "16px",
1291
+ overflow: "auto"
1292
+ };
1293
+ const centeredStyle = {
1294
+ ...containerStyle,
1295
+ alignItems: "center",
1296
+ justifyContent: "center"
1297
+ };
1298
+ const headerStyle = {
1299
+ display: "flex",
1300
+ alignItems: "center",
1301
+ gap: "8px",
1302
+ marginBottom: "12px",
1303
+ paddingBottom: "8px",
1304
+ borderBottom: "1px solid #bae6fd"
1305
+ };
1306
+ const preStyle = {
1307
+ flex: 1,
1308
+ fontSize: "14px",
1309
+ color: "#374151",
1310
+ whiteSpace: "pre-wrap",
1311
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
1312
+ overflow: "auto",
1313
+ margin: 0
1314
+ };
1315
+ if (loading) {
1316
+ return React4.createElement(
1317
+ "div",
1318
+ { style: centeredStyle },
1319
+ React4.createElement("span", { style: { color: "#9ca3af" } }, "Loading...")
1320
+ );
1321
+ }
1322
+ if (error) {
1323
+ return React4.createElement(
1324
+ "div",
1325
+ { style: centeredStyle },
1326
+ React4.createElement("span", { style: { color: "#ef4444" } }, "Failed to load markdown")
1327
+ );
1328
+ }
1329
+ const children = [];
1330
+ if (filename) {
1331
+ children.push(
1332
+ React4.createElement(
1333
+ "div",
1334
+ { key: "header", style: headerStyle },
1335
+ React4.createElement("span", { style: { color: "#0284c7", fontWeight: 500, fontSize: "14px" } }, "\u{1F4C4} " + filename)
1336
+ )
1337
+ );
1338
+ }
1339
+ children.push(React4.createElement("pre", { key: "content", style: preStyle }, content));
1340
+ return React4.createElement("div", { style: containerStyle }, ...children);
1341
+ }
1027
1342
  var createSlide = {
1028
1343
  /**
1029
1344
  * Create a simple image slide (single image, full slide)
@@ -1066,8 +1381,77 @@ var createSlide = {
1066
1381
  position: { x: 0, y: 0 },
1067
1382
  size: { width: 100, height: 100, unit: "percent" }
1068
1383
  }], options);
1384
+ },
1385
+ /**
1386
+ * Create a slide to display a markdown file
1387
+ */
1388
+ markdown: (id, url, options) => {
1389
+ const thumbnail = options?.thumbnail ?? markdownThumbnailSvg;
1390
+ return createSimpleSlide(id, [{
1391
+ id: `${id}-markdown`,
1392
+ type: "component",
1393
+ render: () => React4.createElement(MarkdownContent, { url, filename: options?.filename }),
1394
+ position: { x: 0, y: 0 },
1395
+ size: { width: 100, height: 100, unit: "percent" }
1396
+ }], { thumbnail });
1397
+ },
1398
+ /**
1399
+ * Create a slide to display an audio file
1400
+ */
1401
+ audio: (id, url, options) => {
1402
+ const thumbnail = options?.thumbnail ?? audioThumbnailSvg;
1403
+ return createSimpleSlide(id, [{
1404
+ id: `${id}-audio`,
1405
+ type: "component",
1406
+ render: () => React4.createElement(AudioContent, { url, filename: options?.filename }),
1407
+ position: { x: 0, y: 0 },
1408
+ size: { width: 100, height: 100, unit: "percent" }
1409
+ }], { thumbnail });
1069
1410
  }
1070
1411
  };
1412
+ var markdownThumbnailSvg = "data:image/svg+xml," + encodeURIComponent(
1413
+ '<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>'
1414
+ );
1415
+ var audioThumbnailSvg = "data:image/svg+xml," + encodeURIComponent(
1416
+ '<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>'
1417
+ );
1418
+ function AudioContent({ url, filename }) {
1419
+ const containerStyle = {
1420
+ width: "100%",
1421
+ height: "100%",
1422
+ display: "flex",
1423
+ flexDirection: "column",
1424
+ alignItems: "center",
1425
+ justifyContent: "center",
1426
+ backgroundColor: "#171717",
1427
+ padding: "16px"
1428
+ };
1429
+ const iconStyle = {
1430
+ width: "48px",
1431
+ height: "48px",
1432
+ color: "#34d399",
1433
+ marginBottom: "12px"
1434
+ };
1435
+ const filenameStyle = {
1436
+ color: "#d1d5db",
1437
+ fontSize: "14px",
1438
+ marginBottom: "16px"
1439
+ };
1440
+ const children = [
1441
+ React4.createElement(
1442
+ "svg",
1443
+ { key: "icon", style: iconStyle, viewBox: "0 0 24 24", fill: "currentColor" },
1444
+ React4.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" })
1445
+ )
1446
+ ];
1447
+ if (filename) {
1448
+ children.push(React4.createElement("span", { key: "filename", style: filenameStyle }, filename));
1449
+ }
1450
+ children.push(
1451
+ React4.createElement("audio", { key: "audio", src: url, controls: true, style: { width: "100%", maxWidth: "280px" } })
1452
+ );
1453
+ return React4.createElement("div", { style: containerStyle }, ...children);
1454
+ }
1071
1455
  export {
1072
1456
  Carousel,
1073
1457
  SlideRenderer,