@qwanyx/carousel 0.1.0 → 0.1.3

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,66 @@ 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,
757
+ // Image editing
758
+ onImageEdit,
759
+ editorAspectRatio = "4/3",
760
+ ImageEditor
505
761
  }, ref) => {
506
762
  const containerRef = useRef3(null);
507
- const [isFullscreen, setIsFullscreen] = useState2(false);
508
- const [touchStart, setTouchStart] = useState2(null);
763
+ const [isFullscreen, setIsFullscreen] = useState3(false);
764
+ const [touchStart, setTouchStart] = useState3(null);
765
+ const [copiedState, setCopiedState] = useState3(null);
766
+ const [editingIndex, setEditingIndex] = useState3(null);
767
+ const canEdit = ImageEditor !== void 0 && onImageEdit !== void 0;
768
+ const handleSlideDoubleClick = useCallback2((index) => {
769
+ if (!canEdit) return;
770
+ setEditingIndex(index);
771
+ }, [canEdit]);
772
+ const handleImageSave = useCallback2((blob) => {
773
+ if (editingIndex !== null && onImageEdit) {
774
+ onImageEdit(editingIndex, blob);
775
+ }
776
+ setEditingIndex(null);
777
+ }, [editingIndex, onImageEdit]);
778
+ const handleEditorCancel = useCallback2(() => {
779
+ setEditingIndex(null);
780
+ }, []);
781
+ const getEditingImageUrl = useCallback2(() => {
782
+ if (editingIndex === null) return void 0;
783
+ const slide = slides[editingIndex];
784
+ if (!slide) return void 0;
785
+ return getSlideUrl ? getSlideUrl(slide) : extractSlideUrl(slide);
786
+ }, [editingIndex, slides, getSlideUrl]);
787
+ const copyToClipboard = useCallback2(async (text, type) => {
788
+ try {
789
+ await navigator.clipboard.writeText(text);
790
+ setCopiedState(type);
791
+ setTimeout(() => setCopiedState(null), 2e3);
792
+ } catch (err) {
793
+ console.error("Failed to copy:", err);
794
+ }
795
+ }, []);
796
+ const handleCopyCarousel = useCallback2(() => {
797
+ if (!carouselInfo) return;
798
+ const jsonStr = JSON.stringify(carouselInfo);
799
+ copyToClipboard(jsonStr, "carousel");
800
+ onCopyCarousel?.(carouselInfo);
801
+ }, [carouselInfo, copyToClipboard, onCopyCarousel]);
802
+ const handleCopySlide = useCallback2((slide) => {
803
+ const url = getSlideUrl ? getSlideUrl(slide) : extractSlideUrl(slide);
804
+ if (!url) return;
805
+ copyToClipboard(url, "slide");
806
+ onCopySlide?.(slide, url);
807
+ }, [getSlideUrl, copyToClipboard, onCopySlide]);
509
808
  const nav = typeof navigation === "boolean" ? navigation ? defaultNavigation : {} : { ...defaultNavigation, ...navigation };
510
809
  const {
511
810
  currentIndex,
@@ -654,199 +953,366 @@ var Carousel = forwardRef(
654
953
  return base;
655
954
  }
656
955
  };
657
- return /* @__PURE__ */ jsxs2(
956
+ return /* @__PURE__ */ jsxs3(
658
957
  "div",
659
958
  {
660
- ref: containerRef,
661
- className: `qc-carousel qc-carousel--${theme} ${isFullscreen ? "qc-carousel--fullscreen" : ""} ${className}`,
959
+ className: "qc-carousel-wrapper",
662
960
  style: {
663
- position: "relative",
664
961
  width: "100%",
665
- aspectRatio: isFullscreen ? "auto" : aspectRatio,
666
- overflow: "hidden",
667
- backgroundColor: theme === "dark" ? "#1a1a1a" : "#ffffff",
668
962
  ...style
669
963
  },
670
- onTouchStart: handleTouchStart,
671
- onTouchEnd: handleTouchEnd,
672
964
  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(
965
+ /* @__PURE__ */ jsxs3(
770
966
  "div",
771
967
  {
772
- className: "qc-carousel__dots",
968
+ ref: containerRef,
969
+ className: `qc-carousel qc-carousel--${theme} ${isFullscreen ? "qc-carousel--fullscreen" : ""} ${className}`,
773
970
  style: {
774
- position: "absolute",
775
- bottom: "16px",
776
- left: "50%",
777
- transform: "translateX(-50%)",
778
- display: "flex",
779
- gap: "8px",
780
- zIndex: 10
971
+ position: "relative",
972
+ width: "100%",
973
+ aspectRatio: isFullscreen ? "auto" : aspectRatio,
974
+ overflow: "hidden",
975
+ backgroundColor: theme === "dark" ? "#1a1a1a" : "#ffffff"
781
976
  },
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"
977
+ onTouchStart: handleTouchStart,
978
+ onTouchEnd: handleTouchEnd,
979
+ children: [
980
+ /* @__PURE__ */ jsx3(
981
+ "div",
982
+ {
983
+ className: "qc-carousel__slides",
984
+ style: {
985
+ display: transition === "slide" ? "flex" : "block",
986
+ width: transition === "slide" ? `${totalSlides * 100}%` : "100%",
987
+ height: "100%",
988
+ ...getTransitionStyle()
989
+ },
990
+ children: slides.map((slide, index) => /* @__PURE__ */ jsx3(
991
+ "div",
992
+ {
993
+ className: `qc-carousel__slide ${index === currentIndex ? "qc-carousel__slide--active" : ""}`,
994
+ style: {
995
+ width: transition === "slide" ? `${100 / totalSlides}%` : "100%",
996
+ height: "100%",
997
+ position: transition === "slide" ? "relative" : "absolute",
998
+ top: 0,
999
+ left: 0,
1000
+ opacity: transition === "fade" ? index === currentIndex ? 1 : 0 : 1,
1001
+ transition: `opacity ${transitionDuration}ms ease-in-out`,
1002
+ pointerEvents: index === currentIndex ? "auto" : "none",
1003
+ cursor: canEdit ? "pointer" : "default"
1004
+ },
1005
+ onDoubleClick: () => handleSlideDoubleClick(index),
1006
+ children: /* @__PURE__ */ jsx3(SlideRenderer, { slide, isActive: index === currentIndex, slideIndex: index })
1007
+ },
1008
+ slide.id
1009
+ ))
1010
+ }
1011
+ ),
1012
+ nav.arrows && totalSlides > 1 && /* @__PURE__ */ jsxs3(Fragment2, { children: [
1013
+ /* @__PURE__ */ jsx3(
1014
+ "button",
1015
+ {
1016
+ className: "qc-carousel__arrow qc-carousel__arrow--prev",
1017
+ onClick: () => {
1018
+ prev();
1019
+ handleInteraction();
1020
+ },
1021
+ disabled: !canGoPrev,
1022
+ "aria-label": "Previous slide",
1023
+ style: {
1024
+ position: "absolute",
1025
+ left: "16px",
1026
+ top: "50%",
1027
+ transform: "translateY(-50%)",
1028
+ width: "48px",
1029
+ height: "48px",
1030
+ borderRadius: "50%",
1031
+ border: "none",
1032
+ backgroundColor: "rgba(0,0,0,0.5)",
1033
+ color: "white",
1034
+ cursor: canGoPrev ? "pointer" : "not-allowed",
1035
+ opacity: canGoPrev ? 1 : 0.3,
1036
+ display: "flex",
1037
+ alignItems: "center",
1038
+ justifyContent: "center",
1039
+ fontSize: "24px",
1040
+ zIndex: 10
1041
+ },
1042
+ children: "\u2039"
1043
+ }
1044
+ ),
1045
+ /* @__PURE__ */ jsx3(
1046
+ "button",
1047
+ {
1048
+ className: "qc-carousel__arrow qc-carousel__arrow--next",
1049
+ onClick: () => {
1050
+ next();
1051
+ handleInteraction();
1052
+ },
1053
+ disabled: !canGoNext,
1054
+ "aria-label": "Next slide",
1055
+ style: {
1056
+ position: "absolute",
1057
+ right: "16px",
1058
+ top: "50%",
1059
+ transform: "translateY(-50%)",
1060
+ width: "48px",
1061
+ height: "48px",
1062
+ borderRadius: "50%",
1063
+ border: "none",
1064
+ backgroundColor: "rgba(0,0,0,0.5)",
1065
+ color: "white",
1066
+ cursor: canGoNext ? "pointer" : "not-allowed",
1067
+ opacity: canGoNext ? 1 : 0.3,
1068
+ display: "flex",
1069
+ alignItems: "center",
1070
+ justifyContent: "center",
1071
+ fontSize: "24px",
1072
+ zIndex: 10
1073
+ },
1074
+ children: "\u203A"
1075
+ }
1076
+ )
1077
+ ] }),
1078
+ nav.dots && totalSlides > 1 && /* @__PURE__ */ jsx3(
1079
+ "div",
1080
+ {
1081
+ className: "qc-carousel__dots",
1082
+ style: {
1083
+ position: "absolute",
1084
+ bottom: "16px",
1085
+ left: "50%",
1086
+ transform: "translateX(-50%)",
1087
+ display: "flex",
1088
+ gap: "8px",
1089
+ zIndex: 10
1090
+ },
1091
+ children: slides.map((_, index) => /* @__PURE__ */ jsx3(
1092
+ "button",
1093
+ {
1094
+ className: `qc-carousel__dot ${index === currentIndex ? "qc-carousel__dot--active" : ""}`,
1095
+ onClick: () => {
1096
+ goTo(index);
1097
+ handleInteraction();
1098
+ },
1099
+ "aria-label": `Go to slide ${index + 1}`,
1100
+ style: {
1101
+ width: "10px",
1102
+ height: "10px",
1103
+ borderRadius: "50%",
1104
+ border: "none",
1105
+ backgroundColor: index === currentIndex ? "#E67E22" : "rgba(255,255,255,0.5)",
1106
+ cursor: "pointer",
1107
+ transition: "all 0.2s ease"
1108
+ }
1109
+ },
1110
+ index
1111
+ ))
1112
+ }
1113
+ ),
1114
+ /* @__PURE__ */ jsxs3(
1115
+ "div",
1116
+ {
1117
+ className: "qc-carousel__top-right-buttons",
1118
+ style: {
1119
+ position: "absolute",
1120
+ top: "16px",
1121
+ right: "16px",
1122
+ display: "flex",
1123
+ flexDirection: "column",
1124
+ alignItems: "center",
1125
+ gap: "8px",
1126
+ zIndex: 10
1127
+ },
1128
+ children: [
1129
+ allowFullscreen && /* @__PURE__ */ jsx3(
1130
+ "button",
1131
+ {
1132
+ className: "qc-carousel__fullscreen",
1133
+ onClick: toggleFullscreen,
1134
+ "aria-label": isFullscreen ? "Exit fullscreen" : "Enter fullscreen",
1135
+ style: {
1136
+ width: "40px",
1137
+ height: "40px",
1138
+ borderRadius: "8px",
1139
+ border: "none",
1140
+ backgroundColor: "rgba(0,0,0,0.5)",
1141
+ color: "white",
1142
+ cursor: "pointer",
1143
+ display: "flex",
1144
+ alignItems: "center",
1145
+ justifyContent: "center",
1146
+ fontSize: "18px"
1147
+ },
1148
+ children: isFullscreen ? "\u22A0" : "\u229E"
1149
+ }
1150
+ ),
1151
+ currentSlide && (getSlideUrl || extractSlideUrl(currentSlide)) && /* @__PURE__ */ jsx3(
1152
+ "button",
1153
+ {
1154
+ className: "qc-carousel__copy-slide",
1155
+ onClick: () => handleCopySlide(currentSlide),
1156
+ "aria-label": "Copy image URL",
1157
+ title: "Copy image URL",
1158
+ style: {
1159
+ width: "40px",
1160
+ height: "40px",
1161
+ borderRadius: "8px",
1162
+ border: "none",
1163
+ backgroundColor: copiedState === "slide" ? "rgba(34,197,94,0.8)" : "rgba(0,0,0,0.5)",
1164
+ color: "white",
1165
+ cursor: "pointer",
1166
+ display: "flex",
1167
+ alignItems: "center",
1168
+ justifyContent: "center",
1169
+ fontSize: "16px",
1170
+ transition: "background-color 0.2s ease"
1171
+ },
1172
+ children: copiedState === "slide" ? "\u2713" : "\u{1F517}"
1173
+ }
1174
+ )
1175
+ ]
1176
+ }
1177
+ ),
1178
+ carouselInfo && /* @__PURE__ */ jsx3(
1179
+ "button",
1180
+ {
1181
+ className: "qc-carousel__copy-carousel",
1182
+ onClick: handleCopyCarousel,
1183
+ "aria-label": "Copy carousel link",
1184
+ title: "Copy carousel link",
1185
+ style: {
1186
+ position: "absolute",
1187
+ top: "16px",
1188
+ right: allowFullscreen ? "72px" : "16px",
1189
+ width: "40px",
1190
+ height: "40px",
1191
+ borderRadius: "8px",
1192
+ border: "none",
1193
+ backgroundColor: copiedState === "carousel" ? "rgba(34,197,94,0.8)" : "rgba(0,0,0,0.5)",
1194
+ color: "white",
1195
+ cursor: "pointer",
1196
+ display: "flex",
1197
+ alignItems: "center",
1198
+ justifyContent: "center",
1199
+ fontSize: "16px",
1200
+ transition: "background-color 0.2s ease",
1201
+ zIndex: 10
1202
+ },
1203
+ children: copiedState === "carousel" ? "\u2713" : "\u{1F4CB}"
1204
+ }
1205
+ ),
1206
+ /* @__PURE__ */ jsxs3(
1207
+ "div",
1208
+ {
1209
+ className: "qc-carousel__counter",
1210
+ style: {
1211
+ position: "absolute",
1212
+ top: "16px",
1213
+ left: "16px",
1214
+ padding: "4px 12px",
1215
+ borderRadius: "4px",
1216
+ backgroundColor: "rgba(0,0,0,0.5)",
1217
+ color: "white",
1218
+ fontSize: "14px",
1219
+ zIndex: 10
1220
+ },
1221
+ children: [
1222
+ currentIndex + 1,
1223
+ " / ",
1224
+ totalSlides
1225
+ ]
799
1226
  }
800
- },
801
- index
802
- ))
1227
+ )
1228
+ ]
803
1229
  }
804
1230
  ),
805
- allowFullscreen && /* @__PURE__ */ jsx2(
806
- "button",
1231
+ nav.thumbnails && /* @__PURE__ */ jsx3(
1232
+ Thumbnails,
807
1233
  {
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
1234
+ slides,
1235
+ currentIndex,
1236
+ onSelect: (index) => {
1237
+ goTo(index);
1238
+ handleInteraction();
827
1239
  },
828
- children: isFullscreen ? "\u22A0" : "\u229E"
1240
+ onReorder: onThumbnailReorder,
1241
+ onDelete: onThumbnailDelete,
1242
+ theme: theme === "auto" ? "light" : theme
829
1243
  }
830
1244
  ),
831
- /* @__PURE__ */ jsxs2(
1245
+ editingIndex !== null && ImageEditor && /* @__PURE__ */ jsxs3(
832
1246
  "div",
833
1247
  {
834
- className: "qc-carousel__counter",
1248
+ className: "qc-carousel__editor-overlay",
835
1249
  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
1250
+ position: "fixed",
1251
+ top: 0,
1252
+ left: 0,
1253
+ right: 0,
1254
+ bottom: 0,
1255
+ zIndex: 9999,
1256
+ backgroundColor: theme === "dark" ? "#1a1a1a" : "#f5f5f5",
1257
+ display: "flex",
1258
+ flexDirection: "column"
845
1259
  },
846
1260
  children: [
847
- currentIndex + 1,
848
- " / ",
849
- totalSlides
1261
+ /* @__PURE__ */ jsxs3(
1262
+ "div",
1263
+ {
1264
+ style: {
1265
+ padding: "12px 16px",
1266
+ display: "flex",
1267
+ alignItems: "center",
1268
+ justifyContent: "space-between",
1269
+ borderBottom: `1px solid ${theme === "dark" ? "#333" : "#ddd"}`,
1270
+ backgroundColor: theme === "dark" ? "#242424" : "#fff"
1271
+ },
1272
+ children: [
1273
+ /* @__PURE__ */ jsxs3("span", { style: {
1274
+ color: theme === "dark" ? "#fff" : "#333",
1275
+ fontWeight: 500,
1276
+ fontSize: "16px"
1277
+ }, children: [
1278
+ "Edit Image ",
1279
+ editingIndex + 1,
1280
+ " / ",
1281
+ totalSlides
1282
+ ] }),
1283
+ /* @__PURE__ */ jsx3(
1284
+ "button",
1285
+ {
1286
+ onClick: handleEditorCancel,
1287
+ style: {
1288
+ background: "none",
1289
+ border: "none",
1290
+ color: theme === "dark" ? "#aaa" : "#666",
1291
+ cursor: "pointer",
1292
+ fontSize: "24px",
1293
+ lineHeight: 1,
1294
+ padding: "4px"
1295
+ },
1296
+ children: "\xD7"
1297
+ }
1298
+ )
1299
+ ]
1300
+ }
1301
+ ),
1302
+ /* @__PURE__ */ jsx3("div", { style: { flex: 1, minHeight: 0 }, children: (() => {
1303
+ const imageUrl = getEditingImageUrl();
1304
+ if (!imageUrl) return null;
1305
+ return /* @__PURE__ */ jsx3(
1306
+ ImageEditor,
1307
+ {
1308
+ src: imageUrl,
1309
+ onSave: handleImageSave,
1310
+ onCancel: handleEditorCancel,
1311
+ aspectRatio: editorAspectRatio,
1312
+ theme: theme === "auto" ? "dark" : theme
1313
+ }
1314
+ );
1315
+ })() })
850
1316
  ]
851
1317
  }
852
1318
  )
@@ -857,131 +1323,6 @@ var Carousel = forwardRef(
857
1323
  );
858
1324
  Carousel.displayName = "Carousel";
859
1325
 
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
1326
  // src/types.ts
986
1327
  function createSimpleSlide(id, objects, options) {
987
1328
  return {
@@ -1024,6 +1365,81 @@ function createImageSlide(id, src, options) {
1024
1365
  }
1025
1366
 
1026
1367
  // src/index.ts
1368
+ import React4, { useState as useState4, useEffect as useEffect4 } from "react";
1369
+ function MarkdownContent({ url, filename }) {
1370
+ const [content, setContent] = useState4("");
1371
+ const [loading, setLoading] = useState4(true);
1372
+ const [error, setError] = useState4(null);
1373
+ useEffect4(() => {
1374
+ fetch(url).then((res) => {
1375
+ if (!res.ok) throw new Error("Failed to load");
1376
+ return res.text();
1377
+ }).then((text) => {
1378
+ setContent(text);
1379
+ setLoading(false);
1380
+ }).catch((err) => {
1381
+ setError(err.message);
1382
+ setLoading(false);
1383
+ });
1384
+ }, [url]);
1385
+ const containerStyle = {
1386
+ width: "100%",
1387
+ height: "100%",
1388
+ display: "flex",
1389
+ flexDirection: "column",
1390
+ backgroundColor: "#f0f9ff",
1391
+ padding: "16px",
1392
+ overflow: "auto"
1393
+ };
1394
+ const centeredStyle = {
1395
+ ...containerStyle,
1396
+ alignItems: "center",
1397
+ justifyContent: "center"
1398
+ };
1399
+ const headerStyle = {
1400
+ display: "flex",
1401
+ alignItems: "center",
1402
+ gap: "8px",
1403
+ marginBottom: "12px",
1404
+ paddingBottom: "8px",
1405
+ borderBottom: "1px solid #bae6fd"
1406
+ };
1407
+ const preStyle = {
1408
+ flex: 1,
1409
+ fontSize: "14px",
1410
+ color: "#374151",
1411
+ whiteSpace: "pre-wrap",
1412
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
1413
+ overflow: "auto",
1414
+ margin: 0
1415
+ };
1416
+ if (loading) {
1417
+ return React4.createElement(
1418
+ "div",
1419
+ { style: centeredStyle },
1420
+ React4.createElement("span", { style: { color: "#9ca3af" } }, "Loading...")
1421
+ );
1422
+ }
1423
+ if (error) {
1424
+ return React4.createElement(
1425
+ "div",
1426
+ { style: centeredStyle },
1427
+ React4.createElement("span", { style: { color: "#ef4444" } }, "Failed to load markdown")
1428
+ );
1429
+ }
1430
+ const children = [];
1431
+ if (filename) {
1432
+ children.push(
1433
+ React4.createElement(
1434
+ "div",
1435
+ { key: "header", style: headerStyle },
1436
+ React4.createElement("span", { style: { color: "#0284c7", fontWeight: 500, fontSize: "14px" } }, "\u{1F4C4} " + filename)
1437
+ )
1438
+ );
1439
+ }
1440
+ children.push(React4.createElement("pre", { key: "content", style: preStyle }, content));
1441
+ return React4.createElement("div", { style: containerStyle }, ...children);
1442
+ }
1027
1443
  var createSlide = {
1028
1444
  /**
1029
1445
  * Create a simple image slide (single image, full slide)
@@ -1066,8 +1482,77 @@ var createSlide = {
1066
1482
  position: { x: 0, y: 0 },
1067
1483
  size: { width: 100, height: 100, unit: "percent" }
1068
1484
  }], options);
1485
+ },
1486
+ /**
1487
+ * Create a slide to display a markdown file
1488
+ */
1489
+ markdown: (id, url, options) => {
1490
+ const thumbnail = options?.thumbnail ?? markdownThumbnailSvg;
1491
+ return createSimpleSlide(id, [{
1492
+ id: `${id}-markdown`,
1493
+ type: "component",
1494
+ render: () => React4.createElement(MarkdownContent, { url, filename: options?.filename }),
1495
+ position: { x: 0, y: 0 },
1496
+ size: { width: 100, height: 100, unit: "percent" }
1497
+ }], { thumbnail });
1498
+ },
1499
+ /**
1500
+ * Create a slide to display an audio file
1501
+ */
1502
+ audio: (id, url, options) => {
1503
+ const thumbnail = options?.thumbnail ?? audioThumbnailSvg;
1504
+ return createSimpleSlide(id, [{
1505
+ id: `${id}-audio`,
1506
+ type: "component",
1507
+ render: () => React4.createElement(AudioContent, { url, filename: options?.filename }),
1508
+ position: { x: 0, y: 0 },
1509
+ size: { width: 100, height: 100, unit: "percent" }
1510
+ }], { thumbnail });
1069
1511
  }
1070
1512
  };
1513
+ var markdownThumbnailSvg = "data:image/svg+xml," + encodeURIComponent(
1514
+ '<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>'
1515
+ );
1516
+ var audioThumbnailSvg = "data:image/svg+xml," + encodeURIComponent(
1517
+ '<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>'
1518
+ );
1519
+ function AudioContent({ url, filename }) {
1520
+ const containerStyle = {
1521
+ width: "100%",
1522
+ height: "100%",
1523
+ display: "flex",
1524
+ flexDirection: "column",
1525
+ alignItems: "center",
1526
+ justifyContent: "center",
1527
+ backgroundColor: "#171717",
1528
+ padding: "16px"
1529
+ };
1530
+ const iconStyle = {
1531
+ width: "48px",
1532
+ height: "48px",
1533
+ color: "#34d399",
1534
+ marginBottom: "12px"
1535
+ };
1536
+ const filenameStyle = {
1537
+ color: "#d1d5db",
1538
+ fontSize: "14px",
1539
+ marginBottom: "16px"
1540
+ };
1541
+ const children = [
1542
+ React4.createElement(
1543
+ "svg",
1544
+ { key: "icon", style: iconStyle, viewBox: "0 0 24 24", fill: "currentColor" },
1545
+ 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" })
1546
+ )
1547
+ ];
1548
+ if (filename) {
1549
+ children.push(React4.createElement("span", { key: "filename", style: filenameStyle }, filename));
1550
+ }
1551
+ children.push(
1552
+ React4.createElement("audio", { key: "audio", src: url, controls: true, style: { width: "100%", maxWidth: "280px" } })
1553
+ );
1554
+ return React4.createElement("div", { style: containerStyle }, ...children);
1555
+ }
1071
1556
  export {
1072
1557
  Carousel,
1073
1558
  SlideRenderer,