uix-image-magnify 0.2.0 → 0.2.1

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.
@@ -67,7 +67,6 @@ declare function useImageMagnify(options?: UseImageMagnifyOptions): {
67
67
  interface ImageMagnifyProps extends UseImageMagnifyOptions {
68
68
  src: string;
69
69
  alt: string;
70
- /** Rendered width of the image (height follows aspect ratio). */
71
70
  width?: number | string;
72
71
  className?: string;
73
72
  style?: CSSProperties;
@@ -77,10 +76,9 @@ interface ImageMagnifyProps extends UseImageMagnifyOptions {
77
76
  lensStyle?: CSSProperties;
78
77
  paneClassName?: string;
79
78
  paneStyle?: CSSProperties;
80
- paneWidth?: number;
81
- paneHeight?: number;
79
+ paneWidth?: number | string;
80
+ paneHeight?: number | string;
82
81
  lazy?: boolean;
83
- /** Extra attributes forwarded to the underlying <img>. */
84
82
  imageProps?: Omit<ImgHTMLAttributes<HTMLImageElement>, "src" | "alt" | "style">;
85
83
  onLoad?: ImgHTMLAttributes<HTMLImageElement>["onLoad"];
86
84
  onError?: ImgHTMLAttributes<HTMLImageElement>["onError"];
@@ -67,7 +67,6 @@ declare function useImageMagnify(options?: UseImageMagnifyOptions): {
67
67
  interface ImageMagnifyProps extends UseImageMagnifyOptions {
68
68
  src: string;
69
69
  alt: string;
70
- /** Rendered width of the image (height follows aspect ratio). */
71
70
  width?: number | string;
72
71
  className?: string;
73
72
  style?: CSSProperties;
@@ -77,10 +76,9 @@ interface ImageMagnifyProps extends UseImageMagnifyOptions {
77
76
  lensStyle?: CSSProperties;
78
77
  paneClassName?: string;
79
78
  paneStyle?: CSSProperties;
80
- paneWidth?: number;
81
- paneHeight?: number;
79
+ paneWidth?: number | string;
80
+ paneHeight?: number | string;
82
81
  lazy?: boolean;
83
- /** Extra attributes forwarded to the underlying <img>. */
84
82
  imageProps?: Omit<ImgHTMLAttributes<HTMLImageElement>, "src" | "alt" | "style">;
85
83
  onLoad?: ImgHTMLAttributes<HTMLImageElement>["onLoad"];
86
84
  onError?: ImgHTMLAttributes<HTMLImageElement>["onError"];
package/dist/index.cjs CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  var react = require('react');
4
4
  var jsxRuntime = require('react/jsx-runtime');
5
+ var react$1 = require('swiper/react');
6
+ var modules = require('swiper/modules');
7
+ require('swiper/css');
8
+ require('swiper/css/navigation');
9
+ require('swiper/css/thumbs');
5
10
 
6
11
  // src/components/ImageMagnify.tsx
7
12
  var clamp = (v, min, max) => Math.max(min, Math.min(max, v));
@@ -413,6 +418,19 @@ function useImageMagnify(options = {}) {
413
418
  handlers
414
419
  };
415
420
  }
421
+ var useIsoLayoutEffect2 = typeof window !== "undefined" ? react.useLayoutEffect : react.useEffect;
422
+ function useMediaQuery(query) {
423
+ const [matches, setMatches] = react.useState(false);
424
+ useIsoLayoutEffect2(() => {
425
+ const media = window.matchMedia(query);
426
+ const listener = () => setMatches(media.matches);
427
+ listener();
428
+ media.addEventListener("change", listener);
429
+ return () => media.removeEventListener("change", listener);
430
+ }, [query]);
431
+ return matches;
432
+ }
433
+ var MOBILE_QUERY = "(max-width: 768px)";
416
434
  var ImageMagnify = react.forwardRef(
417
435
  (props, ref) => {
418
436
  const {
@@ -441,6 +459,8 @@ var ImageMagnify = react.forwardRef(
441
459
  onError,
442
460
  ...hookOptions
443
461
  } = props;
462
+ const isMobile = useMediaQuery(MOBILE_QUERY);
463
+ const responsivePanePosition = isMobile ? "bottom" : panePosition;
444
464
  const {
445
465
  isZoomed,
446
466
  bgImage,
@@ -458,25 +478,36 @@ var ImageMagnify = react.forwardRef(
458
478
  disabled,
459
479
  ...hookOptions
460
480
  });
461
- const paneOffset = {};
462
- if (panePosition === "right") {
463
- paneOffset.left = "calc(100% + 12px)";
464
- paneOffset.top = 0;
465
- } else if (panePosition === "left") {
466
- paneOffset.right = "calc(100% + 12px)";
467
- paneOffset.top = 0;
468
- } else if (panePosition === "top") {
469
- paneOffset.left = 0;
470
- paneOffset.bottom = "calc(100% + 12px)";
481
+ const panePositionStyle = {};
482
+ if (responsivePanePosition === "right") {
483
+ panePositionStyle.position = "absolute";
484
+ panePositionStyle.left = "calc(100% + 12px)";
485
+ panePositionStyle.top = 0;
486
+ } else if (responsivePanePosition === "left") {
487
+ panePositionStyle.position = "absolute";
488
+ panePositionStyle.right = "calc(100% + 12px)";
489
+ panePositionStyle.top = 0;
490
+ } else if (responsivePanePosition === "top") {
491
+ panePositionStyle.position = "absolute";
492
+ panePositionStyle.left = 0;
493
+ panePositionStyle.bottom = "calc(100% + 12px)";
494
+ } else if (isMobile) {
495
+ panePositionStyle.position = "static";
496
+ panePositionStyle.display = "block";
497
+ panePositionStyle.marginTop = 12;
498
+ panePositionStyle.width = "100%";
499
+ panePositionStyle.boxSizing = "border-box";
471
500
  } else {
472
- paneOffset.left = 0;
473
- paneOffset.top = "calc(100% + 12px)";
501
+ panePositionStyle.position = "absolute";
502
+ panePositionStyle.left = 0;
503
+ panePositionStyle.top = "calc(100% + 12px)";
474
504
  }
475
505
  const isInner = mode === "inner";
476
506
  const showLens = (mode === "lens" || mode === "follow") && isZoomed;
477
507
  const showPane = mode === "side-by-side" && isZoomed;
478
508
  const showCursor = mode === "side-by-side" && isZoomed;
479
509
  const cursorStyle = disabled ? "default" : zoomType === "hover" || zoomType === "hold" || zoomType === "wheel" ? "crosshair" : isZoomed ? "zoom-out" : "zoom-in";
510
+ const outerWidth = isMobile ? "100%" : width;
480
511
  return /* @__PURE__ */ jsxRuntime.jsxs(
481
512
  "div",
482
513
  {
@@ -486,7 +517,8 @@ var ImageMagnify = react.forwardRef(
486
517
  position: "relative",
487
518
  display: "inline-block",
488
519
  lineHeight: 0,
489
- width,
520
+ width: outerWidth,
521
+ maxWidth: "100%",
490
522
  ...style
491
523
  },
492
524
  children: [
@@ -503,7 +535,6 @@ var ImageMagnify = react.forwardRef(
503
535
  overflow: "hidden",
504
536
  width: "100%",
505
537
  cursor: cursorStyle,
506
- // Only block scrolling while zoomed so page scrolling still works.
507
538
  touchAction: isZoomed ? "none" : "pan-y",
508
539
  outline: "none",
509
540
  lineHeight: 0
@@ -590,16 +621,17 @@ var ImageMagnify = react.forwardRef(
590
621
  className: paneClassName,
591
622
  "aria-hidden": "true",
592
623
  style: {
593
- position: "absolute",
594
- width: paneWidth,
624
+ width: isMobile && responsivePanePosition === "bottom" ? "100%" : paneWidth,
595
625
  height: paneHeight,
626
+ maxWidth: "100%",
627
+ boxSizing: "border-box",
596
628
  backgroundImage: bgImage ? `url("${bgImage}")` : void 0,
597
629
  backgroundRepeat: "no-repeat",
598
630
  border: "1px solid #e5e5e5",
599
631
  borderRadius: 4,
600
632
  boxShadow: "0 8px 24px rgba(0,0,0,0.12)",
601
633
  pointerEvents: "none",
602
- ...paneOffset,
634
+ ...panePositionStyle,
603
635
  ...paneStyle
604
636
  }
605
637
  }
@@ -610,45 +642,6 @@ var ImageMagnify = react.forwardRef(
610
642
  }
611
643
  );
612
644
  ImageMagnify.displayName = "ImageMagnify";
613
- function useImageSlider({
614
- length,
615
- defaultIndex = 0,
616
- index: controlledIndex,
617
- onIndexChange,
618
- loop = true
619
- }) {
620
- const [internalIndex, setInternalIndex] = react.useState(defaultIndex);
621
- const safeLength = Math.max(1, length);
622
- const rawIndex = controlledIndex ?? internalIndex;
623
- const index = Math.max(0, Math.min(safeLength - 1, rawIndex));
624
- const setIndex = react.useCallback(
625
- (next2) => {
626
- if (length <= 0) return;
627
- let resolved;
628
- if (loop) {
629
- resolved = (next2 % safeLength + safeLength) % safeLength;
630
- } else {
631
- resolved = Math.max(0, Math.min(safeLength - 1, next2));
632
- }
633
- if (resolved === index) return;
634
- if (controlledIndex === void 0) {
635
- setInternalIndex(resolved);
636
- }
637
- onIndexChange?.(resolved);
638
- },
639
- [length, safeLength, loop, index, controlledIndex, onIndexChange]
640
- );
641
- const prev = react.useCallback(() => setIndex(index - 1), [setIndex, index]);
642
- const next = react.useCallback(() => setIndex(index + 1), [setIndex, index]);
643
- return {
644
- index,
645
- canPrev: loop || index > 0,
646
- canNext: loop || index < safeLength - 1,
647
- setIndex,
648
- prev,
649
- next
650
- };
651
- }
652
645
  var ArrowLeftIcon = () => /* @__PURE__ */ jsxRuntime.jsx(
653
646
  "svg",
654
647
  {
@@ -700,30 +693,6 @@ var defaultArrowBaseStyle = {
700
693
  backdropFilter: "blur(4px)",
701
694
  WebkitBackdropFilter: "blur(4px)"
702
695
  };
703
- var defaultThumbStripStyle = {
704
- display: "flex",
705
- overflowX: "auto",
706
- overflowY: "hidden",
707
- paddingBottom: 4,
708
- scrollbarWidth: "thin",
709
- WebkitOverflowScrolling: "touch"
710
- };
711
- var defaultThumbBaseStyle = {
712
- flexShrink: 0,
713
- padding: 0,
714
- border: "2px solid #e5e5e5",
715
- background: "#fff",
716
- cursor: "pointer",
717
- overflow: "hidden",
718
- transition: "border-color 0.15s ease",
719
- borderRadius: 2,
720
- display: "inline-flex",
721
- alignItems: "center",
722
- justifyContent: "center"
723
- };
724
- var defaultThumbActiveStyle = {
725
- borderColor: "#111"
726
- };
727
696
  var visuallyHidden = {
728
697
  position: "absolute",
729
698
  width: 1,
@@ -764,70 +733,51 @@ var ImageMagnifyGallery = react.forwardRef((props, ref) => {
764
733
  thumbnailGap = 8,
765
734
  prevArrow,
766
735
  nextArrow,
736
+ width,
767
737
  ...magnifyProps
768
738
  } = props;
769
- const { index, canPrev, canNext, setIndex, prev, next } = useImageSlider({
770
- length: images.length,
771
- defaultIndex,
772
- index: controlledActiveIndex,
773
- onIndexChange,
774
- loop
775
- });
776
- const currentSrc = images[index] ?? "";
777
- const thumbStripRef = react.useRef(null);
778
- const [hoveredThumb, setHoveredThumb] = react.useState(null);
739
+ const [swiper, setSwiper] = react.useState(null);
740
+ const [thumbsSwiper, setThumbsSwiper] = react.useState(null);
741
+ const [localIndex, setLocalIndex] = react.useState(defaultIndex);
742
+ const prevRef = react.useRef(null);
743
+ const nextRef = react.useRef(null);
744
+ const isControlled = controlledActiveIndex !== void 0;
745
+ const activeIndex = isControlled ? controlledActiveIndex : localIndex;
746
+ const hasMultiple = images.length > 1;
779
747
  react.useEffect(() => {
780
- if (!preloadAdjacent || typeof window === "undefined") return;
781
- if (images.length <= 1) return;
782
- const targets = /* @__PURE__ */ new Set();
783
- targets.add((index + 1) % images.length);
784
- targets.add((index - 1 + images.length) % images.length);
748
+ if (!swiper || swiper.destroyed) return;
749
+ if (isControlled && swiper.activeIndex !== controlledActiveIndex) {
750
+ swiper.slideTo(controlledActiveIndex);
751
+ }
752
+ }, [isControlled, controlledActiveIndex, swiper]);
753
+ react.useEffect(() => {
754
+ if (!preloadAdjacent || typeof window === "undefined" || images.length <= 1)
755
+ return;
756
+ const targets = /* @__PURE__ */ new Set([
757
+ (activeIndex + 1) % images.length,
758
+ (activeIndex - 1 + images.length) % images.length
759
+ ]);
785
760
  targets.forEach((i) => {
786
761
  if (images[i]) {
787
762
  const img = new Image();
788
763
  img.src = images[i];
789
764
  }
790
765
  });
791
- }, [index, images, preloadAdjacent]);
792
- react.useEffect(() => {
793
- const strip = thumbStripRef.current;
794
- if (!strip) return;
795
- const active = strip.children[index];
796
- if (!active) return;
797
- const sr = strip.getBoundingClientRect();
798
- const ar = active.getBoundingClientRect();
799
- if (ar.left < sr.left) {
800
- strip.scrollBy({ left: ar.left - sr.left, behavior: "smooth" });
801
- } else if (ar.right > sr.right) {
802
- strip.scrollBy({ left: ar.right - sr.right, behavior: "smooth" });
803
- }
804
- }, [index]);
805
- const handleKeyDown = react.useCallback(
806
- (e) => {
807
- if (!enableKeyboard) return;
808
- if (e.key === "ArrowLeft") {
809
- if (!canPrev) return;
810
- e.preventDefault();
811
- prev();
812
- } else if (e.key === "ArrowRight") {
813
- if (!canNext) return;
814
- e.preventDefault();
815
- next();
816
- }
817
- },
818
- [enableKeyboard, canPrev, canNext, prev, next]
819
- );
820
- const hasMultiple = images.length > 1;
821
- const liveText = react.useMemo(
822
- () => `Image ${index + 1} of ${images.length}`,
823
- [index, images.length]
824
- );
766
+ }, [activeIndex, images, preloadAdjacent]);
767
+ const modules$1 = react.useMemo(() => {
768
+ const m = [modules.Navigation, modules.Thumbs];
769
+ if (enableKeyboard) m.push(modules.Keyboard);
770
+ return m;
771
+ }, [enableKeyboard]);
772
+ if (images.length === 0) {
773
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className, style });
774
+ }
825
775
  return /* @__PURE__ */ jsxRuntime.jsxs(
826
776
  "div",
827
777
  {
828
778
  ref,
829
779
  className,
830
- style,
780
+ style: { maxWidth: "100%", width, ...style },
831
781
  role: "region",
832
782
  "aria-roledescription": "carousel",
833
783
  "aria-label": `${alt} \u2014 image gallery`,
@@ -836,53 +786,80 @@ var ImageMagnifyGallery = react.forwardRef((props, ref) => {
836
786
  "div",
837
787
  {
838
788
  className: viewportClassName,
839
- style: {
840
- position: "relative",
841
- width: "fit-content",
842
- maxWidth: "100%",
843
- ...viewportStyle
844
- },
845
- tabIndex: enableKeyboard && hasMultiple ? 0 : void 0,
846
- onKeyDown: handleKeyDown,
789
+ style: { position: "relative", width: "100%", ...viewportStyle },
847
790
  children: [
848
- /* @__PURE__ */ jsxRuntime.jsx(ImageMagnify, { ...magnifyProps, src: currentSrc, alt }),
849
- showArrows && hasMultiple && canPrev && /* @__PURE__ */ jsxRuntime.jsx(
850
- "button",
791
+ /* @__PURE__ */ jsxRuntime.jsx(
792
+ react$1.Swiper,
851
793
  {
852
- type: "button",
853
- onClick: prev,
854
- className: arrowClassName,
855
- style: {
856
- ...defaultArrowBaseStyle,
857
- left: 12,
858
- ...arrowStyle
794
+ modules: modules$1,
795
+ onSwiper: setSwiper,
796
+ initialSlide: isControlled ? controlledActiveIndex : defaultIndex,
797
+ onSlideChange: (s) => {
798
+ if (!isControlled) setLocalIndex(s.activeIndex);
799
+ onIndexChange?.(s.activeIndex);
859
800
  },
860
- "aria-label": "Previous image",
861
- children: prevArrow ?? /* @__PURE__ */ jsxRuntime.jsx(ArrowLeftIcon, {})
862
- }
863
- ),
864
- showArrows && hasMultiple && canNext && /* @__PURE__ */ jsxRuntime.jsx(
865
- "button",
866
- {
867
- type: "button",
868
- onClick: next,
869
- className: arrowClassName,
801
+ onBeforeInit: (s) => {
802
+ if (!showArrows || !hasMultiple) return;
803
+ if (s.params.navigation && typeof s.params.navigation === "object") {
804
+ s.params.navigation.prevEl = prevRef.current;
805
+ s.params.navigation.nextEl = nextRef.current;
806
+ }
807
+ },
808
+ navigation: showArrows && hasMultiple ? {} : false,
809
+ keyboard: enableKeyboard ? { enabled: true } : void 0,
810
+ thumbs: {
811
+ swiper: thumbsSwiper && !thumbsSwiper.destroyed ? thumbsSwiper : null
812
+ },
813
+ loop: loop && hasMultiple,
814
+ autoHeight: true,
815
+ spaceBetween: 10,
870
816
  style: {
871
- ...defaultArrowBaseStyle,
872
- right: 12,
817
+ width: "100%",
818
+ "--swiper-navigation-size": "20px",
873
819
  ...arrowStyle
874
820
  },
875
- "aria-label": "Next image",
876
- children: nextArrow ?? /* @__PURE__ */ jsxRuntime.jsx(ArrowRightIcon, {})
821
+ children: images.map((src, idx) => /* @__PURE__ */ jsxRuntime.jsx(react$1.SwiperSlide, { children: /* @__PURE__ */ jsxRuntime.jsx(
822
+ ImageMagnify,
823
+ {
824
+ ...magnifyProps,
825
+ src,
826
+ alt,
827
+ width: "100%"
828
+ }
829
+ ) }, idx))
877
830
  }
878
831
  ),
832
+ showArrows && hasMultiple && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
833
+ /* @__PURE__ */ jsxRuntime.jsx(
834
+ "button",
835
+ {
836
+ ref: prevRef,
837
+ type: "button",
838
+ className: arrowClassName,
839
+ style: { ...defaultArrowBaseStyle, left: 12 },
840
+ "aria-label": "Previous image",
841
+ children: prevArrow ?? /* @__PURE__ */ jsxRuntime.jsx(ArrowLeftIcon, {})
842
+ }
843
+ ),
844
+ /* @__PURE__ */ jsxRuntime.jsx(
845
+ "button",
846
+ {
847
+ ref: nextRef,
848
+ type: "button",
849
+ className: arrowClassName,
850
+ style: { ...defaultArrowBaseStyle, right: 12 },
851
+ "aria-label": "Next image",
852
+ children: nextArrow ?? /* @__PURE__ */ jsxRuntime.jsx(ArrowRightIcon, {})
853
+ }
854
+ )
855
+ ] }),
879
856
  showCounter && hasMultiple && /* @__PURE__ */ jsxRuntime.jsxs(
880
857
  "div",
881
858
  {
882
859
  "aria-hidden": "true",
883
860
  style: {
884
861
  position: "absolute",
885
- bottom: 12,
862
+ top: 12,
886
863
  right: 12,
887
864
  background: "rgba(0,0,0,0.6)",
888
865
  color: "#fff",
@@ -894,74 +871,57 @@ var ImageMagnifyGallery = react.forwardRef((props, ref) => {
894
871
  pointerEvents: "none"
895
872
  },
896
873
  children: [
897
- index + 1,
874
+ activeIndex + 1,
898
875
  " / ",
899
876
  images.length
900
877
  ]
901
878
  }
902
879
  ),
903
- /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-live": "polite", "aria-atomic": "true", style: visuallyHidden, children: liveText })
880
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { "aria-live": "polite", "aria-atomic": "true", style: visuallyHidden, children: [
881
+ "Image ",
882
+ activeIndex + 1,
883
+ " of ",
884
+ images.length
885
+ ] })
904
886
  ]
905
887
  }
906
888
  ),
907
889
  showThumbnails && hasMultiple && /* @__PURE__ */ jsxRuntime.jsx(
908
- "div",
890
+ react$1.Swiper,
909
891
  {
910
- ref: thumbStripRef,
911
- className: thumbnailContainerClassName,
912
- role: "tablist",
913
- "aria-label": "Choose image",
914
- style: {
915
- ...defaultThumbStripStyle,
916
- gap: thumbnailGap,
917
- marginTop: 12,
918
- ...thumbnailContainerStyle
892
+ modules: modules$1,
893
+ onSwiper: setSwiper,
894
+ initialSlide: isControlled ? controlledActiveIndex : defaultIndex,
895
+ onSlideChange: (s) => {
896
+ if (!isControlled) setLocalIndex(s.activeIndex);
897
+ onIndexChange?.(s.activeIndex);
919
898
  },
920
- children: images.map((img, idx) => {
921
- const isActive = idx === index;
922
- const isHovered = idx === hoveredThumb;
923
- const cls = isActive ? thumbnailActiveClassName : thumbnailClassName;
924
- return /* @__PURE__ */ jsxRuntime.jsx(
925
- "button",
926
- {
927
- type: "button",
928
- role: "tab",
929
- onClick: () => setIndex(idx),
930
- onMouseEnter: () => setHoveredThumb(idx),
931
- onMouseLeave: () => setHoveredThumb(null),
932
- className: cls,
933
- style: {
934
- ...defaultThumbBaseStyle,
935
- width: thumbnailSize,
936
- height: thumbnailSize,
937
- ...thumbnailStyle,
938
- ...isActive ? defaultThumbActiveStyle : void 0,
939
- ...isActive ? thumbnailActiveStyle : void 0,
940
- ...isHovered && !isActive ? { borderColor: "#999" } : void 0
941
- },
942
- "aria-label": `View image ${idx + 1} of ${images.length}`,
943
- "aria-selected": isActive,
944
- children: /* @__PURE__ */ jsxRuntime.jsx(
945
- "img",
946
- {
947
- src: img,
948
- alt: "",
949
- loading: "lazy",
950
- draggable: false,
951
- style: {
952
- width: "100%",
953
- height: "100%",
954
- objectFit: "contain",
955
- display: "block",
956
- pointerEvents: "none",
957
- userSelect: "none"
958
- }
959
- }
960
- )
961
- },
962
- idx
963
- );
964
- })
899
+ onBeforeInit: (s) => {
900
+ if (!showArrows || !hasMultiple) return;
901
+ const nav = s.params.navigation;
902
+ if (nav && typeof nav === "object") {
903
+ nav.prevEl = prevRef.current;
904
+ nav.nextEl = nextRef.current;
905
+ }
906
+ },
907
+ navigation: showArrows && hasMultiple ? {} : false,
908
+ keyboard: enableKeyboard ? { enabled: true } : void 0,
909
+ thumbs: {
910
+ swiper: thumbsSwiper && !thumbsSwiper.destroyed ? thumbsSwiper : null
911
+ },
912
+ loop: loop && hasMultiple,
913
+ autoHeight: true,
914
+ spaceBetween: 10,
915
+ style: { width: "100%" },
916
+ children: images.map((src, idx) => /* @__PURE__ */ jsxRuntime.jsx(react$1.SwiperSlide, { children: /* @__PURE__ */ jsxRuntime.jsx(
917
+ ImageMagnify,
918
+ {
919
+ ...magnifyProps,
920
+ src,
921
+ alt,
922
+ width: "100%"
923
+ }
924
+ ) }, idx))
965
925
  }
966
926
  )
967
927
  ]
@@ -969,6 +929,45 @@ var ImageMagnifyGallery = react.forwardRef((props, ref) => {
969
929
  );
970
930
  });
971
931
  ImageMagnifyGallery.displayName = "ImageMagnifyGallery";
932
+ function useImageSlider({
933
+ length,
934
+ defaultIndex = 0,
935
+ index: controlledIndex,
936
+ onIndexChange,
937
+ loop = true
938
+ }) {
939
+ const [internalIndex, setInternalIndex] = react.useState(defaultIndex);
940
+ const safeLength = Math.max(1, length);
941
+ const rawIndex = controlledIndex ?? internalIndex;
942
+ const index = Math.max(0, Math.min(safeLength - 1, rawIndex));
943
+ const setIndex = react.useCallback(
944
+ (next2) => {
945
+ if (length <= 0) return;
946
+ let resolved;
947
+ if (loop) {
948
+ resolved = (next2 % safeLength + safeLength) % safeLength;
949
+ } else {
950
+ resolved = Math.max(0, Math.min(safeLength - 1, next2));
951
+ }
952
+ if (resolved === index) return;
953
+ if (controlledIndex === void 0) {
954
+ setInternalIndex(resolved);
955
+ }
956
+ onIndexChange?.(resolved);
957
+ },
958
+ [length, safeLength, loop, index, controlledIndex, onIndexChange]
959
+ );
960
+ const prev = react.useCallback(() => setIndex(index - 1), [setIndex, index]);
961
+ const next = react.useCallback(() => setIndex(index + 1), [setIndex, index]);
962
+ return {
963
+ index,
964
+ canPrev: loop || index > 0,
965
+ canNext: loop || index < safeLength - 1,
966
+ setIndex,
967
+ prev,
968
+ next
969
+ };
970
+ }
972
971
 
973
972
  exports.ImageMagnify = ImageMagnify;
974
973
  exports.ImageMagnifyGallery = ImageMagnifyGallery;