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.
package/dist/index.js CHANGED
@@ -1,10 +1,9 @@
1
- import { forwardRef, useState, useRef, useEffect, useMemo, useCallback, useLayoutEffect } from 'react';
1
+ import { forwardRef, useState, useRef, useMemo, useEffect, useCallback, useLayoutEffect } from 'react';
2
2
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
3
  import { Swiper, SwiperSlide } from 'swiper/react';
4
- import { Navigation, Thumbs, Keyboard } from 'swiper/modules';
4
+ import { Navigation, Keyboard, A11y } from 'swiper/modules';
5
5
  import 'swiper/css';
6
6
  import 'swiper/css/navigation';
7
- import 'swiper/css/thumbs';
8
7
 
9
8
  // src/components/ImageMagnify.tsx
10
9
  var clamp = (v, min, max) => Math.max(min, Math.min(max, v));
@@ -80,6 +79,15 @@ function useImageMagnify(options = {}) {
80
79
  isZoomedRef.current = z;
81
80
  setIsZoomed(z);
82
81
  if (z) {
82
+ if (modeRef.current === "inner" && dragModeRef.current === "pan") {
83
+ const container = containerRef.current;
84
+ if (container) {
85
+ const rect = container.getBoundingClientRect();
86
+ const s = scaleRef.current;
87
+ txRef.current = rect.width * (1 - s) / 2;
88
+ tyRef.current = rect.height * (1 - s) / 2;
89
+ }
90
+ }
83
91
  onZoomStart?.();
84
92
  } else {
85
93
  if (zoomType === "wheel") {
@@ -286,8 +294,6 @@ function useImageMagnify(options = {}) {
286
294
  setZoomed(!isZoomedRef.current);
287
295
  } else if (zoomType === "click") {
288
296
  setZoomed(!isZoomedRef.current);
289
- } else if (zoomType === "wheel") {
290
- setZoomed(!isZoomedRef.current);
291
297
  }
292
298
  schedule();
293
299
  },
@@ -504,7 +510,8 @@ var ImageMagnify = forwardRef(
504
510
  const showLens = (mode === "lens" || mode === "follow") && isZoomed;
505
511
  const showPane = mode === "side-by-side" && isZoomed;
506
512
  const showCursor = mode === "side-by-side" && isZoomed;
507
- const cursorStyle = disabled ? "default" : zoomType === "hover" || zoomType === "hold" || zoomType === "wheel" ? "crosshair" : isZoomed ? "zoom-out" : "zoom-in";
513
+ const isInteractive = !disabled && zoomType !== "none";
514
+ const cursorStyle = !isInteractive ? "default" : zoomType === "hover" || zoomType === "hold" || zoomType === "wheel" ? "crosshair" : isZoomed ? "zoom-out" : "zoom-in";
508
515
  const outerWidth = isMobile ? "100%" : width;
509
516
  return /* @__PURE__ */ jsxs(
510
517
  "div",
@@ -524,7 +531,7 @@ var ImageMagnify = forwardRef(
524
531
  "div",
525
532
  {
526
533
  ref: containerRef,
527
- tabIndex: 0,
534
+ tabIndex: isInteractive ? 0 : -1,
528
535
  role: "img",
529
536
  "aria-label": alt,
530
537
  "aria-disabled": disabled || void 0,
@@ -691,6 +698,23 @@ var defaultArrowBaseStyle = {
691
698
  backdropFilter: "blur(4px)",
692
699
  WebkitBackdropFilter: "blur(4px)"
693
700
  };
701
+ var defaultThumbBaseStyle = {
702
+ flexShrink: 0,
703
+ padding: 0,
704
+ border: "2px solid #e5e5e5",
705
+ background: "#fff",
706
+ cursor: "pointer",
707
+ overflow: "hidden",
708
+ transition: "border-color 0.15s ease, opacity 0.15s ease",
709
+ borderRadius: 2,
710
+ display: "inline-flex",
711
+ alignItems: "center",
712
+ justifyContent: "center",
713
+ boxSizing: "border-box"
714
+ };
715
+ var defaultThumbActiveStyle = {
716
+ borderColor: "#111"
717
+ };
694
718
  var visuallyHidden = {
695
719
  position: "absolute",
696
720
  width: 1,
@@ -719,6 +743,7 @@ var ImageMagnifyGallery = forwardRef((props, ref) => {
719
743
  style,
720
744
  viewportClassName,
721
745
  viewportStyle,
746
+ swiperStyle,
722
747
  thumbnailContainerClassName,
723
748
  thumbnailContainerStyle,
724
749
  thumbnailClassName,
@@ -735,13 +760,17 @@ var ImageMagnifyGallery = forwardRef((props, ref) => {
735
760
  ...magnifyProps
736
761
  } = props;
737
762
  const [swiper, setSwiper] = useState(null);
738
- const [thumbsSwiper, setThumbsSwiper] = useState(null);
739
- const [localIndex, setLocalIndex] = useState(defaultIndex);
763
+ const [localIndex, setLocalIndex] = useState(() => {
764
+ const safeLength = Math.max(1, images.length);
765
+ return Math.max(0, Math.min(safeLength - 1, defaultIndex));
766
+ });
740
767
  const prevRef = useRef(null);
741
768
  const nextRef = useRef(null);
742
769
  const isControlled = controlledActiveIndex !== void 0;
743
- const activeIndex = isControlled ? controlledActiveIndex : localIndex;
770
+ const rawIndex = isControlled ? controlledActiveIndex : localIndex;
771
+ const activeIndex = images.length ? Math.max(0, Math.min(images.length - 1, rawIndex)) : 0;
744
772
  const hasMultiple = images.length > 1;
773
+ const modules = useMemo(() => [Navigation, Keyboard, A11y], []);
745
774
  useEffect(() => {
746
775
  if (!swiper || swiper.destroyed) return;
747
776
  if (isControlled && swiper.activeIndex !== controlledActiveIndex) {
@@ -751,22 +780,31 @@ var ImageMagnifyGallery = forwardRef((props, ref) => {
751
780
  useEffect(() => {
752
781
  if (!preloadAdjacent || typeof window === "undefined" || images.length <= 1)
753
782
  return;
754
- const targets = /* @__PURE__ */ new Set([
755
- (activeIndex + 1) % images.length,
756
- (activeIndex - 1 + images.length) % images.length
757
- ]);
783
+ const targets = /* @__PURE__ */ new Set();
784
+ if (loop) {
785
+ targets.add((activeIndex + 1) % images.length);
786
+ targets.add((activeIndex - 1 + images.length) % images.length);
787
+ } else {
788
+ if (activeIndex < images.length - 1) targets.add(activeIndex + 1);
789
+ if (activeIndex > 0) targets.add(activeIndex - 1);
790
+ }
758
791
  targets.forEach((i) => {
759
- if (images[i]) {
792
+ const src = images[i];
793
+ if (src) {
760
794
  const img = new Image();
761
- img.src = images[i];
795
+ img.src = src;
762
796
  }
763
797
  });
764
- }, [activeIndex, images, preloadAdjacent]);
765
- const modules = useMemo(() => {
766
- const m = [Navigation, Thumbs];
767
- if (enableKeyboard) m.push(Keyboard);
768
- return m;
769
- }, [enableKeyboard]);
798
+ }, [activeIndex, images, preloadAdjacent, loop]);
799
+ const handleSlideChange = (s) => {
800
+ const idx = s.activeIndex;
801
+ if (!isControlled) setLocalIndex(idx);
802
+ onIndexChange?.(idx);
803
+ };
804
+ const handleThumbClick = (idx) => {
805
+ if (!swiper || swiper.destroyed) return;
806
+ swiper.slideTo(idx);
807
+ };
770
808
  if (images.length === 0) {
771
809
  return /* @__PURE__ */ jsx("div", { ref, className, style });
772
810
  }
@@ -792,36 +830,29 @@ var ImageMagnifyGallery = forwardRef((props, ref) => {
792
830
  modules,
793
831
  onSwiper: setSwiper,
794
832
  initialSlide: isControlled ? controlledActiveIndex : defaultIndex,
795
- onSlideChange: (s) => {
796
- if (!isControlled) setLocalIndex(s.activeIndex);
797
- onIndexChange?.(s.activeIndex);
798
- },
833
+ onSlideChange: handleSlideChange,
799
834
  onBeforeInit: (s) => {
800
835
  if (!showArrows || !hasMultiple) return;
801
- if (s.params.navigation && typeof s.params.navigation === "object") {
802
- s.params.navigation.prevEl = prevRef.current;
803
- s.params.navigation.nextEl = nextRef.current;
836
+ const nav = s.params.navigation;
837
+ if (nav && typeof nav === "object") {
838
+ nav.prevEl = prevRef.current;
839
+ nav.nextEl = nextRef.current;
804
840
  }
805
841
  },
806
842
  navigation: showArrows && hasMultiple ? {} : false,
807
- keyboard: enableKeyboard ? { enabled: true } : void 0,
808
- thumbs: {
809
- swiper: thumbsSwiper && !thumbsSwiper.destroyed ? thumbsSwiper : null
810
- },
843
+ keyboard: enableKeyboard ? { enabled: true } : false,
811
844
  loop: loop && hasMultiple,
812
845
  autoHeight: true,
813
846
  spaceBetween: 10,
814
- style: {
815
- width: "100%",
816
- "--swiper-navigation-size": "20px",
817
- ...arrowStyle
818
- },
847
+ observer: true,
848
+ observeParents: true,
849
+ style: { width: "100%", ...swiperStyle },
819
850
  children: images.map((src, idx) => /* @__PURE__ */ jsx(SwiperSlide, { children: /* @__PURE__ */ jsx(
820
851
  ImageMagnify,
821
852
  {
822
853
  ...magnifyProps,
823
854
  src,
824
- alt,
855
+ alt: `${alt} \u2014 image ${idx + 1} of ${images.length}`,
825
856
  width: "100%"
826
857
  }
827
858
  ) }, idx))
@@ -834,7 +865,7 @@ var ImageMagnifyGallery = forwardRef((props, ref) => {
834
865
  ref: prevRef,
835
866
  type: "button",
836
867
  className: arrowClassName,
837
- style: { ...defaultArrowBaseStyle, left: 12 },
868
+ style: { ...defaultArrowBaseStyle, left: 12, ...arrowStyle },
838
869
  "aria-label": "Previous image",
839
870
  children: prevArrow ?? /* @__PURE__ */ jsx(ArrowLeftIcon, {})
840
871
  }
@@ -845,7 +876,7 @@ var ImageMagnifyGallery = forwardRef((props, ref) => {
845
876
  ref: nextRef,
846
877
  type: "button",
847
878
  className: arrowClassName,
848
- style: { ...defaultArrowBaseStyle, right: 12 },
879
+ style: { ...defaultArrowBaseStyle, right: 12, ...arrowStyle },
849
880
  "aria-label": "Next image",
850
881
  children: nextArrow ?? /* @__PURE__ */ jsx(ArrowRightIcon, {})
851
882
  }
@@ -885,41 +916,62 @@ var ImageMagnifyGallery = forwardRef((props, ref) => {
885
916
  }
886
917
  ),
887
918
  showThumbnails && hasMultiple && /* @__PURE__ */ jsx(
888
- Swiper,
919
+ "div",
889
920
  {
890
- modules,
891
- onSwiper: setSwiper,
892
- initialSlide: isControlled ? controlledActiveIndex : defaultIndex,
893
- onSlideChange: (s) => {
894
- if (!isControlled) setLocalIndex(s.activeIndex);
895
- onIndexChange?.(s.activeIndex);
896
- },
897
- onBeforeInit: (s) => {
898
- if (!showArrows || !hasMultiple) return;
899
- const nav = s.params.navigation;
900
- if (nav && typeof nav === "object") {
901
- nav.prevEl = prevRef.current;
902
- nav.nextEl = nextRef.current;
903
- }
904
- },
905
- navigation: showArrows && hasMultiple ? {} : false,
906
- keyboard: enableKeyboard ? { enabled: true } : void 0,
907
- thumbs: {
908
- swiper: thumbsSwiper && !thumbsSwiper.destroyed ? thumbsSwiper : null
921
+ className: thumbnailContainerClassName,
922
+ role: "group",
923
+ "aria-label": "Image thumbnails",
924
+ style: {
925
+ display: "flex",
926
+ gap: thumbnailGap,
927
+ marginTop: 12,
928
+ overflowX: "auto",
929
+ overflowY: "hidden",
930
+ paddingBottom: 4,
931
+ scrollbarWidth: "thin",
932
+ ...thumbnailContainerStyle
909
933
  },
910
- loop: loop && hasMultiple,
911
- autoHeight: true,
912
- spaceBetween: 10,
913
- style: { width: "100%" },
914
- children: images.map((src, idx) => /* @__PURE__ */ jsx(SwiperSlide, { children: /* @__PURE__ */ jsx(
915
- ImageMagnify,
916
- {
917
- ...magnifyProps,
918
- src,
919
- alt,
920
- width: "100%"
921
- }
922
- ) }, idx))
934
+ children: images.map((src, idx) => {
935
+ const isActive = idx === activeIndex;
936
+ const mergedClassName = [
937
+ thumbnailClassName,
938
+ isActive ? thumbnailActiveClassName : void 0
939
+ ].filter(Boolean).join(" ") || void 0;
940
+ return /* @__PURE__ */ jsx(
941
+ "button",
942
+ {
943
+ type: "button",
944
+ onClick: () => handleThumbClick(idx),
945
+ className: mergedClassName,
946
+ style: {
947
+ ...defaultThumbBaseStyle,
948
+ width: thumbnailSize,
949
+ height: thumbnailSize,
950
+ ...thumbnailStyle,
951
+ ...isActive ? { ...defaultThumbActiveStyle, ...thumbnailActiveStyle } : void 0
952
+ },
953
+ "aria-label": `View image ${idx + 1}`,
954
+ "aria-current": isActive ? "true" : void 0,
955
+ children: /* @__PURE__ */ jsx(
956
+ "img",
957
+ {
958
+ src,
959
+ alt: "",
960
+ loading: "lazy",
961
+ draggable: false,
962
+ style: {
963
+ width: "100%",
964
+ height: "100%",
965
+ objectFit: "cover",
966
+ display: "block",
967
+ pointerEvents: "none"
968
+ }
969
+ }
970
+ )
971
+ },
972
+ idx
973
+ );
974
+ })
923
975
  }
924
976
  )
925
977
  ]
@@ -934,7 +986,10 @@ function useImageSlider({
934
986
  onIndexChange,
935
987
  loop = true
936
988
  }) {
937
- const [internalIndex, setInternalIndex] = useState(defaultIndex);
989
+ const [internalIndex, setInternalIndex] = useState(() => {
990
+ const safeLength2 = Math.max(1, length);
991
+ return Math.max(0, Math.min(safeLength2 - 1, defaultIndex));
992
+ });
938
993
  const safeLength = Math.max(1, length);
939
994
  const rawIndex = controlledIndex ?? internalIndex;
940
995
  const index = Math.max(0, Math.min(safeLength - 1, rawIndex));