uix-image-magnify 0.2.2 → 0.2.4

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.
@@ -2,6 +2,10 @@
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');
5
9
 
6
10
  // src/ssr/index.tsx
7
11
  var clamp = (v, min, max) => Math.max(min, Math.min(max, v));
@@ -77,6 +81,15 @@ function useImageMagnify(options = {}) {
77
81
  isZoomedRef.current = z;
78
82
  setIsZoomed(z);
79
83
  if (z) {
84
+ if (modeRef.current === "inner" && dragModeRef.current === "pan") {
85
+ const container = containerRef.current;
86
+ if (container) {
87
+ const rect = container.getBoundingClientRect();
88
+ const s = scaleRef.current;
89
+ txRef.current = rect.width * (1 - s) / 2;
90
+ tyRef.current = rect.height * (1 - s) / 2;
91
+ }
92
+ }
80
93
  onZoomStart?.();
81
94
  } else {
82
95
  if (zoomType === "wheel") {
@@ -283,8 +296,6 @@ function useImageMagnify(options = {}) {
283
296
  setZoomed(!isZoomedRef.current);
284
297
  } else if (zoomType === "click") {
285
298
  setZoomed(!isZoomedRef.current);
286
- } else if (zoomType === "wheel") {
287
- setZoomed(!isZoomedRef.current);
288
299
  }
289
300
  schedule();
290
301
  },
@@ -501,7 +512,8 @@ var ImageMagnify = react.forwardRef(
501
512
  const showLens = (mode === "lens" || mode === "follow") && isZoomed;
502
513
  const showPane = mode === "side-by-side" && isZoomed;
503
514
  const showCursor = mode === "side-by-side" && isZoomed;
504
- const cursorStyle = disabled ? "default" : zoomType === "hover" || zoomType === "hold" || zoomType === "wheel" ? "crosshair" : isZoomed ? "zoom-out" : "zoom-in";
515
+ const isInteractive = !disabled && zoomType !== "none";
516
+ const cursorStyle = !isInteractive ? "default" : zoomType === "hover" || zoomType === "hold" || zoomType === "wheel" ? "crosshair" : isZoomed ? "zoom-out" : "zoom-in";
505
517
  const outerWidth = isMobile ? "100%" : width;
506
518
  return /* @__PURE__ */ jsxRuntime.jsxs(
507
519
  "div",
@@ -521,7 +533,7 @@ var ImageMagnify = react.forwardRef(
521
533
  "div",
522
534
  {
523
535
  ref: containerRef,
524
- tabIndex: 0,
536
+ tabIndex: isInteractive ? 0 : -1,
525
537
  role: "img",
526
538
  "aria-label": alt,
527
539
  "aria-disabled": disabled || void 0,
@@ -637,6 +649,394 @@ var ImageMagnify = react.forwardRef(
637
649
  }
638
650
  );
639
651
  ImageMagnify.displayName = "ImageMagnify";
652
+ var ArrowLeftIcon = () => /* @__PURE__ */ jsxRuntime.jsx(
653
+ "svg",
654
+ {
655
+ viewBox: "0 0 24 24",
656
+ width: "20",
657
+ height: "20",
658
+ fill: "none",
659
+ stroke: "currentColor",
660
+ strokeWidth: "2",
661
+ strokeLinecap: "round",
662
+ strokeLinejoin: "round",
663
+ "aria-hidden": "true",
664
+ children: /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "15 18 9 12 15 6" })
665
+ }
666
+ );
667
+ var ArrowRightIcon = () => /* @__PURE__ */ jsxRuntime.jsx(
668
+ "svg",
669
+ {
670
+ viewBox: "0 0 24 24",
671
+ width: "20",
672
+ height: "20",
673
+ fill: "none",
674
+ stroke: "currentColor",
675
+ strokeWidth: "2",
676
+ strokeLinecap: "round",
677
+ strokeLinejoin: "round",
678
+ "aria-hidden": "true",
679
+ children: /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "9 18 15 12 9 6" })
680
+ }
681
+ );
682
+ var defaultArrowBaseStyle = {
683
+ position: "absolute",
684
+ top: "50%",
685
+ transform: "translateY(-50%)",
686
+ width: 40,
687
+ height: 40,
688
+ display: "inline-flex",
689
+ alignItems: "center",
690
+ justifyContent: "center",
691
+ background: "rgba(255,255,255,0.85)",
692
+ border: "1px solid rgba(0,0,0,0.12)",
693
+ borderRadius: 9999,
694
+ boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
695
+ cursor: "pointer",
696
+ zIndex: 10,
697
+ color: "#111",
698
+ padding: 0,
699
+ transition: "background 0.15s ease",
700
+ backdropFilter: "blur(4px)",
701
+ WebkitBackdropFilter: "blur(4px)"
702
+ };
703
+ var defaultThumbBaseStyle = {
704
+ flexShrink: 0,
705
+ padding: 0,
706
+ border: "2px solid #e5e5e5",
707
+ background: "#fff",
708
+ cursor: "pointer",
709
+ overflow: "hidden",
710
+ transition: "border-color 0.15s ease, opacity 0.15s ease",
711
+ borderRadius: 2,
712
+ display: "inline-flex",
713
+ alignItems: "center",
714
+ justifyContent: "center",
715
+ boxSizing: "border-box"
716
+ };
717
+ var defaultThumbActiveStyle = {
718
+ borderColor: "#111"
719
+ };
720
+ var visuallyHidden = {
721
+ position: "absolute",
722
+ width: 1,
723
+ height: 1,
724
+ padding: 0,
725
+ margin: -1,
726
+ overflow: "hidden",
727
+ clip: "rect(0,0,0,0)",
728
+ whiteSpace: "nowrap",
729
+ border: 0
730
+ };
731
+ var ImageMagnifyGallery = react.forwardRef((props, ref) => {
732
+ const {
733
+ images,
734
+ alt,
735
+ activeIndex: controlledActiveIndex,
736
+ defaultIndex = 0,
737
+ onIndexChange,
738
+ showArrows = true,
739
+ showThumbnails = true,
740
+ showCounter = false,
741
+ loop = true,
742
+ enableKeyboard = true,
743
+ preloadAdjacent = true,
744
+ className,
745
+ style,
746
+ viewportClassName,
747
+ viewportStyle,
748
+ swiperStyle,
749
+ thumbnailContainerClassName,
750
+ thumbnailContainerStyle,
751
+ thumbnailClassName,
752
+ thumbnailActiveClassName,
753
+ thumbnailStyle,
754
+ thumbnailActiveStyle,
755
+ arrowClassName,
756
+ arrowStyle,
757
+ thumbnailSize = 80,
758
+ thumbnailGap = 8,
759
+ prevArrow,
760
+ nextArrow,
761
+ width,
762
+ ...magnifyProps
763
+ } = props;
764
+ const [swiper, setSwiper] = react.useState(null);
765
+ const [localIndex, setLocalIndex] = react.useState(() => {
766
+ const safeLength = Math.max(1, images.length);
767
+ return Math.max(0, Math.min(safeLength - 1, defaultIndex));
768
+ });
769
+ const prevRef = react.useRef(null);
770
+ const nextRef = react.useRef(null);
771
+ const isControlled = controlledActiveIndex !== void 0;
772
+ const rawIndex = isControlled ? controlledActiveIndex : localIndex;
773
+ const activeIndex = images.length ? Math.max(0, Math.min(images.length - 1, rawIndex)) : 0;
774
+ const hasMultiple = images.length > 1;
775
+ const modules$1 = react.useMemo(() => [modules.Navigation, modules.Keyboard, modules.A11y], []);
776
+ react.useEffect(() => {
777
+ if (!swiper || swiper.destroyed) return;
778
+ if (isControlled && swiper.activeIndex !== controlledActiveIndex) {
779
+ swiper.slideTo(controlledActiveIndex);
780
+ }
781
+ }, [isControlled, controlledActiveIndex, swiper]);
782
+ react.useEffect(() => {
783
+ if (!preloadAdjacent || typeof window === "undefined" || images.length <= 1)
784
+ return;
785
+ const targets = /* @__PURE__ */ new Set();
786
+ if (loop) {
787
+ targets.add((activeIndex + 1) % images.length);
788
+ targets.add((activeIndex - 1 + images.length) % images.length);
789
+ } else {
790
+ if (activeIndex < images.length - 1) targets.add(activeIndex + 1);
791
+ if (activeIndex > 0) targets.add(activeIndex - 1);
792
+ }
793
+ targets.forEach((i) => {
794
+ const src = images[i];
795
+ if (src) {
796
+ const img = new Image();
797
+ img.src = src;
798
+ }
799
+ });
800
+ }, [activeIndex, images, preloadAdjacent, loop]);
801
+ const handleSlideChange = (s) => {
802
+ const idx = s.activeIndex;
803
+ if (!isControlled) setLocalIndex(idx);
804
+ onIndexChange?.(idx);
805
+ };
806
+ const handleThumbClick = (idx) => {
807
+ if (!swiper || swiper.destroyed) return;
808
+ swiper.slideTo(idx);
809
+ };
810
+ if (images.length === 0) {
811
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className, style });
812
+ }
813
+ return /* @__PURE__ */ jsxRuntime.jsxs(
814
+ "div",
815
+ {
816
+ ref,
817
+ className,
818
+ style: { maxWidth: "100%", width, ...style },
819
+ role: "region",
820
+ "aria-roledescription": "carousel",
821
+ "aria-label": `${alt} \u2014 image gallery`,
822
+ children: [
823
+ /* @__PURE__ */ jsxRuntime.jsxs(
824
+ "div",
825
+ {
826
+ className: viewportClassName,
827
+ style: { position: "relative", width: "100%", ...viewportStyle },
828
+ children: [
829
+ /* @__PURE__ */ jsxRuntime.jsx(
830
+ react$1.Swiper,
831
+ {
832
+ modules: modules$1,
833
+ onSwiper: setSwiper,
834
+ initialSlide: isControlled ? controlledActiveIndex : defaultIndex,
835
+ onSlideChange: handleSlideChange,
836
+ onBeforeInit: (s) => {
837
+ if (!showArrows || !hasMultiple) return;
838
+ const nav = s.params.navigation;
839
+ if (nav && typeof nav === "object") {
840
+ nav.prevEl = prevRef.current;
841
+ nav.nextEl = nextRef.current;
842
+ }
843
+ },
844
+ navigation: showArrows && hasMultiple ? {} : false,
845
+ keyboard: enableKeyboard ? { enabled: true } : false,
846
+ loop: loop && hasMultiple,
847
+ autoHeight: true,
848
+ spaceBetween: 10,
849
+ observer: true,
850
+ observeParents: true,
851
+ style: { width: "100%", ...swiperStyle },
852
+ children: images.map((src, idx) => /* @__PURE__ */ jsxRuntime.jsx(react$1.SwiperSlide, { children: /* @__PURE__ */ jsxRuntime.jsx(
853
+ ImageMagnify,
854
+ {
855
+ ...magnifyProps,
856
+ src,
857
+ alt: `${alt} \u2014 image ${idx + 1} of ${images.length}`,
858
+ width: "100%"
859
+ }
860
+ ) }, idx))
861
+ }
862
+ ),
863
+ showArrows && hasMultiple && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
864
+ /* @__PURE__ */ jsxRuntime.jsx(
865
+ "button",
866
+ {
867
+ ref: prevRef,
868
+ type: "button",
869
+ className: arrowClassName,
870
+ style: { ...defaultArrowBaseStyle, left: 12, ...arrowStyle },
871
+ "aria-label": "Previous image",
872
+ children: prevArrow ?? /* @__PURE__ */ jsxRuntime.jsx(ArrowLeftIcon, {})
873
+ }
874
+ ),
875
+ /* @__PURE__ */ jsxRuntime.jsx(
876
+ "button",
877
+ {
878
+ ref: nextRef,
879
+ type: "button",
880
+ className: arrowClassName,
881
+ style: { ...defaultArrowBaseStyle, right: 12, ...arrowStyle },
882
+ "aria-label": "Next image",
883
+ children: nextArrow ?? /* @__PURE__ */ jsxRuntime.jsx(ArrowRightIcon, {})
884
+ }
885
+ )
886
+ ] }),
887
+ showCounter && hasMultiple && /* @__PURE__ */ jsxRuntime.jsxs(
888
+ "div",
889
+ {
890
+ "aria-hidden": "true",
891
+ style: {
892
+ position: "absolute",
893
+ top: 12,
894
+ right: 12,
895
+ background: "rgba(0,0,0,0.6)",
896
+ color: "#fff",
897
+ fontSize: 12,
898
+ fontWeight: 600,
899
+ padding: "4px 8px",
900
+ borderRadius: 4,
901
+ zIndex: 10,
902
+ pointerEvents: "none"
903
+ },
904
+ children: [
905
+ activeIndex + 1,
906
+ " / ",
907
+ images.length
908
+ ]
909
+ }
910
+ ),
911
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { "aria-live": "polite", "aria-atomic": "true", style: visuallyHidden, children: [
912
+ "Image ",
913
+ activeIndex + 1,
914
+ " of ",
915
+ images.length
916
+ ] })
917
+ ]
918
+ }
919
+ ),
920
+ showThumbnails && hasMultiple && /* @__PURE__ */ jsxRuntime.jsx(
921
+ "div",
922
+ {
923
+ className: thumbnailContainerClassName,
924
+ role: "group",
925
+ "aria-label": "Image thumbnails",
926
+ style: {
927
+ display: "flex",
928
+ gap: thumbnailGap,
929
+ marginTop: 12,
930
+ overflowX: "auto",
931
+ overflowY: "hidden",
932
+ paddingBottom: 4,
933
+ scrollbarWidth: "thin",
934
+ ...thumbnailContainerStyle
935
+ },
936
+ children: images.map((src, idx) => {
937
+ const isActive = idx === activeIndex;
938
+ const mergedClassName = [
939
+ thumbnailClassName,
940
+ isActive ? thumbnailActiveClassName : void 0
941
+ ].filter(Boolean).join(" ") || void 0;
942
+ return /* @__PURE__ */ jsxRuntime.jsx(
943
+ "button",
944
+ {
945
+ type: "button",
946
+ onClick: () => handleThumbClick(idx),
947
+ className: mergedClassName,
948
+ style: {
949
+ ...defaultThumbBaseStyle,
950
+ width: thumbnailSize,
951
+ height: thumbnailSize,
952
+ ...thumbnailStyle,
953
+ ...isActive ? { ...defaultThumbActiveStyle, ...thumbnailActiveStyle } : void 0
954
+ },
955
+ "aria-label": `View image ${idx + 1}`,
956
+ "aria-current": isActive ? "true" : void 0,
957
+ children: /* @__PURE__ */ jsxRuntime.jsx(
958
+ "img",
959
+ {
960
+ src,
961
+ alt: "",
962
+ loading: "lazy",
963
+ draggable: false,
964
+ style: {
965
+ width: "100%",
966
+ height: "100%",
967
+ objectFit: "cover",
968
+ display: "block",
969
+ pointerEvents: "none"
970
+ }
971
+ }
972
+ )
973
+ },
974
+ idx
975
+ );
976
+ })
977
+ }
978
+ )
979
+ ]
980
+ }
981
+ );
982
+ });
983
+ ImageMagnifyGallery.displayName = "ImageMagnifyGallery";
984
+ function ImageMagnifyGallerySSR(props) {
985
+ const [mounted, setMounted] = react.useState(false);
986
+ react.useEffect(() => {
987
+ setMounted(true);
988
+ }, []);
989
+ if (!mounted) {
990
+ const {
991
+ images,
992
+ alt,
993
+ defaultIndex = 0,
994
+ activeIndex,
995
+ width,
996
+ className,
997
+ style,
998
+ imageClassName,
999
+ imageStyle,
1000
+ imageProps,
1001
+ lazy
1002
+ } = props;
1003
+ const idx = activeIndex ?? defaultIndex;
1004
+ const src = images?.[idx] ?? images?.[0] ?? "";
1005
+ return /* @__PURE__ */ jsxRuntime.jsx(
1006
+ "div",
1007
+ {
1008
+ className,
1009
+ style: {
1010
+ position: "relative",
1011
+ display: "inline-block",
1012
+ lineHeight: 0,
1013
+ width,
1014
+ ...style
1015
+ },
1016
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1017
+ "img",
1018
+ {
1019
+ ...imageProps,
1020
+ src,
1021
+ alt,
1022
+ loading: lazy ? "lazy" : void 0,
1023
+ draggable: false,
1024
+ className: imageClassName,
1025
+ style: {
1026
+ display: "block",
1027
+ width: "100%",
1028
+ height: "auto",
1029
+ userSelect: "none",
1030
+ pointerEvents: "none",
1031
+ ...imageStyle
1032
+ }
1033
+ }
1034
+ )
1035
+ }
1036
+ );
1037
+ }
1038
+ return /* @__PURE__ */ jsxRuntime.jsx(ImageMagnifyGallery, { ...props });
1039
+ }
640
1040
  function ImageMagnifySSR(props) {
641
1041
  const [mounted, setMounted] = react.useState(false);
642
1042
  react.useEffect(() => {
@@ -695,6 +1095,8 @@ function ImageMagnifySSR(props) {
695
1095
  }
696
1096
 
697
1097
  exports.ImageMagnify = ImageMagnify;
1098
+ exports.ImageMagnifyGallery = ImageMagnifyGallery;
1099
+ exports.ImageMagnifyGallerySSR = ImageMagnifyGallerySSR;
698
1100
  exports.ImageMagnifySSR = ImageMagnifySSR;
699
1101
  //# sourceMappingURL=index.cjs.map
700
1102
  //# sourceMappingURL=index.cjs.map