yet-another-react-lightbox 3.6.0 → 3.7.0

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/README.md CHANGED
@@ -123,16 +123,16 @@ See [examples](https://yet-another-react-lightbox.com/examples) on the documenta
123
123
 
124
124
  ## Plugins
125
125
 
126
- Yet Another React Lightbox allows you to add optional features based on your requirements via plugins.
126
+ Yet Another React Lightbox allows you to add optional features to your project based on your requirements via plugins.
127
127
 
128
- The following plugins come bundled in the package:
128
+ The following plugins are bundled in the package:
129
129
 
130
130
  - [Captions](https://yet-another-react-lightbox.com/plugins/captions) - adds support for slide title and
131
131
  description
132
132
  - [Counter](https://yet-another-react-lightbox.com/plugins/counter) - adds slides counter
133
133
  - [Download](https://yet-another-react-lightbox.com/plugins/download) - adds download button
134
134
  - [Fullscreen](https://yet-another-react-lightbox.com/plugins/fullscreen) - adds support for fullscreen mode
135
- - [Inline](https://yet-another-react-lightbox.com/plugins/inline) - adds support for inline rendering mode
135
+ - [Inline](https://yet-another-react-lightbox.com/plugins/inline) - transforms the lightbox into an image carousel
136
136
  - [Slideshow](https://yet-another-react-lightbox.com/plugins/slideshow) - adds slideshow autoplay feature
137
137
  - [Thumbnails](https://yet-another-react-lightbox.com/plugins/thumbnails) - adds thumbnails track
138
138
  - [Video](https://yet-another-react-lightbox.com/plugins/video) - adds support for video slides
package/dist/index.d.ts CHANGED
@@ -79,7 +79,7 @@ declare function useForkRef<InstanceA, InstanceB>(refA: React.Ref<InstanceA> | n
79
79
 
80
80
  declare const useLayoutEffect: typeof React.useEffect;
81
81
 
82
- declare function useLoseFocus(disabled?: boolean): {
82
+ declare function useLoseFocus(focus: () => void, disabled?: boolean): {
83
83
  onFocus: () => void;
84
84
  onBlur: () => void;
85
85
  };
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import * as React from 'react';
3
- import { ACTION_CLOSE, IMAGE_FIT_CONTAIN, MODULE_CONTROLLER, IMAGE_FIT_COVER, UNKNOWN_ACTION_TYPE, ELEMENT_BUTTON, ELEMENT_ICON, EVENT_ON_POINTER_DOWN, EVENT_ON_POINTER_MOVE, EVENT_ON_POINTER_UP, EVENT_ON_POINTER_LEAVE, EVENT_ON_POINTER_CANCEL, EVENT_ON_WHEEL, ACTION_PREV, ACTION_NEXT, ACTION_SWIPE, EVENT_ON_KEY_UP, VK_ESCAPE, CLASS_FLEX_CENTER, EVENT_ON_KEY_DOWN, SLIDE_STATUS_LOADING, activeSlideStatus, SLIDE_STATUS_COMPLETE, SLIDE_STATUS_ERROR, SLIDE_STATUS_PLACEHOLDER, MODULE_CAROUSEL, CLASS_FULLSIZE, VK_ARROW_LEFT, VK_ARROW_RIGHT, MODULE_NAVIGATION, CLASS_NO_SCROLL, CLASS_NO_SCROLL_PADDING, MODULE_NO_SCROLL, MODULE_PORTAL, MODULE_ROOT, MODULE_TOOLBAR } from './types.js';
3
+ import { ACTION_CLOSE, IMAGE_FIT_CONTAIN, MODULE_CONTROLLER, IMAGE_FIT_COVER, UNKNOWN_ACTION_TYPE, ELEMENT_BUTTON, ELEMENT_ICON, EVENT_ON_POINTER_DOWN, EVENT_ON_POINTER_MOVE, EVENT_ON_POINTER_UP, EVENT_ON_POINTER_LEAVE, EVENT_ON_POINTER_CANCEL, EVENT_ON_KEY_DOWN, EVENT_ON_KEY_UP, EVENT_ON_WHEEL, SLIDE_STATUS_LOADING, activeSlideStatus, SLIDE_STATUS_COMPLETE, SLIDE_STATUS_ERROR, SLIDE_STATUS_PLACEHOLDER, ACTION_PREV, ACTION_NEXT, ACTION_SWIPE, VK_ESCAPE, CLASS_FLEX_CENTER, MODULE_CAROUSEL, CLASS_FULLSIZE, VK_ARROW_LEFT, VK_ARROW_RIGHT, MODULE_NAVIGATION, CLASS_NO_SCROLL, CLASS_NO_SCROLL_PADDING, MODULE_NO_SCROLL, MODULE_PORTAL, MODULE_ROOT, MODULE_TOOLBAR } from './types.js';
4
4
  import { createPortal } from 'react-dom';
5
5
  export { ACTIVE_SLIDE_COMPLETE, ACTIVE_SLIDE_ERROR, ACTIVE_SLIDE_LOADING, ACTIVE_SLIDE_PLAYING, PLUGIN_CAPTIONS, PLUGIN_COUNTER, PLUGIN_DOWNLOAD, PLUGIN_FULLSCREEN, PLUGIN_INLINE, PLUGIN_SLIDESHOW, PLUGIN_THUMBNAILS, PLUGIN_ZOOM, SLIDE_STATUS_PLAYING } from './types.js';
6
6
 
@@ -34,6 +34,7 @@ const LightboxDefaultProps = {
34
34
  focus: true,
35
35
  aria: false,
36
36
  touchAction: "none",
37
+ closeOnPullDown: false,
37
38
  closeOnBackdropClick: false,
38
39
  },
39
40
  portal: {},
@@ -488,14 +489,176 @@ function useForkRef(refA, refB) {
488
489
  }, [refA, refB]);
489
490
  }
490
491
 
491
- function usePointerSwipe(subscribeSensors, isSwipeValid, containerWidth, swipeAnimationDuration, onSwipeStart, onSwipeProgress, onSwipeFinish, onSwipeCancel) {
492
+ function useLoseFocus(focus, disabled = false) {
493
+ const focused = React.useRef(disabled);
494
+ useLayoutEffect(() => {
495
+ if (disabled) {
496
+ focus();
497
+ }
498
+ }, [disabled, focus]);
499
+ const onFocus = React.useCallback(() => {
500
+ focused.current = true;
501
+ }, []);
502
+ const onBlur = React.useCallback(() => {
503
+ focused.current = false;
504
+ }, []);
505
+ return { onFocus, onBlur };
506
+ }
507
+
508
+ function useRTL() {
509
+ const [isRTL, setIsRTL] = React.useState(false);
510
+ useLayoutEffect(() => {
511
+ setIsRTL(window.getComputedStyle(window.document.documentElement).direction === "rtl");
512
+ }, []);
513
+ return isRTL;
514
+ }
515
+
516
+ function useSensors() {
517
+ const [subscribers] = React.useState({});
518
+ return React.useMemo(() => {
519
+ const notifySubscribers = (type, event) => {
520
+ var _a;
521
+ (_a = subscribers[type]) === null || _a === void 0 ? void 0 : _a.forEach((listener) => {
522
+ if (!event.isPropagationStopped())
523
+ listener(event);
524
+ });
525
+ };
526
+ return {
527
+ registerSensors: {
528
+ onPointerDown: (event) => notifySubscribers(EVENT_ON_POINTER_DOWN, event),
529
+ onPointerMove: (event) => notifySubscribers(EVENT_ON_POINTER_MOVE, event),
530
+ onPointerUp: (event) => notifySubscribers(EVENT_ON_POINTER_UP, event),
531
+ onPointerLeave: (event) => notifySubscribers(EVENT_ON_POINTER_LEAVE, event),
532
+ onPointerCancel: (event) => notifySubscribers(EVENT_ON_POINTER_CANCEL, event),
533
+ onKeyDown: (event) => notifySubscribers(EVENT_ON_KEY_DOWN, event),
534
+ onKeyUp: (event) => notifySubscribers(EVENT_ON_KEY_UP, event),
535
+ onWheel: (event) => notifySubscribers(EVENT_ON_WHEEL, event),
536
+ },
537
+ subscribeSensors: (type, callback) => {
538
+ if (!subscribers[type]) {
539
+ subscribers[type] = [];
540
+ }
541
+ subscribers[type].unshift(callback);
542
+ return () => {
543
+ const listeners = subscribers[type];
544
+ if (listeners) {
545
+ listeners.splice(0, listeners.length, ...listeners.filter((el) => el !== callback));
546
+ }
547
+ };
548
+ },
549
+ };
550
+ }, [subscribers]);
551
+ }
552
+
553
+ function useThrottle(callback, delay) {
554
+ const lastCallbackTime = React.useRef(0);
555
+ const delayCallback = useDelay();
556
+ const executeCallback = useEventCallback((...args) => {
557
+ lastCallbackTime.current = Date.now();
558
+ callback(args);
559
+ });
560
+ return React.useCallback((...args) => {
561
+ delayCallback(() => {
562
+ executeCallback(args);
563
+ }, delay - (Date.now() - lastCallbackTime.current));
564
+ }, [delay, executeCallback, delayCallback]);
565
+ }
566
+
567
+ const slidePrefix = makeComposePrefix("slide");
568
+ const slideImagePrefix = makeComposePrefix("slide_image");
569
+ function ImageSlide({ slide: image, offset, render, rect, imageFit, onClick, onLoad, style }) {
570
+ var _a, _b, _c, _d, _e, _f, _g;
571
+ const [status, setStatus] = React.useState(SLIDE_STATUS_LOADING);
572
+ const { publish } = useEvents();
573
+ const { setTimeout } = useTimeouts();
574
+ const imageRef = React.useRef(null);
575
+ React.useEffect(() => {
576
+ if (offset === 0) {
577
+ publish(activeSlideStatus(status));
578
+ }
579
+ }, [offset, status, publish]);
580
+ const handleLoading = useEventCallback((img) => {
581
+ ("decode" in img ? img.decode() : Promise.resolve())
582
+ .catch(() => { })
583
+ .then(() => {
584
+ if (!img.parentNode) {
585
+ return;
586
+ }
587
+ setStatus(SLIDE_STATUS_COMPLETE);
588
+ setTimeout(() => {
589
+ onLoad === null || onLoad === void 0 ? void 0 : onLoad(img);
590
+ }, 0);
591
+ });
592
+ });
593
+ const setImageRef = React.useCallback((img) => {
594
+ imageRef.current = img;
595
+ if (img === null || img === void 0 ? void 0 : img.complete) {
596
+ handleLoading(img);
597
+ }
598
+ }, [handleLoading]);
599
+ const handleOnLoad = React.useCallback((event) => {
600
+ handleLoading(event.currentTarget);
601
+ }, [handleLoading]);
602
+ const onError = React.useCallback(() => {
603
+ setStatus(SLIDE_STATUS_ERROR);
604
+ }, []);
605
+ const cover = isImageFitCover(image, imageFit);
606
+ const nonInfinite = (value, fallback) => (Number.isFinite(value) ? value : fallback);
607
+ const maxWidth = nonInfinite(Math.max(...((_b = (_a = image.srcSet) === null || _a === void 0 ? void 0 : _a.map((x) => x.width)) !== null && _b !== void 0 ? _b : []).concat(image.width ? [image.width] : [])), ((_c = imageRef.current) === null || _c === void 0 ? void 0 : _c.naturalWidth) || 0);
608
+ const maxHeight = nonInfinite(Math.max(...((_e = (_d = image.srcSet) === null || _d === void 0 ? void 0 : _d.map((x) => x.height)) !== null && _e !== void 0 ? _e : []).concat(image.height ? [image.height] : [])), ((_f = imageRef.current) === null || _f === void 0 ? void 0 : _f.naturalHeight) || 0);
609
+ const defaultStyle = maxWidth && maxHeight
610
+ ? {
611
+ maxWidth: `min(${maxWidth}px, 100%)`,
612
+ maxHeight: `min(${maxHeight}px, 100%)`,
613
+ }
614
+ : {
615
+ maxWidth: "100%",
616
+ maxHeight: "100%",
617
+ };
618
+ const srcSet = (_g = image.srcSet) === null || _g === void 0 ? void 0 : _g.sort((a, b) => a.width - b.width).map((item) => `${item.src} ${item.width}w`).join(", ");
619
+ const estimateActualWidth = () => rect && !cover && image.width && image.height ? (rect.height / image.height) * image.width : Number.MAX_VALUE;
620
+ const sizes = srcSet && rect && hasWindow() ? `${Math.round(Math.min(estimateActualWidth(), rect.width))}px` : undefined;
621
+ return (React.createElement(React.Fragment, null,
622
+ React.createElement("img", { ref: setImageRef, onLoad: handleOnLoad, onError: onError, onClick: onClick, className: clsx(cssClass(slideImagePrefix()), cover && cssClass(slideImagePrefix("cover")), status !== SLIDE_STATUS_COMPLETE && cssClass(slideImagePrefix("loading"))), draggable: false, alt: image.alt, style: { ...defaultStyle, ...style }, sizes: sizes, srcSet: srcSet, src: image.src }),
623
+ status !== SLIDE_STATUS_COMPLETE && (React.createElement("div", { className: cssClass(slidePrefix(SLIDE_STATUS_PLACEHOLDER)) },
624
+ status === SLIDE_STATUS_LOADING &&
625
+ ((render === null || render === void 0 ? void 0 : render.iconLoading) ? (render.iconLoading()) : (React.createElement(LoadingIcon, { className: clsx(cssClass(ELEMENT_ICON), cssClass(slidePrefix(SLIDE_STATUS_LOADING))) }))),
626
+ status === SLIDE_STATUS_ERROR &&
627
+ ((render === null || render === void 0 ? void 0 : render.iconError) ? (render.iconError()) : (React.createElement(ErrorIcon, { className: clsx(cssClass(ELEMENT_ICON), cssClass(slidePrefix(SLIDE_STATUS_ERROR))) })))))));
628
+ }
629
+
630
+ var SwipeState;
631
+ (function (SwipeState) {
632
+ SwipeState[SwipeState["NONE"] = 0] = "NONE";
633
+ SwipeState[SwipeState["SWIPE"] = 1] = "SWIPE";
634
+ SwipeState[SwipeState["ANIMATION"] = 2] = "ANIMATION";
635
+ })(SwipeState || (SwipeState = {}));
636
+
637
+ function usePointerEvents(subscribeSensors, onPointerDown, onPointerMove, onPointerUp, disabled) {
638
+ React.useEffect(() => {
639
+ if (disabled)
640
+ return () => { };
641
+ return cleanup(subscribeSensors(EVENT_ON_POINTER_DOWN, onPointerDown), subscribeSensors(EVENT_ON_POINTER_MOVE, onPointerMove), subscribeSensors(EVENT_ON_POINTER_UP, onPointerUp), subscribeSensors(EVENT_ON_POINTER_LEAVE, onPointerUp), subscribeSensors(EVENT_ON_POINTER_CANCEL, onPointerUp));
642
+ }, [subscribeSensors, onPointerDown, onPointerMove, onPointerUp, disabled]);
643
+ }
644
+
645
+ var Gesture;
646
+ (function (Gesture) {
647
+ Gesture[Gesture["NONE"] = 0] = "NONE";
648
+ Gesture[Gesture["SWIPE"] = 1] = "SWIPE";
649
+ Gesture[Gesture["PULL_DOWN"] = 2] = "PULL_DOWN";
650
+ })(Gesture || (Gesture = {}));
651
+ const SWIPE_THRESHOLD = 30;
652
+ function usePointerSwipe(subscribeSensors, isSwipeValid, containerWidth, swipeAnimationDuration, onSwipeStart, onSwipeProgress, onSwipeFinish, onSwipeCancel, onPullDownStart, onPullDownProgress, onPullDownFinish, onPullDownCancel) {
492
653
  const offset = React.useRef(0);
493
654
  const pointers = React.useRef([]);
494
655
  const activePointer = React.useRef();
495
656
  const startTime = React.useRef(0);
657
+ const gesture = React.useRef(Gesture.NONE);
496
658
  const clearPointer = React.useCallback((event) => {
497
659
  if (activePointer.current === event.pointerId) {
498
660
  activePointer.current = undefined;
661
+ gesture.current = Gesture.NONE;
499
662
  }
500
663
  const currentPointers = pointers.current;
501
664
  currentPointers.splice(0, currentPointers.length, ...currentPointers.filter((p) => p.pointerId !== event.pointerId));
@@ -513,14 +676,25 @@ function usePointerSwipe(subscribeSensors, isSwipeValid, containerWidth, swipeAn
513
676
  activePointer.current === event.pointerId) {
514
677
  const duration = Date.now() - startTime.current;
515
678
  const currentOffset = offset.current;
516
- if (Math.abs(currentOffset) > 0.3 * containerWidth ||
517
- (Math.abs(currentOffset) > 5 && duration < swipeAnimationDuration)) {
518
- onSwipeFinish(currentOffset, duration);
679
+ if (gesture.current === Gesture.SWIPE) {
680
+ if (Math.abs(currentOffset) > 0.3 * containerWidth ||
681
+ (Math.abs(currentOffset) > 5 && duration < swipeAnimationDuration)) {
682
+ onSwipeFinish(currentOffset, duration);
683
+ }
684
+ else {
685
+ onSwipeCancel(currentOffset);
686
+ }
519
687
  }
520
- else {
521
- onSwipeCancel(currentOffset);
688
+ else if (gesture.current === Gesture.PULL_DOWN) {
689
+ if (currentOffset > 2 * SWIPE_THRESHOLD) {
690
+ onPullDownFinish(currentOffset, duration);
691
+ }
692
+ else {
693
+ onPullDownCancel(currentOffset);
694
+ }
522
695
  }
523
696
  offset.current = 0;
697
+ gesture.current = Gesture.NONE;
524
698
  }
525
699
  clearPointer(event);
526
700
  });
@@ -539,22 +713,35 @@ function usePointerSwipe(subscribeSensors, isSwipeValid, containerWidth, swipeAn
539
713
  }
540
714
  const deltaX = event.clientX - pointer.clientX;
541
715
  const deltaY = event.clientY - pointer.clientY;
542
- if (activePointer.current === undefined &&
543
- isSwipeValid(deltaX) &&
544
- Math.abs(deltaX) > Math.abs(deltaY) &&
545
- Math.abs(deltaX) > 30) {
546
- addPointer(event);
547
- activePointer.current = event.pointerId;
548
- startTime.current = Date.now();
549
- onSwipeStart();
716
+ if (activePointer.current === undefined) {
717
+ const startGesture = (newGesture) => {
718
+ addPointer(event);
719
+ activePointer.current = event.pointerId;
720
+ startTime.current = Date.now();
721
+ gesture.current = newGesture;
722
+ };
723
+ if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > SWIPE_THRESHOLD && isSwipeValid(deltaX)) {
724
+ startGesture(Gesture.SWIPE);
725
+ onSwipeStart();
726
+ }
727
+ else if (Math.abs(deltaY) > Math.abs(deltaX) && Math.abs(deltaY) > SWIPE_THRESHOLD) {
728
+ startGesture(Gesture.PULL_DOWN);
729
+ onPullDownStart();
730
+ }
550
731
  }
551
732
  else if (isCurrentPointer) {
552
- offset.current = deltaX;
553
- onSwipeProgress(deltaX);
733
+ if (gesture.current === Gesture.SWIPE) {
734
+ offset.current = deltaX;
735
+ onSwipeProgress(deltaX);
736
+ }
737
+ else if (gesture.current === Gesture.PULL_DOWN) {
738
+ offset.current = deltaY;
739
+ onPullDownProgress(deltaY);
740
+ }
554
741
  }
555
742
  }
556
743
  });
557
- React.useEffect(() => cleanup(subscribeSensors(EVENT_ON_POINTER_DOWN, onPointerDown), subscribeSensors(EVENT_ON_POINTER_MOVE, onPointerMove), subscribeSensors(EVENT_ON_POINTER_UP, onPointerUp), subscribeSensors(EVENT_ON_POINTER_LEAVE, onPointerUp), subscribeSensors(EVENT_ON_POINTER_CANCEL, onPointerUp)), [subscribeSensors, onPointerDown, onPointerMove, onPointerUp]);
744
+ usePointerEvents(subscribeSensors, onPointerDown, onPointerMove, onPointerUp);
558
745
  }
559
746
 
560
747
  const WHEEL = "wheel";
@@ -665,13 +852,6 @@ function useWheelSwipe(swipeState, subscribeSensors, isSwipeValid, containerWidt
665
852
  React.useEffect(() => subscribeSensors(EVENT_ON_WHEEL, onWheel), [subscribeSensors, onWheel]);
666
853
  }
667
854
 
668
- var SwipeState;
669
- (function (SwipeState) {
670
- SwipeState[SwipeState["NONE"] = 0] = "NONE";
671
- SwipeState[SwipeState["SWIPE"] = 1] = "SWIPE";
672
- SwipeState[SwipeState["ANIMATION"] = 2] = "ANIMATION";
673
- })(SwipeState || (SwipeState = {}));
674
-
675
855
  const cssContainerPrefix = makeComposePrefix("container");
676
856
  const ControllerContext = React.createContext(null);
677
857
  const useController = makeUseContext("useController", "ControllerContext", ControllerContext);
@@ -814,7 +994,17 @@ function Controller({ children, ...props }) {
814
994
  (offset, duration) => swipe({ offset, duration, count: 1 }),
815
995
  (offset) => swipe({ offset, count: 0 }),
816
996
  ];
817
- usePointerSwipe(...swipeParams);
997
+ const pullDownParams = [
998
+ () => { },
999
+ () => { },
1000
+ () => {
1001
+ if (controller.closeOnPullDown) {
1002
+ close();
1003
+ }
1004
+ },
1005
+ () => { },
1006
+ ];
1007
+ usePointerSwipe(...swipeParams, ...pullDownParams);
818
1008
  useWheelSwipe(swipeState, ...swipeParams);
819
1009
  const focusOnMount = useEventCallback(() => {
820
1010
  var _a;
@@ -881,145 +1071,6 @@ function Controller({ children, ...props }) {
881
1071
  }
882
1072
  const ControllerModule = createModule(MODULE_CONTROLLER, Controller);
883
1073
 
884
- function useLoseFocus(disabled = false) {
885
- const focused = React.useRef(disabled);
886
- const { focus } = useController();
887
- useLayoutEffect(() => {
888
- if (disabled) {
889
- focus();
890
- }
891
- }, [disabled, focus]);
892
- const onFocus = React.useCallback(() => {
893
- focused.current = true;
894
- }, []);
895
- const onBlur = React.useCallback(() => {
896
- focused.current = false;
897
- }, []);
898
- return { onFocus, onBlur };
899
- }
900
-
901
- function useRTL() {
902
- const [isRTL, setIsRTL] = React.useState(false);
903
- useLayoutEffect(() => {
904
- setIsRTL(window.getComputedStyle(window.document.documentElement).direction === "rtl");
905
- }, []);
906
- return isRTL;
907
- }
908
-
909
- function useSensors() {
910
- const [subscribers] = React.useState({});
911
- return React.useMemo(() => {
912
- const notifySubscribers = (type, event) => {
913
- var _a;
914
- (_a = subscribers[type]) === null || _a === void 0 ? void 0 : _a.forEach((listener) => {
915
- if (!event.isPropagationStopped())
916
- listener(event);
917
- });
918
- };
919
- return {
920
- registerSensors: {
921
- onPointerDown: (event) => notifySubscribers(EVENT_ON_POINTER_DOWN, event),
922
- onPointerMove: (event) => notifySubscribers(EVENT_ON_POINTER_MOVE, event),
923
- onPointerUp: (event) => notifySubscribers(EVENT_ON_POINTER_UP, event),
924
- onPointerLeave: (event) => notifySubscribers(EVENT_ON_POINTER_LEAVE, event),
925
- onPointerCancel: (event) => notifySubscribers(EVENT_ON_POINTER_CANCEL, event),
926
- onKeyDown: (event) => notifySubscribers(EVENT_ON_KEY_DOWN, event),
927
- onKeyUp: (event) => notifySubscribers(EVENT_ON_KEY_UP, event),
928
- onWheel: (event) => notifySubscribers(EVENT_ON_WHEEL, event),
929
- },
930
- subscribeSensors: (type, callback) => {
931
- if (!subscribers[type]) {
932
- subscribers[type] = [];
933
- }
934
- subscribers[type].unshift(callback);
935
- return () => {
936
- const listeners = subscribers[type];
937
- if (listeners) {
938
- listeners.splice(0, listeners.length, ...listeners.filter((el) => el !== callback));
939
- }
940
- };
941
- },
942
- };
943
- }, [subscribers]);
944
- }
945
-
946
- function useThrottle(callback, delay) {
947
- const lastCallbackTime = React.useRef(0);
948
- const delayCallback = useDelay();
949
- const executeCallback = useEventCallback((...args) => {
950
- lastCallbackTime.current = Date.now();
951
- callback(args);
952
- });
953
- return React.useCallback((...args) => {
954
- delayCallback(() => {
955
- executeCallback(args);
956
- }, delay - (Date.now() - lastCallbackTime.current));
957
- }, [delay, executeCallback, delayCallback]);
958
- }
959
-
960
- const slidePrefix = makeComposePrefix("slide");
961
- const slideImagePrefix = makeComposePrefix("slide_image");
962
- function ImageSlide({ slide: image, offset, render, rect, imageFit, onClick, onLoad, style }) {
963
- var _a, _b, _c, _d, _e, _f, _g;
964
- const [status, setStatus] = React.useState(SLIDE_STATUS_LOADING);
965
- const { publish } = useEvents();
966
- const { setTimeout } = useTimeouts();
967
- const imageRef = React.useRef(null);
968
- React.useEffect(() => {
969
- if (offset === 0) {
970
- publish(activeSlideStatus(status));
971
- }
972
- }, [offset, status, publish]);
973
- const handleLoading = useEventCallback((img) => {
974
- ("decode" in img ? img.decode() : Promise.resolve())
975
- .catch(() => { })
976
- .then(() => {
977
- if (!img.parentNode) {
978
- return;
979
- }
980
- setStatus(SLIDE_STATUS_COMPLETE);
981
- setTimeout(() => {
982
- onLoad === null || onLoad === void 0 ? void 0 : onLoad(img);
983
- }, 0);
984
- });
985
- });
986
- const setImageRef = React.useCallback((img) => {
987
- imageRef.current = img;
988
- if (img === null || img === void 0 ? void 0 : img.complete) {
989
- handleLoading(img);
990
- }
991
- }, [handleLoading]);
992
- const handleOnLoad = React.useCallback((event) => {
993
- handleLoading(event.currentTarget);
994
- }, [handleLoading]);
995
- const onError = React.useCallback(() => {
996
- setStatus(SLIDE_STATUS_ERROR);
997
- }, []);
998
- const cover = isImageFitCover(image, imageFit);
999
- const nonInfinite = (value, fallback) => (Number.isFinite(value) ? value : fallback);
1000
- const maxWidth = nonInfinite(Math.max(...((_b = (_a = image.srcSet) === null || _a === void 0 ? void 0 : _a.map((x) => x.width)) !== null && _b !== void 0 ? _b : []).concat(image.width ? [image.width] : [])), ((_c = imageRef.current) === null || _c === void 0 ? void 0 : _c.naturalWidth) || 0);
1001
- const maxHeight = nonInfinite(Math.max(...((_e = (_d = image.srcSet) === null || _d === void 0 ? void 0 : _d.map((x) => x.height)) !== null && _e !== void 0 ? _e : []).concat(image.height ? [image.height] : [])), ((_f = imageRef.current) === null || _f === void 0 ? void 0 : _f.naturalHeight) || 0);
1002
- const defaultStyle = maxWidth && maxHeight
1003
- ? {
1004
- maxWidth: `min(${maxWidth}px, 100%)`,
1005
- maxHeight: `min(${maxHeight}px, 100%)`,
1006
- }
1007
- : {
1008
- maxWidth: "100%",
1009
- maxHeight: "100%",
1010
- };
1011
- const srcSet = (_g = image.srcSet) === null || _g === void 0 ? void 0 : _g.sort((a, b) => a.width - b.width).map((item) => `${item.src} ${item.width}w`).join(", ");
1012
- const estimateActualWidth = () => rect && !cover && image.width && image.height ? (rect.height / image.height) * image.width : Number.MAX_VALUE;
1013
- const sizes = srcSet && rect && hasWindow() ? `${Math.round(Math.min(estimateActualWidth(), rect.width))}px` : undefined;
1014
- return (React.createElement(React.Fragment, null,
1015
- React.createElement("img", { ref: setImageRef, onLoad: handleOnLoad, onError: onError, onClick: onClick, className: clsx(cssClass(slideImagePrefix()), cover && cssClass(slideImagePrefix("cover")), status !== SLIDE_STATUS_COMPLETE && cssClass(slideImagePrefix("loading"))), draggable: false, alt: image.alt, style: { ...defaultStyle, ...style }, sizes: sizes, srcSet: srcSet, src: image.src }),
1016
- status !== SLIDE_STATUS_COMPLETE && (React.createElement("div", { className: cssClass(slidePrefix(SLIDE_STATUS_PLACEHOLDER)) },
1017
- status === SLIDE_STATUS_LOADING &&
1018
- ((render === null || render === void 0 ? void 0 : render.iconLoading) ? (render.iconLoading()) : (React.createElement(LoadingIcon, { className: clsx(cssClass(ELEMENT_ICON), cssClass(slidePrefix(SLIDE_STATUS_LOADING))) }))),
1019
- status === SLIDE_STATUS_ERROR &&
1020
- ((render === null || render === void 0 ? void 0 : render.iconError) ? (render.iconError()) : (React.createElement(ErrorIcon, { className: clsx(cssClass(ELEMENT_ICON), cssClass(slidePrefix(SLIDE_STATUS_ERROR))) })))))));
1021
- }
1022
-
1023
1074
  function cssPrefix$2(value) {
1024
1075
  return composePrefix(MODULE_CAROUSEL, value);
1025
1076
  }
@@ -1087,7 +1138,7 @@ function Carousel({ carousel: { finite, preload, padding, spacing } }) {
1087
1138
  const CarouselModule = createModule(MODULE_CAROUSEL, Carousel);
1088
1139
 
1089
1140
  function NavigationButton({ label, icon, renderIcon, action, onClick, disabled }) {
1090
- return (React.createElement(IconButton, { label: label, icon: icon, renderIcon: renderIcon, className: cssClass(`navigation_${action}`), disabled: disabled, onClick: onClick, ...useLoseFocus(disabled) }));
1141
+ return (React.createElement(IconButton, { label: label, icon: icon, renderIcon: renderIcon, className: cssClass(`navigation_${action}`), disabled: disabled, onClick: onClick, ...useLoseFocus(useController().focus, disabled) }));
1091
1142
  }
1092
1143
  function Navigation({ carousel: { finite }, animation, render: { buttonPrev, buttonNext, iconPrev, iconNext }, }) {
1093
1144
  var _a;
@@ -1314,4 +1365,4 @@ function Lightbox({ carousel, animation, render, toolbar, controller, on, plugin
1314
1365
  React.createElement(EventsProvider, null, renderNode(createNode(RootModule, config), props))))));
1315
1366
  }
1316
1367
 
1317
- export { ACTION_CLOSE, ACTION_NEXT, ACTION_PREV, ACTION_SWIPE, CLASS_FLEX_CENTER, CLASS_FULLSIZE, CLASS_NO_SCROLL, CLASS_NO_SCROLL_PADDING, Carousel, CarouselModule, CloseIcon, Controller, ControllerContext, ControllerModule, ELEMENT_BUTTON, ELEMENT_ICON, EVENT_ON_KEY_DOWN, EVENT_ON_KEY_UP, EVENT_ON_POINTER_CANCEL, EVENT_ON_POINTER_DOWN, EVENT_ON_POINTER_LEAVE, EVENT_ON_POINTER_MOVE, EVENT_ON_POINTER_UP, EVENT_ON_WHEEL, ErrorIcon, EventsContext, EventsProvider, IMAGE_FIT_CONTAIN, IMAGE_FIT_COVER, IconButton, ImageSlide, Lightbox, LightboxDefaultProps, LightboxDispatchContext, LightboxPropsContext, LightboxPropsProvider, LightboxStateContext, LightboxStateProvider, LoadingIcon, MODULE_CAROUSEL, MODULE_CONTROLLER, MODULE_NAVIGATION, MODULE_NO_SCROLL, MODULE_PORTAL, MODULE_ROOT, MODULE_TOOLBAR, Navigation, NavigationButton, NavigationModule, NextIcon, NoScroll, NoScrollModule, Portal, PortalModule, PreviousIcon, Root, RootModule, SLIDE_STATUS_COMPLETE, SLIDE_STATUS_ERROR, SLIDE_STATUS_LOADING, SLIDE_STATUS_PLACEHOLDER, TimeoutsContext, TimeoutsProvider, Toolbar, ToolbarModule, UNKNOWN_ACTION_TYPE, VK_ARROW_LEFT, VK_ARROW_RIGHT, VK_ESCAPE, activeSlideStatus, addToolbarButton, cleanup, clsx, composePrefix, computeSlideRect, createIcon, createIconDisabled, createModule, createNode, cssClass, cssVar, Lightbox as default, devicePixelRatio, getSlide, getSlideIfPresent, getSlideIndex, hasSlides, hasWindow, isImageFitCover, isImageSlide, label, makeComposePrefix, makeUseContext, parseLengthPercentage, round, setRef, stopNavigationEventsPropagation, useAnimation, useContainerRect, useController, useDelay, useEventCallback, useEvents, useForkRef, useLayoutEffect, useLightboxDispatch, useLightboxProps, useLightboxState, useLoseFocus, useMotionPreference, useRTL, useSensors, useThrottle, useTimeouts, withPlugins };
1368
+ export { ACTION_CLOSE, ACTION_NEXT, ACTION_PREV, ACTION_SWIPE, CLASS_FLEX_CENTER, CLASS_FULLSIZE, CLASS_NO_SCROLL, CLASS_NO_SCROLL_PADDING, Carousel, CarouselModule, CloseIcon, Controller, ControllerContext, ControllerModule, ELEMENT_BUTTON, ELEMENT_ICON, EVENT_ON_KEY_DOWN, EVENT_ON_KEY_UP, EVENT_ON_POINTER_CANCEL, EVENT_ON_POINTER_DOWN, EVENT_ON_POINTER_LEAVE, EVENT_ON_POINTER_MOVE, EVENT_ON_POINTER_UP, EVENT_ON_WHEEL, ErrorIcon, EventsContext, EventsProvider, IMAGE_FIT_CONTAIN, IMAGE_FIT_COVER, IconButton, ImageSlide, Lightbox, LightboxDefaultProps, LightboxDispatchContext, LightboxPropsContext, LightboxPropsProvider, LightboxStateContext, LightboxStateProvider, LoadingIcon, MODULE_CAROUSEL, MODULE_CONTROLLER, MODULE_NAVIGATION, MODULE_NO_SCROLL, MODULE_PORTAL, MODULE_ROOT, MODULE_TOOLBAR, Navigation, NavigationButton, NavigationModule, NextIcon, NoScroll, NoScrollModule, Portal, PortalModule, PreviousIcon, Root, RootModule, SLIDE_STATUS_COMPLETE, SLIDE_STATUS_ERROR, SLIDE_STATUS_LOADING, SLIDE_STATUS_PLACEHOLDER, TimeoutsContext, TimeoutsProvider, Toolbar, ToolbarModule, UNKNOWN_ACTION_TYPE, VK_ARROW_LEFT, VK_ARROW_RIGHT, VK_ESCAPE, activeSlideStatus, addToolbarButton, cleanup, clsx, composePrefix, computeSlideRect, createIcon, createIconDisabled, createModule, createNode, cssClass, cssVar, Lightbox as default, devicePixelRatio, getSlide, getSlideIfPresent, getSlideIndex, hasSlides, hasWindow, isImageFitCover, isImageSlide, label, makeComposePrefix, makeUseContext, parseLengthPercentage, round, setRef, stopNavigationEventsPropagation, useAnimation, useContainerRect, useController, useDelay, useEventCallback, useEvents, useForkRef, useLayoutEffect, useLightboxDispatch, useLightboxProps, useLightboxState, useLoseFocus, useMotionPreference, usePointerEvents, useRTL, useSensors, useThrottle, useTimeouts, withPlugins };
@@ -94,7 +94,7 @@ const PauseIcon = createIcon("Pause", React.createElement("path", { d: "M6 19h4V
94
94
  function SlideshowButton() {
95
95
  const { playing, disabled, play, pause } = useSlideshow();
96
96
  const { render } = useLightboxProps();
97
- const focusListeners = useLoseFocus(disabled);
97
+ const focusListeners = useLoseFocus(useController().focus, disabled);
98
98
  if (render.buttonSlideshow) {
99
99
  return React.createElement(React.Fragment, null, render.buttonSlideshow({ playing, disabled, play, pause }));
100
100
  }
@@ -80,9 +80,8 @@ function isHorizontal(position) {
80
80
  function boxSize(thumbnails, dimension, includeGap) {
81
81
  return dimension + 2 * (thumbnails.border + thumbnails.padding) + (includeGap ? thumbnails.gap : 0);
82
82
  }
83
- function ThumbnailsTrack({ containerRef }) {
83
+ function ThumbnailsTrack({ visible, containerRef }) {
84
84
  const track = React.useRef(null);
85
- const { visible } = useThumbnails();
86
85
  const { carousel, styles } = useLightboxProps();
87
86
  const { slides, globalIndex, animation } = useLightboxState();
88
87
  const { publish, subscribe } = useEvents();
@@ -222,9 +221,9 @@ function ThumbnailsContextProvider({ children, ...props }) {
222
221
  return (React.createElement(LightboxPropsProvider, { ...props },
223
222
  React.createElement(ThumbnailsContext.Provider, { value: context },
224
223
  React.createElement("div", { ref: containerRef, className: clsx(cssClass(cssPrefix()), cssClass(cssPrefix(`${position}`))) },
225
- ["start", "top"].includes(position) && React.createElement(ThumbnailsTrack, { containerRef: containerRef }),
224
+ ["start", "top"].includes(position) && (React.createElement(ThumbnailsTrack, { containerRef: containerRef, visible: visible })),
226
225
  React.createElement("div", { className: cssClass(cssPrefix("wrapper")) }, children),
227
- ["end", "bottom"].includes(position) && React.createElement(ThumbnailsTrack, { containerRef: containerRef })))));
226
+ ["end", "bottom"].includes(position) && (React.createElement(ThumbnailsTrack, { containerRef: containerRef, visible: visible }))))));
228
227
  }
229
228
 
230
229
  const thumbnailsIcon = () => (React.createElement(React.Fragment, null,
@@ -1,6 +1,6 @@
1
- import { useLightboxProps, useMotionPreference, useEventCallback, useLayoutEffect, useLightboxState, isImageSlide, isImageFitCover, round, useController, cleanup, makeUseContext, createIcon, IconButton, devicePixelRatio, ImageSlide, clsx, cssClass, addToolbarButton, createModule } from '../../index.js';
1
+ import { useLightboxProps, useMotionPreference, useEventCallback, useLayoutEffect, useLightboxState, isImageSlide, isImageFitCover, round, useController, usePointerEvents, cleanup, makeUseContext, createIcon, IconButton, devicePixelRatio, ImageSlide, clsx, cssClass, addToolbarButton, createModule } from '../../index.js';
2
2
  import * as React from 'react';
3
- import { EVENT_ON_KEY_DOWN, EVENT_ON_WHEEL, EVENT_ON_POINTER_DOWN, EVENT_ON_POINTER_MOVE, EVENT_ON_POINTER_UP, EVENT_ON_POINTER_LEAVE, EVENT_ON_POINTER_CANCEL, UNKNOWN_ACTION_TYPE, CLASS_FULLSIZE, CLASS_FLEX_CENTER, PLUGIN_ZOOM } from '../../types.js';
3
+ import { EVENT_ON_KEY_DOWN, EVENT_ON_WHEEL, UNKNOWN_ACTION_TYPE, CLASS_FULLSIZE, CLASS_FLEX_CENTER, PLUGIN_ZOOM } from '../../types.js';
4
4
 
5
5
  const defaultZoomProps = {
6
6
  maxZoomPixelRatio: 1,
@@ -255,13 +255,14 @@ function useZoomSensors(zoom, maxZoom, disabled, changeZoom, changeOffsets, zoom
255
255
  lastPointerDown.current = 0;
256
256
  pinchZoomDistance.current = undefined;
257
257
  }, []);
258
+ usePointerEvents(subscribeSensors, onPointerDown, onPointerMove, onPointerUp, disabled);
258
259
  React.useEffect(cleanupSensors, [globalIndex, cleanupSensors]);
259
260
  React.useEffect(() => {
260
261
  if (!disabled) {
261
- return cleanup(cleanupSensors, subscribeSensors(EVENT_ON_KEY_DOWN, onKeyDown), subscribeSensors(EVENT_ON_WHEEL, onWheel), subscribeSensors(EVENT_ON_POINTER_DOWN, onPointerDown), subscribeSensors(EVENT_ON_POINTER_MOVE, onPointerMove), subscribeSensors(EVENT_ON_POINTER_UP, onPointerUp), subscribeSensors(EVENT_ON_POINTER_LEAVE, onPointerUp), subscribeSensors(EVENT_ON_POINTER_CANCEL, onPointerUp));
262
+ return cleanup(cleanupSensors, subscribeSensors(EVENT_ON_KEY_DOWN, onKeyDown), subscribeSensors(EVENT_ON_WHEEL, onWheel));
262
263
  }
263
264
  return () => { };
264
- }, [disabled, subscribeSensors, cleanupSensors, onKeyDown, onWheel, onPointerDown, onPointerMove, onPointerUp]);
265
+ }, [disabled, subscribeSensors, cleanupSensors, onKeyDown, onWheel]);
265
266
  }
266
267
 
267
268
  function getCurrentSource(slides, currentIndex) {
package/dist/types.d.ts CHANGED
@@ -213,6 +213,8 @@ interface ControllerSettings {
213
213
  touchAction: "none" | "pan-y";
214
214
  /** if `true`, set ARIA attributes on the controller div */
215
215
  aria: boolean;
216
+ /** if `true`, close the lightbox on pull-down gesture */
217
+ closeOnPullDown: boolean;
216
218
  /** if `true`, close the lightbox when the backdrop is clicked */
217
219
  closeOnBackdropClick: boolean;
218
220
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yet-another-react-lightbox",
3
- "version": "3.6.0",
3
+ "version": "3.7.0",
4
4
  "description": "Modern React lightbox component",
5
5
  "author": "Igor Danchenko",
6
6
  "license": "MIT",