yet-another-react-lightbox 2.3.1 → 2.4.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.
Files changed (47) hide show
  1. package/dist/core/components/IconButton.d.ts +1 -1
  2. package/dist/core/components/ImageSlide.js +5 -2
  3. package/dist/core/consts.d.ts +7 -5
  4. package/dist/core/consts.js +2 -0
  5. package/dist/core/contexts/Events.d.ts +8 -4
  6. package/dist/core/contexts/Events.js +2 -3
  7. package/dist/core/contexts/LightboxState.d.ts +6 -4
  8. package/dist/core/contexts/LightboxState.js +13 -10
  9. package/dist/core/hooks/index.d.ts +4 -0
  10. package/dist/core/hooks/index.js +4 -0
  11. package/dist/core/hooks/useAnimation.d.ts +10 -0
  12. package/dist/core/hooks/useAnimation.js +48 -0
  13. package/dist/core/hooks/useDelay.d.ts +1 -0
  14. package/dist/core/hooks/useDelay.js +10 -0
  15. package/dist/core/hooks/useLoseFocus.d.ts +4 -0
  16. package/dist/core/hooks/useLoseFocus.js +19 -0
  17. package/dist/core/hooks/useThrottle.d.ts +1 -0
  18. package/dist/core/hooks/useThrottle.js +16 -0
  19. package/dist/core/modules/Carousel.js +3 -4
  20. package/dist/core/modules/Controller.d.ts +11 -0
  21. package/dist/core/modules/Controller.js +46 -59
  22. package/dist/core/modules/Navigation.js +13 -12
  23. package/dist/core/modules/Toolbar.d.ts +6 -0
  24. package/dist/core/modules/Toolbar.js +2 -2
  25. package/dist/core/utils.d.ts +2 -0
  26. package/dist/core/utils.js +2 -0
  27. package/dist/plugins/captions/CaptionsContext.js +2 -6
  28. package/dist/plugins/fullscreen/Fullscreen.js +1 -7
  29. package/dist/plugins/fullscreen/FullscreenButton.d.ts +1 -6
  30. package/dist/plugins/fullscreen/FullscreenButton.js +6 -99
  31. package/dist/plugins/fullscreen/FullscreenContext.d.ts +7 -3
  32. package/dist/plugins/fullscreen/FullscreenContext.js +97 -3
  33. package/dist/plugins/fullscreen/index.d.ts +2 -1
  34. package/dist/plugins/slideshow/Slideshow.js +7 -11
  35. package/dist/plugins/slideshow/SlideshowButton.js +6 -55
  36. package/dist/plugins/slideshow/SlideshowContext.d.ts +9 -0
  37. package/dist/plugins/slideshow/SlideshowContext.js +58 -0
  38. package/dist/plugins/thumbnails/ThumbnailsContainer.js +2 -2
  39. package/dist/plugins/thumbnails/ThumbnailsTrack.d.ts +0 -1
  40. package/dist/plugins/thumbnails/ThumbnailsTrack.js +28 -46
  41. package/dist/plugins/video/Video.js +2 -5
  42. package/dist/plugins/video/index.d.ts +9 -0
  43. package/dist/plugins/video/index.js +1 -0
  44. package/dist/plugins/zoom/ZoomContainer.js +1 -1
  45. package/dist/plugins/zoom/ZoomContext.js +1 -1
  46. package/dist/plugins/zoom/index.d.ts +6 -0
  47. package/package.json +1 -1
@@ -4,4 +4,4 @@ export type IconButtonProps = Omit<React.DetailedHTMLProps<React.ButtonHTMLAttri
4
4
  icon: React.ElementType;
5
5
  renderIcon?: () => React.ReactNode;
6
6
  };
7
- export declare const IconButton: React.ForwardRefExoticComponent<Pick<IconButtonProps, "icon" | "hidden" | "color" | "style" | "translate" | "disabled" | "className" | "placeholder" | "onPointerDown" | "onPointerMove" | "onPointerUp" | "onPointerLeave" | "onPointerCancel" | "onKeyDown" | "onKeyUp" | "onWheel" | "value" | "children" | "key" | "defaultChecked" | "defaultValue" | "suppressContentEditableWarning" | "suppressHydrationWarning" | "accessKey" | "contentEditable" | "contextMenu" | "dir" | "draggable" | "id" | "lang" | "nonce" | "slot" | "spellCheck" | "tabIndex" | "title" | "radioGroup" | "role" | "about" | "datatype" | "inlist" | "prefix" | "property" | "resource" | "typeof" | "vocab" | "autoCapitalize" | "autoCorrect" | "autoSave" | "itemProp" | "itemScope" | "itemType" | "itemID" | "itemRef" | "results" | "security" | "unselectable" | "inputMode" | "is" | "aria-activedescendant" | "aria-atomic" | "aria-autocomplete" | "aria-busy" | "aria-checked" | "aria-colcount" | "aria-colindex" | "aria-colspan" | "aria-controls" | "aria-current" | "aria-describedby" | "aria-details" | "aria-disabled" | "aria-dropeffect" | "aria-errormessage" | "aria-expanded" | "aria-flowto" | "aria-grabbed" | "aria-haspopup" | "aria-hidden" | "aria-invalid" | "aria-keyshortcuts" | "aria-labelledby" | "aria-level" | "aria-live" | "aria-modal" | "aria-multiline" | "aria-multiselectable" | "aria-orientation" | "aria-owns" | "aria-placeholder" | "aria-posinset" | "aria-pressed" | "aria-readonly" | "aria-relevant" | "aria-required" | "aria-roledescription" | "aria-rowcount" | "aria-rowindex" | "aria-rowspan" | "aria-selected" | "aria-setsize" | "aria-sort" | "aria-valuemax" | "aria-valuemin" | "aria-valuenow" | "aria-valuetext" | "dangerouslySetInnerHTML" | "onCopy" | "onCopyCapture" | "onCut" | "onCutCapture" | "onPaste" | "onPasteCapture" | "onCompositionEnd" | "onCompositionEndCapture" | "onCompositionStart" | "onCompositionStartCapture" | "onCompositionUpdate" | "onCompositionUpdateCapture" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onChangeCapture" | "onBeforeInput" | "onBeforeInputCapture" | "onInput" | "onInputCapture" | "onReset" | "onResetCapture" | "onSubmit" | "onSubmitCapture" | "onInvalid" | "onInvalidCapture" | "onLoad" | "onLoadCapture" | "onError" | "onErrorCapture" | "onKeyDownCapture" | "onKeyPress" | "onKeyPressCapture" | "onKeyUpCapture" | "onAbort" | "onAbortCapture" | "onCanPlay" | "onCanPlayCapture" | "onCanPlayThrough" | "onCanPlayThroughCapture" | "onDurationChange" | "onDurationChangeCapture" | "onEmptied" | "onEmptiedCapture" | "onEncrypted" | "onEncryptedCapture" | "onEnded" | "onEndedCapture" | "onLoadedData" | "onLoadedDataCapture" | "onLoadedMetadata" | "onLoadedMetadataCapture" | "onLoadStart" | "onLoadStartCapture" | "onPause" | "onPauseCapture" | "onPlay" | "onPlayCapture" | "onPlaying" | "onPlayingCapture" | "onProgress" | "onProgressCapture" | "onRateChange" | "onRateChangeCapture" | "onResize" | "onResizeCapture" | "onSeeked" | "onSeekedCapture" | "onSeeking" | "onSeekingCapture" | "onStalled" | "onStalledCapture" | "onSuspend" | "onSuspendCapture" | "onTimeUpdate" | "onTimeUpdateCapture" | "onVolumeChange" | "onVolumeChangeCapture" | "onWaiting" | "onWaitingCapture" | "onAuxClick" | "onAuxClickCapture" | "onClick" | "onClickCapture" | "onContextMenu" | "onContextMenuCapture" | "onDoubleClick" | "onDoubleClickCapture" | "onDrag" | "onDragCapture" | "onDragEnd" | "onDragEndCapture" | "onDragEnter" | "onDragEnterCapture" | "onDragExit" | "onDragExitCapture" | "onDragLeave" | "onDragLeaveCapture" | "onDragOver" | "onDragOverCapture" | "onDragStart" | "onDragStartCapture" | "onDrop" | "onDropCapture" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseMoveCapture" | "onMouseOut" | "onMouseOutCapture" | "onMouseOver" | "onMouseOverCapture" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onSelectCapture" | "onTouchCancel" | "onTouchCancelCapture" | "onTouchEnd" | "onTouchEndCapture" | "onTouchMove" | "onTouchMoveCapture" | "onTouchStart" | "onTouchStartCapture" | "onPointerDownCapture" | "onPointerMoveCapture" | "onPointerUpCapture" | "onPointerCancelCapture" | "onPointerEnter" | "onPointerEnterCapture" | "onPointerLeaveCapture" | "onPointerOver" | "onPointerOverCapture" | "onPointerOut" | "onPointerOutCapture" | "onGotPointerCapture" | "onGotPointerCaptureCapture" | "onLostPointerCapture" | "onLostPointerCaptureCapture" | "onScroll" | "onScrollCapture" | "onWheelCapture" | "onAnimationStart" | "onAnimationStartCapture" | "onAnimationEnd" | "onAnimationEndCapture" | "onAnimationIteration" | "onAnimationIterationCapture" | "onTransitionEnd" | "onTransitionEndCapture" | "form" | "label" | "autoFocus" | "formAction" | "formEncType" | "formMethod" | "formNoValidate" | "formTarget" | "name" | "renderIcon"> & React.RefAttributes<HTMLButtonElement>>;
7
+ export declare const IconButton: React.ForwardRefExoticComponent<Omit<IconButtonProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
@@ -1,7 +1,7 @@
1
1
  import * as React from "react";
2
2
  import { clsx, cssClass, hasWindow, makeComposePrefix } from "../utils.js";
3
3
  import { useEventCallback } from "../hooks/index.js";
4
- import { useEvents } from "../contexts/index.js";
4
+ import { useEvents, useTimeouts } from "../contexts/index.js";
5
5
  import { ErrorIcon, LoadingIcon } from "./Icons.js";
6
6
  import { activeSlideStatus, ELEMENT_ICON, IMAGE_FIT_CONTAIN, IMAGE_FIT_COVER, SLIDE_STATUS_COMPLETE, SLIDE_STATUS_ERROR, SLIDE_STATUS_LOADING, SLIDE_STATUS_PLACEHOLDER, } from "../consts.js";
7
7
  const slidePrefix = makeComposePrefix("slide");
@@ -10,6 +10,7 @@ export const ImageSlide = ({ slide: image, offset, render, rect, imageFit, onCli
10
10
  var _a, _b, _c, _d, _e, _f, _g;
11
11
  const [status, setStatus] = React.useState(SLIDE_STATUS_LOADING);
12
12
  const { publish } = useEvents();
13
+ const { setTimeout } = useTimeouts();
13
14
  const imageRef = React.useRef(null);
14
15
  React.useEffect(() => {
15
16
  if (offset === 0) {
@@ -27,7 +28,9 @@ export const ImageSlide = ({ slide: image, offset, render, rect, imageFit, onCli
27
28
  return;
28
29
  }
29
30
  setStatus(SLIDE_STATUS_COMPLETE);
30
- onLoad === null || onLoad === void 0 ? void 0 : onLoad(img);
31
+ setTimeout(() => {
32
+ onLoad === null || onLoad === void 0 ? void 0 : onLoad(img);
33
+ }, 0);
31
34
  });
32
35
  });
33
36
  const setImageRef = React.useCallback((img) => {
@@ -17,18 +17,20 @@ export declare const SLIDE_STATUS_ERROR = "error";
17
17
  export declare const SLIDE_STATUS_COMPLETE = "complete";
18
18
  export declare const SLIDE_STATUS_PLACEHOLDER = "placeholder";
19
19
  export type SlideStatus = typeof SLIDE_STATUS_LOADING | typeof SLIDE_STATUS_PLAYING | typeof SLIDE_STATUS_ERROR | typeof SLIDE_STATUS_COMPLETE;
20
- export declare const activeSlideStatus: (status: SlideStatus) => string;
21
- export declare const ACTIVE_SLIDE_LOADING: string;
22
- export declare const ACTIVE_SLIDE_PLAYING: string;
23
- export declare const ACTIVE_SLIDE_ERROR: string;
24
- export declare const ACTIVE_SLIDE_COMPLETE: string;
20
+ export declare const activeSlideStatus: (status: SlideStatus) => `active-slide-${SlideStatus}`;
21
+ export declare const ACTIVE_SLIDE_LOADING: "active-slide-loading";
22
+ export declare const ACTIVE_SLIDE_PLAYING: "active-slide-playing";
23
+ export declare const ACTIVE_SLIDE_ERROR: "active-slide-error";
24
+ export declare const ACTIVE_SLIDE_COMPLETE: "active-slide-complete";
25
25
  export declare const YARL_EVENT_BACKDROP_CLICK = "backdrop_click";
26
+ export declare const YARL_EVENT_TOOLBAR_WIDTH = "toolbar-width";
26
27
  export declare const CLASS_FULLSIZE = "fullsize";
27
28
  export declare const CLASS_FLEX_CENTER = "flex_center";
28
29
  export declare const CLASS_NO_SCROLL = "no_scroll";
29
30
  export declare const CLASS_NO_SCROLL_PADDING = "no_scroll_padding";
30
31
  export declare const ACTION_PREV = "prev";
31
32
  export declare const ACTION_NEXT = "next";
33
+ export declare const ACTION_SWIPE = "swipe";
32
34
  export declare const ACTION_CLOSE = "close";
33
35
  export declare const EVENT_ON_POINTER_DOWN = "onPointerDown";
34
36
  export declare const EVENT_ON_POINTER_MOVE = "onPointerMove";
@@ -22,12 +22,14 @@ export const ACTIVE_SLIDE_PLAYING = activeSlideStatus(SLIDE_STATUS_PLAYING);
22
22
  export const ACTIVE_SLIDE_ERROR = activeSlideStatus(SLIDE_STATUS_ERROR);
23
23
  export const ACTIVE_SLIDE_COMPLETE = activeSlideStatus(SLIDE_STATUS_COMPLETE);
24
24
  export const YARL_EVENT_BACKDROP_CLICK = "backdrop_click";
25
+ export const YARL_EVENT_TOOLBAR_WIDTH = "toolbar-width";
25
26
  export const CLASS_FULLSIZE = "fullsize";
26
27
  export const CLASS_FLEX_CENTER = "flex_center";
27
28
  export const CLASS_NO_SCROLL = "no_scroll";
28
29
  export const CLASS_NO_SCROLL_PADDING = "no_scroll_padding";
29
30
  export const ACTION_PREV = "prev";
30
31
  export const ACTION_NEXT = "next";
32
+ export const ACTION_SWIPE = "swipe";
31
33
  export const ACTION_CLOSE = "close";
32
34
  export const EVENT_ON_POINTER_DOWN = "onPointerDown";
33
35
  export const EVENT_ON_POINTER_MOVE = "onPointerMove";
@@ -1,8 +1,12 @@
1
1
  import * as React from "react";
2
- export type Callback = (event?: unknown) => void;
3
- export type Subscribe = (topic: string, callback: Callback) => () => void;
4
- export type Unsubscribe = (topic: string, callback: Callback) => void;
5
- export type Publish = (topic: string, event?: unknown) => void;
2
+ export interface EventTypes {
3
+ }
4
+ export type Topic = keyof EventTypes;
5
+ export type Event<T extends Topic> = EventTypes[T];
6
+ export type Callback<T extends Topic> = (event?: Event<T>) => void;
7
+ export type Subscribe = <T extends Topic>(topic: T, callback: Callback<T>) => () => void;
8
+ export type Unsubscribe = <T extends Topic>(topic: T, callback: Callback<T>) => void;
9
+ export type Publish = <T extends Topic>(topic: T, event?: Event<T>) => void;
6
10
  export type EventsContextType = {
7
11
  subscribe: Subscribe;
8
12
  unsubscribe: Unsubscribe;
@@ -9,9 +9,8 @@ export const EventsProvider = ({ children }) => {
9
9
  }, [subscriptions]);
10
10
  const context = React.useMemo(() => {
11
11
  const unsubscribe = (topic, callback) => {
12
- if (subscriptions[topic]) {
13
- subscriptions[topic] = subscriptions[topic].filter((cb) => cb !== callback);
14
- }
12
+ var _a;
13
+ (_a = subscriptions[topic]) === null || _a === void 0 ? void 0 : _a.splice(0, subscriptions[topic].length, ...subscriptions[topic].filter((cb) => cb !== callback));
15
14
  };
16
15
  const subscribe = (topic, callback) => {
17
16
  if (!subscriptions[topic]) {
@@ -2,12 +2,14 @@ import * as React from "react";
2
2
  export type LightboxState = {
3
3
  currentIndex: number;
4
4
  globalIndex: number;
5
+ /** @deprecated use `animation.duration` */
5
6
  animationDuration: number;
7
+ animation?: LightboxStateAction;
6
8
  };
7
- type LightboxStateAction = {
8
- increment?: number;
9
- animationDuration: number;
10
- };
9
+ export type LightboxStateAction = {
10
+ increment: number;
11
+ duration?: number;
12
+ } | undefined;
11
13
  export declare const useLightboxState: () => {
12
14
  state: LightboxState;
13
15
  dispatch: React.Dispatch<LightboxStateAction>;
@@ -1,19 +1,22 @@
1
1
  import * as React from "react";
2
- import { makeUseContext } from "../utils.js";
2
+ import { getSlideIndex, makeUseContext } from "../utils.js";
3
3
  const LightboxStateContext = React.createContext(null);
4
4
  export const useLightboxState = makeUseContext("useLightboxState", "LightboxStateContext", LightboxStateContext);
5
5
  const reducer = (slidesCount) => (state, action) => {
6
- var _a;
7
- return action.increment !== undefined || action.animationDuration !== undefined
8
- ? {
9
- currentIndex: (((state.currentIndex + (action.increment || 0)) % slidesCount) + slidesCount) % slidesCount,
10
- globalIndex: state.globalIndex + (action.increment || 0),
11
- animationDuration: (_a = action.animationDuration) !== null && _a !== void 0 ? _a : state.animationDuration,
12
- }
13
- : state;
6
+ const increment = (action === null || action === void 0 ? void 0 : action.increment) || 0;
7
+ const globalIndex = state.globalIndex + increment;
8
+ const currentIndex = getSlideIndex(globalIndex, slidesCount);
9
+ const animationDuration = (action === null || action === void 0 ? void 0 : action.duration) || 0;
10
+ return {
11
+ globalIndex,
12
+ currentIndex,
13
+ animation: action,
14
+ animationDuration,
15
+ };
14
16
  };
15
17
  export const LightboxStateProvider = ({ initialIndex, slidesCount, children, }) => {
16
- const [state, dispatch] = React.useReducer(reducer(slidesCount), {
18
+ const memoizedReducer = React.useMemo(() => reducer(slidesCount), [slidesCount]);
19
+ const [state, dispatch] = React.useReducer(memoizedReducer, {
17
20
  currentIndex: initialIndex,
18
21
  globalIndex: initialIndex,
19
22
  animationDuration: 0,
@@ -1,7 +1,11 @@
1
+ export * from "./useAnimation.js";
1
2
  export * from "./useContainerRect.js";
3
+ export * from "./useDelay.js";
2
4
  export * from "./useEventCallback.js";
3
5
  export * from "./useForkRef.js";
4
6
  export * from "./useLayoutEffect.js";
7
+ export * from "./useLoseFocus.js";
5
8
  export * from "./useMotionPreference.js";
6
9
  export * from "./useRTL.js";
7
10
  export * from "./useSensors.js";
11
+ export * from "./useThrottle.js";
@@ -1,7 +1,11 @@
1
+ export * from "./useAnimation.js";
1
2
  export * from "./useContainerRect.js";
3
+ export * from "./useDelay.js";
2
4
  export * from "./useEventCallback.js";
3
5
  export * from "./useForkRef.js";
4
6
  export * from "./useLayoutEffect.js";
7
+ export * from "./useLoseFocus.js";
5
8
  export * from "./useMotionPreference.js";
6
9
  export * from "./useRTL.js";
7
10
  export * from "./useSensors.js";
11
+ export * from "./useThrottle.js";
@@ -0,0 +1,10 @@
1
+ import * as React from "react";
2
+ export declare const useAnimation: <T>(nodeRef: React.RefObject<HTMLElement | null>, computeAnimation: (snapshot: T, rect: DOMRect, translate: {
3
+ x: number;
4
+ y: number;
5
+ z: number;
6
+ }) => {
7
+ keyframes: Keyframe[];
8
+ duration: number;
9
+ onfinish?: () => void;
10
+ } | undefined) => (currentSnapshot: T | undefined) => void;
@@ -0,0 +1,48 @@
1
+ import * as React from "react";
2
+ import { useLayoutEffect } from "./useLayoutEffect.js";
3
+ import { useMotionPreference } from "./useMotionPreference.js";
4
+ const currentTransformation = (node) => {
5
+ let x = 0;
6
+ let y = 0;
7
+ let z = 0;
8
+ const matrix = window.getComputedStyle(node).transform;
9
+ const matcher = matrix.match(/matrix.*\((.+)\)/);
10
+ if (matcher) {
11
+ const values = matcher[1].split(",").map((str) => Number.parseInt(str, 10));
12
+ if (values.length === 6) {
13
+ x = values[4];
14
+ y = values[5];
15
+ }
16
+ else if (values.length === 16) {
17
+ x = values[12];
18
+ y = values[13];
19
+ z = values[14];
20
+ }
21
+ }
22
+ return { x, y, z };
23
+ };
24
+ export const useAnimation = (nodeRef, computeAnimation) => {
25
+ const snapshot = React.useRef();
26
+ const animation = React.useRef();
27
+ const reduceMotion = useMotionPreference();
28
+ useLayoutEffect(() => {
29
+ var _a, _b, _c;
30
+ if (nodeRef.current && snapshot.current !== undefined && !reduceMotion) {
31
+ const { keyframes, duration, onfinish } = computeAnimation(snapshot.current, nodeRef.current.getBoundingClientRect(), currentTransformation(nodeRef.current)) || {};
32
+ if (keyframes && duration) {
33
+ (_a = animation.current) === null || _a === void 0 ? void 0 : _a.cancel();
34
+ animation.current = (_c = (_b = nodeRef.current).animate) === null || _c === void 0 ? void 0 : _c.call(_b, keyframes, duration);
35
+ if (animation.current) {
36
+ animation.current.onfinish = () => {
37
+ animation.current = undefined;
38
+ onfinish === null || onfinish === void 0 ? void 0 : onfinish();
39
+ };
40
+ }
41
+ }
42
+ }
43
+ snapshot.current = undefined;
44
+ });
45
+ return (currentSnapshot) => {
46
+ snapshot.current = currentSnapshot;
47
+ };
48
+ };
@@ -0,0 +1 @@
1
+ export declare const useDelay: () => (callback: (...args: any[]) => void, delay: number) => void;
@@ -0,0 +1,10 @@
1
+ import * as React from "react";
2
+ import { useTimeouts } from "../contexts/Timeouts.js";
3
+ export const useDelay = () => {
4
+ const timeoutId = React.useRef();
5
+ const { setTimeout, clearTimeout } = useTimeouts();
6
+ return React.useCallback((callback, delay) => {
7
+ clearTimeout(timeoutId.current);
8
+ timeoutId.current = setTimeout(callback, delay > 0 ? delay : 0);
9
+ }, [setTimeout, clearTimeout]);
10
+ };
@@ -0,0 +1,4 @@
1
+ export declare const useLoseFocus: (disabled?: boolean) => {
2
+ onFocus: () => void;
3
+ onBlur: () => void;
4
+ };
@@ -0,0 +1,19 @@
1
+ import * as React from "react";
2
+ import { useLayoutEffect } from "./useLayoutEffect.js";
3
+ import { useController } from "../modules/Controller.js";
4
+ export const useLoseFocus = (disabled = false) => {
5
+ const focused = React.useRef(disabled);
6
+ const { transferFocus } = useController();
7
+ useLayoutEffect(() => {
8
+ if (disabled) {
9
+ transferFocus();
10
+ }
11
+ }, [disabled, transferFocus]);
12
+ const onFocus = React.useCallback(() => {
13
+ focused.current = true;
14
+ }, []);
15
+ const onBlur = React.useCallback(() => {
16
+ focused.current = false;
17
+ }, []);
18
+ return { onFocus, onBlur };
19
+ };
@@ -0,0 +1 @@
1
+ export declare const useThrottle: (callback: (...args: any[]) => void, delay: number) => (...args: any[]) => void;
@@ -0,0 +1,16 @@
1
+ import * as React from "react";
2
+ import { useDelay } from "./useDelay.js";
3
+ import { useEventCallback } from "./useEventCallback.js";
4
+ export const useThrottle = (callback, delay) => {
5
+ const lastCallbackTime = React.useRef(0);
6
+ const delayCallback = useDelay();
7
+ const executeCallback = useEventCallback((...args) => {
8
+ lastCallbackTime.current = Date.now();
9
+ callback(args);
10
+ });
11
+ return React.useCallback((...args) => {
12
+ delayCallback(() => {
13
+ executeCallback(args);
14
+ }, delay - (Date.now() - lastCallbackTime.current));
15
+ }, [delay, executeCallback, delayCallback]);
16
+ };
@@ -12,9 +12,8 @@ const cssSlidePrefix = (value) => composePrefix("slide", value);
12
12
  const CarouselSlide = ({ slide, offset }) => {
13
13
  const { setContainerRef, containerRect, containerRef } = useContainerRect();
14
14
  const { publish } = useEvents();
15
- const { state: { currentIndex }, } = useLightboxState();
16
- const { getLightboxProps } = useController();
17
- const { render, carousel: { imageFit }, on: { click: onClick }, } = getLightboxProps();
15
+ const { currentIndex } = useLightboxState().state;
16
+ const { render, carousel: { imageFit }, on: { click: onClick }, } = useController().getLightboxProps();
18
17
  const renderSlide = (rect) => {
19
18
  var _a, _b, _c, _d;
20
19
  let rendered = (_a = render.slide) === null || _a === void 0 ? void 0 : _a.call(render, slide, offset, rect);
@@ -41,7 +40,7 @@ const CarouselSlide = ({ slide, offset }) => {
41
40
  };
42
41
  const Placeholder = () => React.createElement("div", { className: cssClass("slide") });
43
42
  export const Carousel = ({ slides, carousel: { finite, preload, padding, spacing } }) => {
44
- const { state: { currentIndex, globalIndex }, } = useLightboxState();
43
+ const { currentIndex, globalIndex } = useLightboxState().state;
45
44
  const { setCarouselRef } = useController();
46
45
  const spacingValue = parseLengthPercentage(spacing);
47
46
  const paddingValue = parseLengthPercentage(padding);
@@ -1,6 +1,17 @@
1
1
  import * as React from "react";
2
2
  import { Component, ComponentProps, ContainerRect } from "../../types.js";
3
3
  import { SubscribeSensors } from "../hooks/index.js";
4
+ import { LightboxStateAction } from "../contexts/index.js";
5
+ import { ACTION_CLOSE, ACTION_NEXT, ACTION_PREV, ACTION_SWIPE, YARL_EVENT_BACKDROP_CLICK } from "../consts.js";
6
+ declare module "../" {
7
+ interface EventTypes {
8
+ [ACTION_PREV]: number;
9
+ [ACTION_NEXT]: number;
10
+ [ACTION_SWIPE]: LightboxStateAction;
11
+ [ACTION_CLOSE]: void;
12
+ [YARL_EVENT_BACKDROP_CLICK]: void;
13
+ }
14
+ }
4
15
  export type ControllerContextType = {
5
16
  getLightboxProps: () => ComponentProps;
6
17
  subscribeSensors: SubscribeSensors<HTMLDivElement>;
@@ -1,10 +1,10 @@
1
1
  import * as React from "react";
2
2
  import { createModule } from "../config.js";
3
3
  import { cleanup, clsx, cssClass, cssVar, isNumber, makeComposePrefix, makeUseContext, parseLengthPercentage, } from "../utils.js";
4
- import { useContainerRect, useEventCallback, useForkRef, useLayoutEffect, useMotionPreference, useRTL, useSensors, } from "../hooks/index.js";
5
- import { useEvents, useLightboxState, useTimeouts } from "../contexts/index.js";
4
+ import { useAnimation, useContainerRect, useDelay, useEventCallback, useForkRef, useRTL, useSensors, } from "../hooks/index.js";
5
+ import { useEvents, useLightboxState } from "../contexts/index.js";
6
6
  import { SwipeState, usePointerSwipe, usePreventSwipeNavigation, useWheelSwipe } from "./controller/index.js";
7
- import { ACTION_CLOSE, ACTION_NEXT, ACTION_PREV, CLASS_FLEX_CENTER, EVENT_ON_KEY_UP, MODULE_CONTROLLER, VK_ESCAPE, YARL_EVENT_BACKDROP_CLICK, } from "../consts.js";
7
+ import { ACTION_CLOSE, ACTION_NEXT, ACTION_PREV, ACTION_SWIPE, CLASS_FLEX_CENTER, EVENT_ON_KEY_UP, MODULE_CONTROLLER, VK_ESCAPE, YARL_EVENT_BACKDROP_CLICK, } from "../consts.js";
8
8
  const cssContainerPrefix = makeComposePrefix("container");
9
9
  const ControllerContext = React.createContext(null);
10
10
  export const useController = makeUseContext("useController", "ControllerContext", ControllerContext);
@@ -13,27 +13,45 @@ export const Controller = ({ children, ...props }) => {
13
13
  const { state, dispatch } = useLightboxState();
14
14
  const [swipeState, setSwipeState] = React.useState(SwipeState.NONE);
15
15
  const swipeOffset = React.useRef(0);
16
- const swipeAnimationReset = React.useRef();
17
16
  const { registerSensors, subscribeSensors } = useSensors();
18
17
  const { subscribe, publish } = useEvents();
19
- const { setTimeout, clearTimeout } = useTimeouts();
18
+ const cleanupAnimationIncrement = useDelay();
19
+ const cleanupSwipeOffset = useDelay();
20
20
  const { containerRef, setContainerRef, containerRect } = useContainerRect();
21
21
  const handleContainerRef = useForkRef(usePreventSwipeNavigation(), setContainerRef);
22
22
  const carouselRef = React.useRef(null);
23
23
  const setCarouselRef = useForkRef(carouselRef, undefined);
24
- const carouselAnimation = React.useRef();
25
- const carouselSwipeAnimation = React.useRef();
26
- const reduceMotion = useMotionPreference();
27
24
  const isRTL = useRTL();
28
- const rtl = useEventCallback((value) => (isRTL ? -1 : 1) * (isNumber(value) ? value : 1));
29
- const isSwipeValid = useEventCallback((offset) => !(carousel.finite &&
25
+ const rtl = (value) => (isRTL ? -1 : 1) * (isNumber(value) ? value : 1);
26
+ const isSwipeValid = (offset) => !(carousel.finite &&
30
27
  ((rtl(offset) > 0 && state.currentIndex === 0) ||
31
- (rtl(offset) < 0 && state.currentIndex === slides.length - 1))));
32
- const setSwipeOffset = React.useCallback((offset) => {
28
+ (rtl(offset) < 0 && state.currentIndex === slides.length - 1)));
29
+ const setSwipeOffset = (offset) => {
33
30
  var _a;
34
31
  swipeOffset.current = offset;
35
32
  (_a = containerRef.current) === null || _a === void 0 ? void 0 : _a.style.setProperty(cssVar("swipe_offset"), `${Math.round(offset)}px`);
36
- }, [containerRef]);
33
+ };
34
+ const animate = useAnimation(carouselRef, (snapshot, rect, translate) => {
35
+ var _a;
36
+ if (carouselRef.current && containerRect && ((_a = state.animation) === null || _a === void 0 ? void 0 : _a.duration)) {
37
+ const parsedSpacing = parseLengthPercentage(carousel.spacing);
38
+ const spacingValue = (parsedSpacing.percent ? (parsedSpacing.percent * containerRect.width) / 100 : parsedSpacing.pixel) ||
39
+ 0;
40
+ return {
41
+ keyframes: [
42
+ {
43
+ transform: `translateX(${rtl(state.globalIndex - snapshot.index) * (containerRect.width + spacingValue) +
44
+ snapshot.rect.x -
45
+ rect.x +
46
+ translate.x}px)`,
47
+ },
48
+ { transform: "translateX(0)" },
49
+ ],
50
+ duration: state.animation.duration,
51
+ };
52
+ }
53
+ return undefined;
54
+ });
37
55
  const swipe = useEventCallback((action) => {
38
56
  var _a;
39
57
  const swipeDuration = animation.swipe;
@@ -65,7 +83,7 @@ export const Controller = ({ children, ...props }) => {
65
83
  direction = rtl(currentSwipeOffset) > 0 ? ACTION_PREV : ACTION_NEXT;
66
84
  }
67
85
  }
68
- let increment;
86
+ let increment = 0;
69
87
  if (direction === ACTION_PREV) {
70
88
  if (isSwipeValid(rtl(1))) {
71
89
  increment = -count;
@@ -84,51 +102,26 @@ export const Controller = ({ children, ...props }) => {
84
102
  newSwipeAnimationDuration = swipeDuration;
85
103
  }
86
104
  }
105
+ newSwipeAnimationDuration = Math.round(newSwipeAnimationDuration);
106
+ cleanupSwipeOffset(() => {
107
+ setSwipeOffset(0);
108
+ setSwipeState(SwipeState.NONE);
109
+ }, newSwipeAnimationDuration);
87
110
  if (carouselRef.current) {
88
- carouselSwipeAnimation.current = {
111
+ animate({
89
112
  rect: carouselRef.current.getBoundingClientRect(),
90
113
  index: state.globalIndex,
91
- };
92
- }
93
- newSwipeAnimationDuration = Math.round(newSwipeAnimationDuration);
94
- clearTimeout(swipeAnimationReset.current);
95
- if (newSwipeState) {
96
- const timeoutId = setTimeout(() => {
97
- if (swipeAnimationReset.current === timeoutId) {
98
- setSwipeOffset(0);
99
- setSwipeState(SwipeState.NONE);
100
- }
101
- }, newSwipeAnimationDuration);
102
- swipeAnimationReset.current = timeoutId;
114
+ });
103
115
  }
104
116
  setSwipeState(newSwipeState);
105
- dispatch({ increment, animationDuration: newSwipeAnimationDuration });
117
+ publish(ACTION_SWIPE, { increment, duration: newSwipeAnimationDuration });
106
118
  });
107
- const animateCarouselSwipe = useEventCallback(() => {
108
- var _a, _b, _c;
109
- const swipeAnimation = carouselSwipeAnimation.current;
110
- carouselSwipeAnimation.current = undefined;
111
- if (swipeAnimation && carouselRef.current && containerRect) {
112
- const parsedSpacing = parseLengthPercentage(carousel.spacing);
113
- const spacingValue = (parsedSpacing.percent ? (parsedSpacing.percent * containerRect.width) / 100 : parsedSpacing.pixel) ||
114
- 0;
115
- (_a = carouselAnimation.current) === null || _a === void 0 ? void 0 : _a.cancel();
116
- carouselAnimation.current = (_c = (_b = carouselRef.current).animate) === null || _c === void 0 ? void 0 : _c.call(_b, [
117
- {
118
- transform: `translateX(${rtl(state.globalIndex - swipeAnimation.index) * (containerRect.width + spacingValue) +
119
- swipeAnimation.rect.x -
120
- carouselRef.current.getBoundingClientRect().x}px)`,
121
- },
122
- { transform: "translateX(0)" },
123
- ], !reduceMotion ? state.animationDuration : 0);
124
- if (carouselAnimation.current) {
125
- carouselAnimation.current.onfinish = () => {
126
- carouselAnimation.current = undefined;
127
- };
128
- }
119
+ React.useEffect(() => {
120
+ var _a, _b;
121
+ if (((_a = state.animation) === null || _a === void 0 ? void 0 : _a.increment) && ((_b = state.animation) === null || _b === void 0 ? void 0 : _b.duration)) {
122
+ cleanupAnimationIncrement(() => dispatch({ increment: 0 }), state.animation.duration);
129
123
  }
130
- });
131
- useLayoutEffect(animateCarouselSwipe);
124
+ }, [state.animation, dispatch, cleanupAnimationIncrement]);
132
125
  const swipeParams = [
133
126
  subscribeSensors,
134
127
  isSwipeValid,
@@ -153,13 +146,7 @@ export const Controller = ({ children, ...props }) => {
153
146
  (_a = on.view) === null || _a === void 0 ? void 0 : _a.call(on, state.currentIndex);
154
147
  });
155
148
  React.useEffect(handleIndexChange, [state.currentIndex, handleIndexChange]);
156
- React.useEffect(() => cleanup(subscribe(ACTION_PREV, (count) => swipe({
157
- direction: ACTION_PREV,
158
- count: isNumber(count) ? count : undefined,
159
- })), subscribe(ACTION_NEXT, (count) => swipe({
160
- direction: ACTION_NEXT,
161
- count: isNumber(count) ? count : undefined,
162
- }))), [subscribe, swipe]);
149
+ React.useEffect(() => cleanup(subscribe(ACTION_PREV, (count) => swipe({ direction: ACTION_PREV, count })), subscribe(ACTION_NEXT, (count) => swipe({ direction: ACTION_NEXT, count })), subscribe(ACTION_SWIPE, (action) => dispatch(action))), [subscribe, swipe, dispatch]);
163
150
  React.useEffect(() => subscribeSensors(EVENT_ON_KEY_UP, (event) => {
164
151
  if (event.code === VK_ESCAPE) {
165
152
  publish(ACTION_CLOSE);
@@ -1,30 +1,31 @@
1
1
  import * as React from "react";
2
2
  import { createModule } from "../config.js";
3
- import { useEventCallback, useRTL } from "../hooks/index.js";
3
+ import { useEventCallback, useLoseFocus, useRTL, useThrottle } from "../hooks/index.js";
4
4
  import { cssClass, label as translateLabel } from "../utils.js";
5
5
  import { IconButton, NextIcon, PreviousIcon } from "../components/index.js";
6
6
  import { useEvents, useLightboxState } from "../contexts/index.js";
7
7
  import { useController } from "./Controller.js";
8
8
  import { ACTION_NEXT, ACTION_PREV, EVENT_ON_KEY_DOWN, MODULE_NAVIGATION, VK_ARROW_LEFT, VK_ARROW_RIGHT, } from "../consts.js";
9
- export const NavigationButton = ({ publish, labels, label, icon, renderIcon, action, disabled, }) => (React.createElement(IconButton, { label: translateLabel(labels, label), icon: icon, renderIcon: renderIcon, className: cssClass(`navigation_${action}`), disabled: disabled, "aria-disabled": disabled, onClick: () => {
10
- publish(action);
11
- } }));
12
- export const Navigation = ({ slides, carousel: { finite }, labels, render: { buttonPrev, buttonNext, iconPrev, iconNext }, }) => {
13
- const { state: { currentIndex }, } = useLightboxState();
9
+ export const NavigationButton = ({ publish, labels, label, icon, renderIcon, action, disabled, }) => (React.createElement(IconButton, { label: translateLabel(labels, label), icon: icon, renderIcon: renderIcon, className: cssClass(`navigation_${action}`), disabled: disabled, onClick: () => publish(action), ...useLoseFocus(disabled) }));
10
+ export const Navigation = ({ slides, carousel: { finite }, animation: { swipe }, labels, render: { buttonPrev, buttonNext, iconPrev, iconNext }, }) => {
11
+ const { currentIndex } = useLightboxState().state;
14
12
  const { subscribeSensors } = useController();
15
13
  const { publish } = useEvents();
16
14
  const isRTL = useRTL();
15
+ const prevDisabled = slides.length === 0 || (finite && currentIndex === 0);
16
+ const nextDisabled = slides.length === 0 || (finite && currentIndex === slides.length - 1);
17
+ const publishThrottled = useThrottle((action) => publish(action), swipe / 2);
17
18
  const handleKeyDown = useEventCallback((event) => {
18
- if (event.key === VK_ARROW_LEFT) {
19
- publish(isRTL ? ACTION_NEXT : ACTION_PREV);
19
+ if (event.key === VK_ARROW_LEFT && !(isRTL ? nextDisabled : prevDisabled)) {
20
+ publishThrottled(isRTL ? ACTION_NEXT : ACTION_PREV);
20
21
  }
21
- else if (event.key === VK_ARROW_RIGHT) {
22
- publish(isRTL ? ACTION_PREV : ACTION_NEXT);
22
+ if (event.key === VK_ARROW_RIGHT && !(isRTL ? prevDisabled : nextDisabled)) {
23
+ publishThrottled(isRTL ? ACTION_PREV : ACTION_NEXT);
23
24
  }
24
25
  });
25
26
  React.useEffect(() => subscribeSensors(EVENT_ON_KEY_DOWN, handleKeyDown), [subscribeSensors, handleKeyDown]);
26
27
  return (React.createElement(React.Fragment, null,
27
- buttonPrev ? (buttonPrev()) : (React.createElement(NavigationButton, { label: "Previous", action: ACTION_PREV, icon: PreviousIcon, renderIcon: iconPrev, disabled: slides.length === 0 || (finite && currentIndex === 0), labels: labels, publish: publish })),
28
- buttonNext ? (buttonNext()) : (React.createElement(NavigationButton, { label: "Next", action: ACTION_NEXT, icon: NextIcon, renderIcon: iconNext, disabled: slides.length === 0 || (finite && currentIndex === slides.length - 1), labels: labels, publish: publish }))));
28
+ buttonPrev ? (buttonPrev()) : (React.createElement(NavigationButton, { label: "Previous", action: ACTION_PREV, icon: PreviousIcon, renderIcon: iconPrev, disabled: prevDisabled, labels: labels, publish: publish })),
29
+ buttonNext ? (buttonNext()) : (React.createElement(NavigationButton, { label: "Next", action: ACTION_NEXT, icon: NextIcon, renderIcon: iconNext, disabled: nextDisabled, labels: labels, publish: publish }))));
29
30
  };
30
31
  export const NavigationModule = createModule(MODULE_NAVIGATION, Navigation);
@@ -1,3 +1,9 @@
1
1
  import { Component } from "../../types.js";
2
+ import { YARL_EVENT_TOOLBAR_WIDTH } from "../consts.js";
3
+ declare module "../" {
4
+ interface EventTypes {
5
+ [YARL_EVENT_TOOLBAR_WIDTH]: number;
6
+ }
7
+ }
2
8
  export declare const Toolbar: Component;
3
9
  export declare const ToolbarModule: import("../../types.js").Module;
@@ -4,14 +4,14 @@ import { composePrefix, cssClass, label } from "../utils.js";
4
4
  import { useEvents } from "../contexts/index.js";
5
5
  import { CloseIcon, IconButton } from "../components/index.js";
6
6
  import { useContainerRect } from "../hooks/useContainerRect.js";
7
- import { ACTION_CLOSE, MODULE_TOOLBAR } from "../consts.js";
7
+ import { ACTION_CLOSE, MODULE_TOOLBAR, YARL_EVENT_TOOLBAR_WIDTH } from "../consts.js";
8
8
  const cssPrefix = (value) => composePrefix(MODULE_TOOLBAR, value);
9
9
  export const Toolbar = ({ toolbar: { buttons }, labels, render: { buttonClose, iconClose } }) => {
10
10
  const { publish } = useEvents();
11
11
  const { setContainerRef, containerRect } = useContainerRect();
12
12
  React.useEffect(() => {
13
13
  if (containerRect === null || containerRect === void 0 ? void 0 : containerRect.width) {
14
- publish("toolbar-width", containerRect.width);
14
+ publish(YARL_EVENT_TOOLBAR_WIDTH, containerRect.width);
15
15
  }
16
16
  }, [publish, containerRect === null || containerRect === void 0 ? void 0 : containerRect.width]);
17
17
  const renderCloseButton = () => buttonClose ? (buttonClose()) : (React.createElement(IconButton, { key: ACTION_CLOSE, label: label(labels, "Close"), icon: CloseIcon, renderIcon: iconClose, onClick: () => publish(ACTION_CLOSE) }));
@@ -21,3 +21,5 @@ export declare const parseLengthPercentage: (input: unknown) => {
21
21
  pixel?: undefined;
22
22
  };
23
23
  export declare const devicePixelRatio: () => number;
24
+ export declare const getSlideIndex: (index: number, slidesCount: number) => number;
25
+ export declare const getSlide: (slides: Slide[], index: number) => Slide;
@@ -39,3 +39,5 @@ export const parseLengthPercentage = (input) => {
39
39
  export const devicePixelRatio = () => {
40
40
  return (typeof window !== "undefined" ? window === null || window === void 0 ? void 0 : window.devicePixelRatio : undefined) || 1;
41
41
  };
42
+ export const getSlideIndex = (index, slidesCount) => ((index % slidesCount) + slidesCount) % slidesCount;
43
+ export const getSlide = (slides, index) => slides[getSlideIndex(index, slides.length)];