yet-another-react-lightbox 3.23.4 → 3.25.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/dist/index.d.ts CHANGED
@@ -12,7 +12,10 @@ declare function cssClass(name: string): string;
12
12
  declare function cssVar(name: string): string;
13
13
  declare function composePrefix(base: string, prefix?: string): string;
14
14
  declare function makeComposePrefix(base: string): (prefix?: string) => string;
15
+ declare function translateLabel(labels: Labels | undefined, defaultLabel: Label): string;
16
+ /** @deprecated - use `translateLabel` instead */
15
17
  declare function label(labels: Labels | undefined, defaultLabel: Label): string;
18
+ declare function translateSlideCounter(labels: Labels | undefined, slides: Slide[], index: number): string;
16
19
  declare function cleanup(...cleaners: (() => void)[]): () => void;
17
20
  declare function makeUseContext<T>(name: string, contextName: string, context: React.Context<T | null>): () => NonNullable<T>;
18
21
  declare function hasWindow(): boolean;
@@ -111,6 +114,19 @@ declare function useSensors<T extends Element>(): UseSensors<T>;
111
114
 
112
115
  declare function useThrottle(callback: (...args: unknown[]) => void, delay: number): (...args: unknown[]) => void;
113
116
 
117
+ type A11yContextType = {
118
+ focusWithin: boolean;
119
+ trackFocusWithin: (onFocus?: React.FocusEventHandler, onBlur?: React.FocusEventHandler) => {
120
+ onFocus: React.FocusEventHandler;
121
+ onBlur: React.FocusEventHandler;
122
+ };
123
+ autoPlaying: boolean;
124
+ setAutoPlaying: (value: boolean) => void;
125
+ };
126
+ declare const A11yContext: React.Context<A11yContextType | null>;
127
+ declare const useA11yContext: () => A11yContextType;
128
+ declare function A11yContextProvider({ children }: React.PropsWithChildren): React.JSX.Element;
129
+
114
130
  type DocumentContextType = {
115
131
  getOwnerDocument: (node?: Node | null) => Document;
116
132
  getOwnerWindow: (node?: Node | null) => Window;
@@ -215,7 +231,7 @@ declare function ImageSlide({ slide: image, offset, render, rect, imageFit, imag
215
231
 
216
232
  declare const LightboxRoot: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
217
233
 
218
- declare function Carousel({ carousel }: ComponentProps): React.JSX.Element;
234
+ declare function Carousel({ carousel, labels }: ComponentProps): React.JSX.Element;
219
235
  declare const CarouselModule: Module;
220
236
 
221
237
  declare enum SwipeState {
@@ -275,7 +291,7 @@ declare function useNavigationState(): {
275
291
  declare function NoScroll({ noScroll: { disabled }, children }: ComponentProps): React.JSX.Element;
276
292
  declare const NoScrollModule: Module;
277
293
 
278
- declare function Portal({ children, animation, styles, className, on, portal, close }: ComponentProps): React.ReactPortal | null;
294
+ declare function Portal({ children, animation, styles, className, on, portal, close, labels }: ComponentProps): React.ReactPortal | null;
279
295
  declare const PortalModule: Module;
280
296
 
281
297
  declare function Root({ children }: ComponentProps): React.JSX.Element;
@@ -284,5 +300,5 @@ declare const RootModule: Module;
284
300
  declare function Toolbar({ toolbar: { buttons }, render: { buttonClose, iconClose }, styles }: ComponentProps): React.JSX.Element;
285
301
  declare const ToolbarModule: Module;
286
302
 
287
- export { Augmentation, Callback, Carousel, CarouselModule, CarouselSettings, CloseIcon, Component, ComponentProps, ContainerRect, Controller, ControllerContext, ControllerModule, ControllerRef, ControllerSettings, DocumentContext, DocumentContextProvider, ErrorIcon, EventTypes, EventsContext, EventsProvider, IconButton, ImageSlide, Label, Labels, LengthOrPercentage, Lightbox, LightboxDefaultProps, LightboxDispatchContext, LightboxExternalProps, LightboxProps, LightboxPropsContext, LightboxPropsProvider, LightboxRoot, LightboxState, LightboxStateContext, LightboxStateProvider, LightboxStateSwipeAction, LightboxStateUpdateAction, LoadingIcon, Module, Navigation, NavigationButton, NavigationModule, NextIcon, NoScroll, NoScrollModule, Node$1 as Node, Plugin, Portal, PortalModule, PreviousIcon, Render, RenderFunction, Root, RootModule, Slide, SlideImage, SwipeState, TimeoutsContext, TimeoutsProvider, Toolbar, ToolbarModule, ToolbarSettings, addToolbarButton, calculatePreload, cleanup, clsx, composePrefix, computeSlideRect, createIcon, createIconDisabled, createModule, createNode, cssClass, cssVar, Lightbox as default, devicePixelRatio, getSlide, getSlideIfPresent, getSlideIndex, getSlideKey, hasSlides, hasWindow, isImageFitCover, isImageSlide, label, makeComposePrefix, makeInertWhen, makeUseContext, parseInt, parseLengthPercentage, reflow, round, setRef, stopNavigationEventsPropagation, useAnimation, useContainerRect, useController, useDelay, useDocumentContext, useEventCallback, useEvents, useForkRef, useKeyboardNavigation, useLayoutEffect, useLightboxDispatch, useLightboxProps, useLightboxState, useLoseFocus, useMotionPreference, useNavigationState, usePointerSwipe, usePreventWheelDefaults, useRTL, useSensors, useThrottle, useTimeouts, useWheelSwipe, withPlugins };
288
- export type { ComputeAnimation, ControllerContextType, DocumentContextProviderProps, DocumentContextType, Event, EventCallback, EventsContextType, IconButtonProps, ImageSlideProps, KeyboardEventType, LightboxDispatchContextType, LightboxPropsContextType, LightboxStateAction, LightboxStateContextType, LightboxStateProviderProps, NavigationButtonProps, PointerEventType, Publish, ReactEventType, RegisterSensors, SensorCallback, Subscribe, SubscribeSensors, SupportedEventType, TimeoutsContextType, Topic, Unsubscribe, UseSensors, WheelEventType };
303
+ export { A11yContext, A11yContextProvider, Augmentation, Callback, Carousel, CarouselModule, CarouselSettings, CloseIcon, Component, ComponentProps, ContainerRect, Controller, ControllerContext, ControllerModule, ControllerRef, ControllerSettings, DocumentContext, DocumentContextProvider, ErrorIcon, EventTypes, EventsContext, EventsProvider, IconButton, ImageSlide, Label, Labels, LengthOrPercentage, Lightbox, LightboxDefaultProps, LightboxDispatchContext, LightboxExternalProps, LightboxProps, LightboxPropsContext, LightboxPropsProvider, LightboxRoot, LightboxState, LightboxStateContext, LightboxStateProvider, LightboxStateSwipeAction, LightboxStateUpdateAction, LoadingIcon, Module, Navigation, NavigationButton, NavigationModule, NextIcon, NoScroll, NoScrollModule, Node$1 as Node, Plugin, Portal, PortalModule, PreviousIcon, Render, RenderFunction, Root, RootModule, Slide, SlideImage, SwipeState, TimeoutsContext, TimeoutsProvider, Toolbar, ToolbarModule, ToolbarSettings, addToolbarButton, calculatePreload, cleanup, clsx, composePrefix, computeSlideRect, createIcon, createIconDisabled, createModule, createNode, cssClass, cssVar, Lightbox as default, devicePixelRatio, getSlide, getSlideIfPresent, getSlideIndex, getSlideKey, hasSlides, hasWindow, isImageFitCover, isImageSlide, label, makeComposePrefix, makeInertWhen, makeUseContext, parseInt, parseLengthPercentage, reflow, round, setRef, stopNavigationEventsPropagation, translateLabel, translateSlideCounter, useA11yContext, useAnimation, useContainerRect, useController, useDelay, useDocumentContext, useEventCallback, useEvents, useForkRef, useKeyboardNavigation, useLayoutEffect, useLightboxDispatch, useLightboxProps, useLightboxState, useLoseFocus, useMotionPreference, useNavigationState, usePointerSwipe, usePreventWheelDefaults, useRTL, useSensors, useThrottle, useTimeouts, useWheelSwipe, withPlugins };
304
+ export type { A11yContextType, ComputeAnimation, ControllerContextType, DocumentContextProviderProps, DocumentContextType, Event, EventCallback, EventsContextType, IconButtonProps, ImageSlideProps, KeyboardEventType, LightboxDispatchContextType, LightboxPropsContextType, LightboxStateAction, LightboxStateContextType, LightboxStateProviderProps, NavigationButtonProps, PointerEventType, Publish, ReactEventType, RegisterSensors, SensorCallback, Subscribe, SubscribeSensors, SupportedEventType, TimeoutsContextType, Topic, Unsubscribe, UseSensors, WheelEventType };
package/dist/index.js CHANGED
@@ -20,10 +20,18 @@ function composePrefix(base, prefix) {
20
20
  function makeComposePrefix(base) {
21
21
  return (prefix) => composePrefix(base, prefix);
22
22
  }
23
- function label(labels, defaultLabel) {
23
+ function translateLabel(labels, defaultLabel) {
24
24
  var _a;
25
25
  return (_a = labels === null || labels === void 0 ? void 0 : labels[defaultLabel]) !== null && _a !== void 0 ? _a : defaultLabel;
26
26
  }
27
+ function label(labels, defaultLabel) {
28
+ return translateLabel(labels, defaultLabel);
29
+ }
30
+ function translateSlideCounter(labels, slides, index) {
31
+ return translateLabel(labels, "{index} of {total}")
32
+ .replace(/\{index}/g, `${getSlideIndex(index, slides.length) + 1}`)
33
+ .replace(/\{total}/g, `${slides.length}`);
34
+ }
27
35
  function cleanup(...cleaners) {
28
36
  return () => {
29
37
  cleaners.forEach((cleaner) => {
@@ -263,6 +271,30 @@ function withPlugins(root, plugins = [], augmentations = []) {
263
271
  };
264
272
  }
265
273
 
274
+ const A11yContext = React.createContext(null);
275
+ const useA11yContext = makeUseContext("useA11yContext", "A11yContext", A11yContext);
276
+ function A11yContextProvider({ children }) {
277
+ const [focusWithin, setFocusWithin] = React.useState(false);
278
+ const [autoPlaying, setAutoPlaying] = React.useState(false);
279
+ const context = React.useMemo(() => {
280
+ const trackFocusWithin = (onFocus, onBlur) => {
281
+ const trackAndDelegate = (focusWithinValue) => (event) => {
282
+ var _a;
283
+ if (!event.currentTarget.contains(event.relatedTarget)) {
284
+ setFocusWithin(focusWithinValue);
285
+ }
286
+ (_a = (focusWithinValue ? onFocus : onBlur)) === null || _a === void 0 ? void 0 : _a(event);
287
+ };
288
+ return {
289
+ onFocus: trackAndDelegate(true),
290
+ onBlur: trackAndDelegate(false),
291
+ };
292
+ };
293
+ return { focusWithin, trackFocusWithin, autoPlaying, setAutoPlaying };
294
+ }, [focusWithin, autoPlaying]);
295
+ return React.createElement(A11yContext.Provider, { value: context }, children);
296
+ }
297
+
266
298
  const DocumentContext = React.createContext(null);
267
299
  const useDocumentContext = makeUseContext("useDocument", "DocumentContext", DocumentContext);
268
300
  function DocumentContextProvider({ nodeRef, children }) {
@@ -389,9 +421,9 @@ function TimeoutsProvider({ children }) {
389
421
  return React.createElement(TimeoutsContext.Provider, { value: context }, children);
390
422
  }
391
423
 
392
- const IconButton = React.forwardRef(function IconButton({ label: label$1, className, icon: Icon, renderIcon, onClick, style, ...rest }, ref) {
424
+ const IconButton = React.forwardRef(function IconButton({ label, className, icon: Icon, renderIcon, onClick, style, ...rest }, ref) {
393
425
  const { styles, labels } = useLightboxProps();
394
- const buttonLabel = label(labels, label$1);
426
+ const buttonLabel = translateLabel(labels, label);
395
427
  return (React.createElement("button", { ref: ref, type: "button", title: buttonLabel, "aria-label": buttonLabel, className: clsx(cssClass(ELEMENT_BUTTON), className), onClick: onClick, style: { ...style, ...styles.button }, ...rest }, renderIcon ? renderIcon() : React.createElement(Icon, { className: cssClass(ELEMENT_ICON), style: styles.icon })));
396
428
  });
397
429
 
@@ -634,7 +666,7 @@ function useThrottle(callback, delay) {
634
666
  const slidePrefix = makeComposePrefix("slide");
635
667
  const slideImagePrefix = makeComposePrefix("slide_image");
636
668
  function ImageSlide({ slide: image, offset, render, rect, imageFit, imageProps, onClick, onLoad, onError, style, }) {
637
- var _a, _b, _c, _d, _e, _f, _g;
669
+ var _a, _b, _c, _d, _e, _f, _g, _h;
638
670
  const [status, setStatus] = React.useState(SLIDE_STATUS_LOADING);
639
671
  const { publish } = useEvents();
640
672
  const { setTimeout } = useTimeouts();
@@ -688,7 +720,7 @@ function ImageSlide({ slide: image, offset, render, rect, imageFit, imageProps,
688
720
  const sizes = srcSet && rect && hasWindow() ? `${Math.round(Math.min(estimateActualWidth(), rect.width))}px` : undefined;
689
721
  const { style: imagePropsStyle, className: imagePropsClassName, ...restImageProps } = imageProps || {};
690
722
  return (React.createElement(React.Fragment, null,
691
- React.createElement("img", { ref: setImageRef, onLoad: handleOnLoad, onError: handleOnError, onClick: onClick, draggable: false, className: clsx(cssClass(slideImagePrefix()), cover && cssClass(slideImagePrefix("cover")), status !== SLIDE_STATUS_COMPLETE && cssClass(slideImagePrefix("loading")), imagePropsClassName), style: { ...defaultStyle, ...style, ...imagePropsStyle }, ...restImageProps, alt: image.alt, sizes: sizes, srcSet: srcSet, src: image.src }),
723
+ React.createElement("img", { ref: setImageRef, onLoad: handleOnLoad, onError: handleOnError, onClick: onClick, draggable: false, className: clsx(cssClass(slideImagePrefix()), cover && cssClass(slideImagePrefix("cover")), status !== SLIDE_STATUS_COMPLETE && cssClass(slideImagePrefix("loading")), imagePropsClassName), style: { ...defaultStyle, ...style, ...imagePropsStyle }, ...restImageProps, alt: (_h = image.alt) !== null && _h !== void 0 ? _h : "", sizes: sizes, srcSet: srcSet, src: image.src }),
692
724
  status !== SLIDE_STATUS_COMPLETE && (React.createElement("div", { className: cssClass(slidePrefix(SLIDE_STATUS_PLACEHOLDER)) },
693
725
  status === SLIDE_STATUS_LOADING &&
694
726
  ((render === null || render === void 0 ? void 0 : render.iconLoading) ? (render.iconLoading()) : (React.createElement(LoadingIcon, { className: clsx(cssClass(ELEMENT_ICON), cssClass(slidePrefix(SLIDE_STATUS_LOADING))) }))),
@@ -696,10 +728,11 @@ function ImageSlide({ slide: image, offset, render, rect, imageFit, imageProps,
696
728
  ((render === null || render === void 0 ? void 0 : render.iconError) ? (render.iconError()) : (React.createElement(ErrorIcon, { className: clsx(cssClass(ELEMENT_ICON), cssClass(slidePrefix(SLIDE_STATUS_ERROR))) })))))));
697
729
  }
698
730
 
699
- const LightboxRoot = React.forwardRef(function LightboxRoot({ className, children, ...rest }, ref) {
731
+ const LightboxRoot = React.forwardRef(function LightboxRoot({ className, children, onFocus, onBlur, ...rest }, ref) {
700
732
  const nodeRef = React.useRef(null);
733
+ const { trackFocusWithin } = useA11yContext();
701
734
  return (React.createElement(DocumentContextProvider, { nodeRef: nodeRef },
702
- React.createElement("div", { ref: useForkRef(ref, nodeRef), className: clsx(cssClass("root"), className), ...rest }, children)));
735
+ React.createElement("div", { ref: useForkRef(ref, nodeRef), className: clsx(cssClass("root"), className), ...trackFocusWithin(onFocus, onBlur), ...rest }, children)));
703
736
  });
704
737
 
705
738
  var SwipeState;
@@ -1224,7 +1257,7 @@ function Controller({ children, ...props }) {
1224
1257
  : null),
1225
1258
  ...(controller.touchAction !== "none" ? { [cssVar("controller_touch_action")]: controller.touchAction } : null),
1226
1259
  ...styles.container,
1227
- }, ...(controller.aria ? { role: "region", "aria-live": "polite", "aria-roledescription": "carousel" } : null), tabIndex: -1, ...registerSensors }, containerRect && (React.createElement(ControllerContext.Provider, { value: context },
1260
+ }, tabIndex: -1, ...registerSensors }, containerRect && (React.createElement(ControllerContext.Provider, { value: context },
1228
1261
  children, (_a = render.controls) === null || _a === void 0 ? void 0 :
1229
1262
  _a.call(render)))));
1230
1263
  }
@@ -1238,9 +1271,9 @@ function cssSlidePrefix(value) {
1238
1271
  }
1239
1272
  function CarouselSlide({ slide, offset }) {
1240
1273
  const containerRef = React.useRef(null);
1241
- const { currentIndex } = useLightboxState();
1274
+ const { currentIndex, slides } = useLightboxState();
1242
1275
  const { slideRect, focus } = useController();
1243
- const { render, carousel: { imageFit, imageProps }, on: { click: onClick }, styles: { slide: style }, } = useLightboxProps();
1276
+ const { render, carousel: { imageFit, imageProps }, on: { click: onClick }, styles: { slide: style }, labels, } = useLightboxProps();
1244
1277
  const { getOwnerDocument } = useDocumentContext();
1245
1278
  const offscreen = offset !== 0;
1246
1279
  React.useEffect(() => {
@@ -1260,15 +1293,16 @@ function CarouselSlide({ slide, offset }) {
1260
1293
  ((_c = render.slideContainer) !== null && _c !== void 0 ? _c : (({ children }) => children))({ slide, children: rendered }), (_d = render.slideFooter) === null || _d === void 0 ? void 0 :
1261
1294
  _d.call(render, { slide }))) : null;
1262
1295
  };
1263
- return (React.createElement("div", { ref: containerRef, className: clsx(cssClass(cssSlidePrefix()), !offscreen && cssClass(cssSlidePrefix("current")), cssClass(CLASS_FLEX_CENTER)), ...makeInertWhen(offscreen), style: style, role: "region", "aria-roledescription": "slide" }, renderSlide()));
1296
+ return (React.createElement("div", { ref: containerRef, className: clsx(cssClass(cssSlidePrefix()), !offscreen && cssClass(cssSlidePrefix("current")), cssClass(CLASS_FLEX_CENTER)), ...makeInertWhen(offscreen), style: style, role: "group", "aria-roledescription": translateLabel(labels, "Slide"), "aria-label": translateSlideCounter(labels, slides, currentIndex + offset) }, renderSlide()));
1264
1297
  }
1265
1298
  function Placeholder() {
1266
1299
  const style = useLightboxProps().styles.slide;
1267
1300
  return React.createElement("div", { className: cssClass(CLASS_SLIDE), style: style });
1268
1301
  }
1269
- function Carousel({ carousel }) {
1302
+ function Carousel({ carousel, labels }) {
1270
1303
  const { slides, currentIndex, globalIndex } = useLightboxState();
1271
1304
  const { setCarouselRef } = useController();
1305
+ const { autoPlaying, focusWithin } = useA11yContext();
1272
1306
  const spacingValue = parseLengthPercentage(carousel.spacing);
1273
1307
  const paddingValue = parseLengthPercentage(carousel.padding);
1274
1308
  const preload = calculatePreload(carousel, slides, 1);
@@ -1293,7 +1327,7 @@ function Carousel({ carousel }) {
1293
1327
  [`${cssVar(cssPrefix$2("spacing_percent"))}`]: spacingValue.percent || 0,
1294
1328
  [`${cssVar(cssPrefix$2("padding_px"))}`]: paddingValue.pixel || 0,
1295
1329
  [`${cssVar(cssPrefix$2("padding_percent"))}`]: paddingValue.percent || 0,
1296
- } }, items.map(({ key, slide, offset }) => slide ? React.createElement(CarouselSlide, { key: key, slide: slide, offset: offset }) : React.createElement(Placeholder, { key: key }))));
1330
+ }, role: "region", "aria-live": autoPlaying && !focusWithin ? "off" : "polite", "aria-roledescription": translateLabel(labels, "Carousel"), "aria-label": translateLabel(labels, "Photo gallery") }, items.map(({ key, slide, offset }) => slide ? React.createElement(CarouselSlide, { key: key, slide: slide, offset: offset }) : React.createElement(Placeholder, { key: key }))));
1297
1331
  }
1298
1332
  const CarouselModule = createModule(MODULE_CAROUSEL, Carousel);
1299
1333
 
@@ -1413,7 +1447,7 @@ function setAttribute(element, attribute, value) {
1413
1447
  }
1414
1448
  };
1415
1449
  }
1416
- function Portal({ children, animation, styles, className, on, portal, close }) {
1450
+ function Portal({ children, animation, styles, className, on, portal, close, labels }) {
1417
1451
  const [mounted, setMounted] = React.useState(false);
1418
1452
  const [visible, setVisible] = React.useState(false);
1419
1453
  const cleanup = React.useRef([]);
@@ -1476,7 +1510,7 @@ function Portal({ children, animation, styles, className, on, portal, close }) {
1476
1510
  }
1477
1511
  }, [handleEnter, handleCleanup]);
1478
1512
  return mounted
1479
- ? createPortal(React.createElement(LightboxRoot, { ref: handleRef, className: clsx(className, cssClass(cssPrefix$1()), cssClass(CLASS_NO_SCROLL_PADDING), visible && cssClass(cssPrefix$1("open"))), "aria-modal": true, role: "dialog", "aria-live": "polite", "aria-roledescription": "lightbox", style: {
1513
+ ? createPortal(React.createElement(LightboxRoot, { ref: handleRef, className: clsx(className, cssClass(cssPrefix$1()), cssClass(CLASS_NO_SCROLL_PADDING), visible && cssClass(cssPrefix$1("open"))), "aria-modal": true, role: "dialog", "aria-label": translateLabel(labels, "Lightbox"), style: {
1480
1514
  ...(animation.fade !== LightboxDefaultProps.animation.fade
1481
1515
  ? { [cssVar("fade_animation_duration")]: `${animationDuration}ms` }
1482
1516
  : null),
@@ -1558,7 +1592,8 @@ function Lightbox({ carousel, animation, render, toolbar, controller, noScroll,
1558
1592
  return (React.createElement(LightboxPropsProvider, { ...props },
1559
1593
  React.createElement(LightboxStateProvider, { slides: slides || defaultSlides, index: parseInt(index || defaultIndex) },
1560
1594
  React.createElement(TimeoutsProvider, null,
1561
- React.createElement(EventsProvider, null, renderNode(createNode(RootModule, config), props))))));
1595
+ React.createElement(EventsProvider, null,
1596
+ React.createElement(A11yContextProvider, null, renderNode(createNode(RootModule, config), props)))))));
1562
1597
  }
1563
1598
 
1564
- export { ACTION_CLOSE, ACTION_NEXT, ACTION_PREV, ACTION_SWIPE, CLASS_FLEX_CENTER, CLASS_NO_SCROLL, CLASS_NO_SCROLL_PADDING, CLASS_SLIDE, CLASS_SLIDE_WRAPPER, Carousel, CarouselModule, CloseIcon, Controller, ControllerContext, ControllerModule, DocumentContext, DocumentContextProvider, 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, LightboxRoot, 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, SwipeState, TimeoutsContext, TimeoutsProvider, Toolbar, ToolbarModule, UNKNOWN_ACTION_TYPE, VK_ARROW_LEFT, VK_ARROW_RIGHT, VK_ESCAPE, activeSlideStatus, addToolbarButton, calculatePreload, cleanup, clsx, composePrefix, computeSlideRect, createIcon, createIconDisabled, createModule, createNode, cssClass, cssVar, Lightbox as default, devicePixelRatio, getSlide, getSlideIfPresent, getSlideIndex, getSlideKey, hasSlides, hasWindow, isImageFitCover, isImageSlide, label, makeComposePrefix, makeInertWhen, makeUseContext, parseInt, parseLengthPercentage, reflow, round, setRef, stopNavigationEventsPropagation, useAnimation, useContainerRect, useController, useDelay, useDocumentContext, useEventCallback, useEvents, useForkRef, useKeyboardNavigation, useLayoutEffect, useLightboxDispatch, useLightboxProps, useLightboxState, useLoseFocus, useMotionPreference, useNavigationState, usePointerEvents, usePointerSwipe, usePreventWheelDefaults, useRTL, useSensors, useThrottle, useTimeouts, useWheelSwipe, withPlugins };
1599
+ export { A11yContext, A11yContextProvider, ACTION_CLOSE, ACTION_NEXT, ACTION_PREV, ACTION_SWIPE, CLASS_FLEX_CENTER, CLASS_NO_SCROLL, CLASS_NO_SCROLL_PADDING, CLASS_SLIDE, CLASS_SLIDE_WRAPPER, Carousel, CarouselModule, CloseIcon, Controller, ControllerContext, ControllerModule, DocumentContext, DocumentContextProvider, 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, LightboxRoot, 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, SwipeState, TimeoutsContext, TimeoutsProvider, Toolbar, ToolbarModule, UNKNOWN_ACTION_TYPE, VK_ARROW_LEFT, VK_ARROW_RIGHT, VK_ESCAPE, activeSlideStatus, addToolbarButton, calculatePreload, cleanup, clsx, composePrefix, computeSlideRect, createIcon, createIconDisabled, createModule, createNode, cssClass, cssVar, Lightbox as default, devicePixelRatio, getSlide, getSlideIfPresent, getSlideIndex, getSlideKey, hasSlides, hasWindow, isImageFitCover, isImageSlide, label, makeComposePrefix, makeInertWhen, makeUseContext, parseInt, parseLengthPercentage, reflow, round, setRef, stopNavigationEventsPropagation, translateLabel, translateSlideCounter, useA11yContext, useAnimation, useContainerRect, useController, useDelay, useDocumentContext, useEventCallback, useEvents, useForkRef, useKeyboardNavigation, useLayoutEffect, useLightboxDispatch, useLightboxProps, useLightboxState, useLoseFocus, useMotionPreference, useNavigationState, usePointerEvents, usePointerSwipe, usePreventWheelDefaults, useRTL, useSensors, useThrottle, useTimeouts, useWheelSwipe, withPlugins };
@@ -48,7 +48,11 @@ declare module "yet-another-react-lightbox" {
48
48
  buttonCaptions?: RenderFunction<CaptionsRef>;
49
49
  }
50
50
  interface Labels {
51
+ /** Slide description ARIA role description */
52
+ Caption?: string;
53
+ /** `Show captions` button title */
51
54
  "Show captions"?: string;
55
+ /** `Hide captions` button title */
52
56
  "Hide captions"?: string;
53
57
  }
54
58
  /** Captions plugin ref */
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { cssClass, useLightboxProps, makeUseContext, useController, clsx, cssVar, createIcon, createIconDisabled, IconButton, addToolbarButton, createModule } from '../../index.js';
2
+ import { cssClass, useLightboxProps, makeUseContext, useController, clsx, cssVar, translateLabel, createIcon, createIconDisabled, IconButton, addToolbarButton, createModule } from '../../index.js';
3
3
  import { PLUGIN_CAPTIONS } from '../../types.js';
4
4
 
5
5
  const cssPrefix = (className) => cssClass(`slide_${className}`);
@@ -39,7 +39,7 @@ function Title({ title }) {
39
39
  const { visible } = useCaptions();
40
40
  if (!visible)
41
41
  return null;
42
- return (React.createElement("div", { style: styles.captionsTitleContainer, className: clsx(cssPrefix("captions_container"), cssPrefix("title_container")) },
42
+ return (React.createElement("div", { role: "heading", "aria-level": 2, style: styles.captionsTitleContainer, className: clsx(cssPrefix("captions_container"), cssPrefix("title_container")) },
43
43
  React.createElement("div", { className: cssPrefix("title"), style: {
44
44
  ...(toolbarWidth ? { [cssVar("toolbar_width")]: `${toolbarWidth}px` } : null),
45
45
  ...styles.captionsTitle,
@@ -48,7 +48,7 @@ function Title({ title }) {
48
48
 
49
49
  function Description({ description }) {
50
50
  const { descriptionTextAlign, descriptionMaxLines } = useCaptionsProps();
51
- const { styles } = useLightboxProps();
51
+ const { styles, labels } = useLightboxProps();
52
52
  const { visible } = useCaptions();
53
53
  if (!visible)
54
54
  return null;
@@ -62,7 +62,7 @@ function Description({ description }) {
62
62
  }
63
63
  : null),
64
64
  ...styles.captionsDescription,
65
- } }, typeof description === "string"
65
+ }, role: "paragraph", "aria-roledescription": translateLabel(labels, "Caption") }, typeof description === "string"
66
66
  ? description.split("\n").flatMap((line, index) => [...(index > 0 ? [React.createElement("br", { key: index })] : []), line])
67
67
  : description)));
68
68
  }
@@ -16,7 +16,7 @@ function CounterComponent({ counter }) {
16
16
  const { separator, container: { className, ...rest }, className: legacyClassName, ...legacyRest } = resolveCounterProps(counter);
17
17
  if (slides.length === 0)
18
18
  return null;
19
- return (React.createElement("div", { className: clsx(cssClass("counter"), className || legacyClassName), ...legacyRest, ...rest },
19
+ return (React.createElement("div", { className: clsx(cssClass("counter"), className || legacyClassName), ...legacyRest, ...rest, "aria-hidden": true },
20
20
  currentIndex + 1,
21
21
  " ",
22
22
  separator,
@@ -30,6 +30,7 @@ declare module "yet-another-react-lightbox" {
30
30
  iconDownload?: RenderFunction;
31
31
  }
32
32
  interface Labels {
33
+ /** `Download` button title */
33
34
  Download?: string;
34
35
  }
35
36
  interface Callbacks {
@@ -23,7 +23,9 @@ declare module "yet-another-react-lightbox" {
23
23
  iconExitFullscreen?: RenderFunction;
24
24
  }
25
25
  interface Labels {
26
+ /** `Enter Fullscreen` button title */
26
27
  "Enter Fullscreen"?: string;
28
+ /** `Exit Fullscreen` button title */
27
29
  "Exit Fullscreen"?: string;
28
30
  }
29
31
  interface Callbacks {
@@ -30,6 +30,7 @@ declare module "yet-another-react-lightbox" {
30
30
  iconShare?: RenderFunction;
31
31
  }
32
32
  interface Labels {
33
+ /** `Share` button title */
33
34
  Share?: string;
34
35
  }
35
36
  interface Callbacks {
@@ -25,7 +25,9 @@ declare module "yet-another-react-lightbox" {
25
25
  buttonSlideshow?: RenderFunction<SlideshowRef>;
26
26
  }
27
27
  interface Labels {
28
+ /** `Play` button title */
28
29
  Play?: string;
30
+ /** `Pause` button title */
29
31
  Pause?: string;
30
32
  }
31
33
  interface Callbacks {
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { makeUseContext, useLightboxState, useTimeouts, useEvents, useController, useEventCallback, cleanup, createIcon, useLightboxProps, useLoseFocus, IconButton, addToolbarButton, createModule } from '../../index.js';
2
+ import { makeUseContext, useLightboxState, useTimeouts, useEvents, useController, useA11yContext, useEventCallback, cleanup, createIcon, useLightboxProps, useLoseFocus, IconButton, addToolbarButton, createModule } from '../../index.js';
3
3
  import { SLIDE_STATUS_LOADING, SLIDE_STATUS_PLAYING, ACTIVE_SLIDE_LOADING, ACTIVE_SLIDE_PLAYING, ACTIVE_SLIDE_ERROR, SLIDE_STATUS_ERROR, ACTIVE_SLIDE_COMPLETE, SLIDE_STATUS_COMPLETE, PLUGIN_SLIDESHOW } from '../../types.js';
4
4
 
5
5
  const defaultSlideshowProps = {
@@ -24,6 +24,8 @@ function SlideshowContextProvider({ slideshow, carousel: { finite }, on, childre
24
24
  const { setTimeout, clearTimeout } = useTimeouts();
25
25
  const { subscribe } = useEvents();
26
26
  const { next } = useController();
27
+ const { setAutoPlaying } = useA11yContext();
28
+ React.useEffect(() => setAutoPlaying(playing), [playing, setAutoPlaying]);
27
29
  const disabled = slides.length === 0 || (finite && currentIndex === slides.length - 1);
28
30
  const play = React.useCallback(() => {
29
31
  if (!playing && !disabled) {
@@ -53,7 +53,11 @@ declare module "yet-another-react-lightbox" {
53
53
  buttonThumbnails?: RenderFunction<ThumbnailsRef>;
54
54
  }
55
55
  interface Labels {
56
+ /** Thumbnails ARIA label */
57
+ Thumbnails?: string;
58
+ /** `Show thumbnails` button title */
56
59
  "Show thumbnails"?: string;
60
+ /** `Hide thumbnails` button title */
57
61
  "Hide thumbnails"?: string;
58
62
  }
59
63
  /** `render.thumbnail` render function props */
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { useLightboxProps, composePrefix, createIcon, ImageSlide, isImageSlide, cssClass, makeComposePrefix, useDocumentContext, useEventCallback, cssVar, clsx, getSlideKey, useRTL, useEvents, useLightboxState, useSensors, useKeyboardNavigation, useAnimation, cleanup, calculatePreload, hasSlides, getSlide, makeUseContext, LightboxPropsProvider, createIconDisabled, IconButton, addToolbarButton, createModule } from '../../index.js';
2
+ import { useLightboxProps, composePrefix, createIcon, ImageSlide, isImageSlide, cssClass, makeComposePrefix, useLightboxState, useDocumentContext, useEventCallback, translateSlideCounter, cssVar, clsx, getSlideKey, useRTL, useEvents, useSensors, useKeyboardNavigation, useAnimation, cleanup, calculatePreload, hasSlides, getSlide, translateLabel, makeUseContext, LightboxPropsProvider, createIconDisabled, IconButton, addToolbarButton, createModule } from '../../index.js';
3
3
  import { PLUGIN_THUMBNAILS, ELEMENT_ICON, CLASS_FLEX_CENTER, ACTION_SWIPE, ACTION_NEXT, ACTION_PREV, PLUGIN_FULLSCREEN, MODULE_CONTROLLER } from '../../types.js';
4
4
 
5
5
  const defaultThumbnailsProps = {
@@ -57,12 +57,14 @@ const fadeOutPrefix = makeComposePrefix("fadeout");
57
57
  const placeholderPrefix = makeComposePrefix("placeholder");
58
58
  const DELAY = "delay";
59
59
  const DURATION = "duration";
60
- function Thumbnail({ slide, onClick, active, fadeIn, fadeOut, placeholder, onLoseFocus }) {
60
+ function Thumbnail({ slide, index, onClick, fadeIn, fadeOut, placeholder, onLoseFocus }) {
61
61
  const ref = React.useRef(null);
62
- const { render, styles } = useLightboxProps();
62
+ const { render, styles, labels } = useLightboxProps();
63
+ const { slides, globalIndex } = useLightboxState();
63
64
  const { getOwnerDocument } = useDocumentContext();
64
65
  const { width, height, imageFit } = useThumbnailsProps();
65
66
  const rect = { width, height };
67
+ const active = index === globalIndex;
66
68
  const onLoseFocusCallback = useEventCallback(onLoseFocus);
67
69
  React.useEffect(() => {
68
70
  if (fadeOut && getOwnerDocument().activeElement === ref.current) {
@@ -83,7 +85,7 @@ function Thumbnail({ slide, onClick, active, fadeIn, fadeOut, placeholder, onLos
83
85
  }
84
86
  : null),
85
87
  ...styles.thumbnail,
86
- }, onClick: onClick }, slide && renderThumbnail({ slide, render, rect, imageFit })));
88
+ }, onClick: onClick, "aria-current": active ? true : undefined, "aria-label": translateSlideCounter(labels, slides, index) }, slide && renderThumbnail({ slide, render, rect, imageFit })));
87
89
  }
88
90
 
89
91
  function isHorizontal(position) {
@@ -103,7 +105,7 @@ function ThumbnailsTrack({ visible, containerRef }) {
103
105
  const track = React.useRef(null);
104
106
  const isRTL = useRTL();
105
107
  const { publish, subscribe } = useEvents();
106
- const { carousel, styles } = useLightboxProps();
108
+ const { carousel, styles, labels } = useLightboxProps();
107
109
  const { slides, globalIndex, animation } = useLightboxState();
108
110
  const { registerSensors, subscribeSensors } = useSensors();
109
111
  useKeyboardNavigation(subscribeSensors);
@@ -180,7 +182,7 @@ function ThumbnailsTrack({ visible, containerRef }) {
180
182
  ...(gap !== defaultThumbnailsProps.gap ? { [cssVar(cssThumbnailPrefix("gap"))]: `${gap}px` } : null),
181
183
  ...styles.thumbnailsContainer,
182
184
  } },
183
- React.createElement("nav", { ref: track, style: styles.thumbnailsTrack, className: clsx(cssClass(cssPrefix("track")), cssClass(CLASS_FLEX_CENTER)), tabIndex: -1, ...registerSensors }, items.map(({ key, index, slide }) => {
185
+ React.createElement("nav", { ref: track, style: styles.thumbnailsTrack, className: clsx(cssClass(cssPrefix("track")), cssClass(CLASS_FLEX_CENTER)), "aria-label": translateLabel(labels, "Thumbnails"), tabIndex: -1, ...registerSensors }, items.map(({ key, index, slide }) => {
184
186
  const fadeAnimationDuration = animationDuration / Math.abs(offset || 1);
185
187
  const fadeIn = (offset > 0 && index > globalIndex + preload - offset && index <= globalIndex + preload) ||
186
188
  (offset < 0 && index < globalIndex - preload - offset && index >= globalIndex - preload)
@@ -199,7 +201,7 @@ function ThumbnailsTrack({ visible, containerRef }) {
199
201
  : -offset - (index - (globalIndex + preload))) * fadeAnimationDuration,
200
202
  }
201
203
  : undefined;
202
- return (React.createElement(Thumbnail, { key: key, slide: slide, active: index === globalIndex, fadeIn: fadeIn, fadeOut: fadeOut, placeholder: !slide, onClick: handleClick(index), onLoseFocus: () => { var _a; return (_a = track.current) === null || _a === void 0 ? void 0 : _a.focus(); } }));
204
+ return (React.createElement(Thumbnail, { key: key, index: index, slide: slide, fadeIn: fadeIn, fadeOut: fadeOut, placeholder: !slide, onClick: handleClick(index), onLoseFocus: () => { var _a; return (_a = track.current) === null || _a === void 0 ? void 0 : _a.focus(); } }));
203
205
  })),
204
206
  vignette && React.createElement("div", { className: cssClass(cssPrefix("vignette")) })));
205
207
  }
@@ -10,6 +10,8 @@ declare module "yet-another-react-lightbox" {
10
10
  zoom?: {
11
11
  /** Zoom plugin ref */
12
12
  ref?: React.ForwardedRef<ZoomRef>;
13
+ /** override minimum zoom level (default: 1.0) */
14
+ minZoom?: number;
13
15
  /** ratio of image pixels to physical pixels at maximum zoom level */
14
16
  maxZoomPixelRatio?: number;
15
17
  /** zoom-in multiplier */
@@ -43,7 +45,9 @@ declare module "yet-another-react-lightbox" {
43
45
  iconZoomOut?: RenderFunction;
44
46
  }
45
47
  interface Labels {
48
+ /** `Zoom in` button title */
46
49
  "Zoom in"?: string;
50
+ /** `Zoom out` button title */
47
51
  "Zoom out"?: string;
48
52
  }
49
53
  interface RenderSlideProps {
@@ -68,6 +72,8 @@ declare module "yet-another-react-lightbox" {
68
72
  interface ZoomRef {
69
73
  /** current zoom level */
70
74
  zoom: number;
75
+ /** minimum zoom level */
76
+ minZoom: number;
71
77
  /** maximum zoom level */
72
78
  maxZoom: number;
73
79
  /** horizontal offset */
@@ -3,6 +3,7 @@ import { useLightboxProps, useMotionPreference, useEventCallback, useLayoutEffec
3
3
  import { EVENT_ON_KEY_DOWN, EVENT_ON_WHEEL, UNKNOWN_ACTION_TYPE, CLASS_FULLSIZE, CLASS_FLEX_CENTER, CLASS_SLIDE_WRAPPER, CLASS_SLIDE_WRAPPER_INTERACTIVE, PLUGIN_ZOOM } from '../../types.js';
4
4
 
5
5
  const defaultZoomProps = {
6
+ minZoom: 1,
6
7
  maxZoomPixelRatio: 1,
7
8
  zoomInMultiplier: 2,
8
9
  doubleTapDelay: 300,
@@ -13,10 +14,13 @@ const defaultZoomProps = {
13
14
  pinchZoomDistanceFactor: 100,
14
15
  scrollToZoom: false,
15
16
  };
16
- const resolveZoomProps = (zoom) => ({
17
- ...defaultZoomProps,
18
- ...zoom,
19
- });
17
+ function validateMinZoom(minZoom) {
18
+ return Math.min(Math.max(minZoom, Number.EPSILON), 1);
19
+ }
20
+ function resolveZoomProps(zoom) {
21
+ const { minZoom, ...rest } = { ...defaultZoomProps, ...zoom };
22
+ return { minZoom: validateMinZoom(minZoom), ...rest };
23
+ }
20
24
 
21
25
  function useZoomAnimation(zoom, offsetX, offsetY, zoomWrapperRef) {
22
26
  const zoomAnimation = React.useRef(undefined);
@@ -118,7 +122,7 @@ function distance(pointerA, pointerB) {
118
122
  function scaleZoom(value, delta, factor = 100, clamp = 2) {
119
123
  return value * Math.min(1 + Math.abs(delta / factor), clamp) ** Math.sign(delta);
120
124
  }
121
- function useZoomSensors(zoom, maxZoom, disabled, changeZoom, changeOffsets, zoomWrapperRef) {
125
+ function useZoomSensors(zoom, minZoom, maxZoom, disabled, zoomIn, zoomOut, changeZoom, changeOffsets, zoomWrapperRef) {
122
126
  const activePointers = React.useRef([]);
123
127
  const lastPointerDown = React.useRef(0);
124
128
  const pinchZoomDistance = React.useRef(undefined);
@@ -160,18 +164,17 @@ function useZoomSensors(zoom, maxZoom, disabled, changeZoom, changeOffsets, zoom
160
164
  move(keyboardMoveDistance, 0);
161
165
  }
162
166
  }
163
- const handleChangeZoom = (zoomValue) => {
164
- preventDefault();
165
- changeZoom(zoomValue);
166
- };
167
167
  if (key === "+" || (meta && key === "=")) {
168
- handleChangeZoom(zoom * zoomInMultiplier);
168
+ preventDefault();
169
+ zoomIn();
169
170
  }
170
171
  else if (key === "-" || (meta && key === "_")) {
171
- handleChangeZoom(zoom / zoomInMultiplier);
172
+ preventDefault();
173
+ zoomOut();
172
174
  }
173
175
  else if (meta && key === "0") {
174
- handleChangeZoom(1);
176
+ preventDefault();
177
+ changeZoom(1);
175
178
  }
176
179
  });
177
180
  const onWheel = useEventCallback((event) => {
@@ -212,7 +215,14 @@ function useZoomSensors(zoom, maxZoom, disabled, changeZoom, changeOffsets, zoom
212
215
  if (pointers.length === 0 &&
213
216
  timeStamp - lastPointerDown.current < (event.pointerType === "touch" ? doubleTapDelay : doubleClickDelay)) {
214
217
  lastPointerDown.current = 0;
215
- changeZoom(zoom !== maxZoom ? zoom * Math.max(maxZoom ** (1 / doubleClickMaxStops), zoomInMultiplier) : 1, false, ...translateCoordinates(event));
218
+ const targetZoom = zoom >= 1
219
+ ? zoom !== maxZoom
220
+ ? zoom * Math.max(maxZoom ** (1 / doubleClickMaxStops), zoomInMultiplier)
221
+ : 1
222
+ : zoom !== minZoom
223
+ ? zoom / Math.max(minZoom ** (-1 / doubleClickMaxStops), zoomInMultiplier)
224
+ : 1;
225
+ changeZoom(targetZoom, false, ...translateCoordinates(event));
216
226
  }
217
227
  else {
218
228
  lastPointerDown.current = timeStamp;
@@ -278,7 +288,7 @@ function useZoomState(imageRect, maxZoom, zoomWrapperRef) {
278
288
  const animate = useZoomAnimation(zoom, offsetX, offsetY, zoomWrapperRef);
279
289
  const { currentSlide, globalIndex } = useLightboxState();
280
290
  const { containerRect, slideRect } = useController();
281
- const { zoomInMultiplier } = useZoomProps();
291
+ const { minZoom, zoomInMultiplier } = useZoomProps();
282
292
  const currentSource = currentSlide && isImageSlide(currentSlide) ? currentSlide.src : undefined;
283
293
  const disabled = !currentSource || !(zoomWrapperRef === null || zoomWrapperRef === void 0 ? void 0 : zoomWrapperRef.current);
284
294
  useLayoutEffect(() => {
@@ -296,7 +306,7 @@ function useZoomState(imageRect, maxZoom, zoomWrapperRef) {
296
306
  setOffsetY(Math.min(Math.abs(newOffsetY), Math.max(maxOffsetY, 0)) * Math.sign(newOffsetY));
297
307
  }, [zoom, offsetX, offsetY, slideRect, imageRect.width, imageRect.height]);
298
308
  const changeZoom = React.useCallback((targetZoom, rapid, dx, dy) => {
299
- const newZoom = round(Math.min(Math.max(targetZoom + 0.001 < maxZoom ? targetZoom : maxZoom, 1), maxZoom), 5);
309
+ const newZoom = round(Math.min(Math.max(targetZoom + 0.001 < maxZoom ? targetZoom : maxZoom, minZoom), maxZoom), 5);
300
310
  if (newZoom === zoom)
301
311
  return;
302
312
  if (!rapid) {
@@ -304,7 +314,7 @@ function useZoomState(imageRect, maxZoom, zoomWrapperRef) {
304
314
  }
305
315
  changeOffsets(dx ? dx * (1 / zoom - 1 / newZoom) : 0, dy ? dy * (1 / zoom - 1 / newZoom) : 0, newZoom);
306
316
  setZoom(newZoom);
307
- }, [zoom, maxZoom, changeOffsets, animate]);
317
+ }, [zoom, minZoom, maxZoom, changeOffsets, animate]);
308
318
  const handleControllerRectChange = useEventCallback(() => {
309
319
  if (zoom > 1) {
310
320
  if (zoom > maxZoom) {
@@ -314,8 +324,14 @@ function useZoomState(imageRect, maxZoom, zoomWrapperRef) {
314
324
  }
315
325
  });
316
326
  useLayoutEffect(handleControllerRectChange, [containerRect.width, containerRect.height, handleControllerRectChange]);
317
- const zoomIn = React.useCallback(() => changeZoom(zoom * zoomInMultiplier), [zoom, zoomInMultiplier, changeZoom]);
318
- const zoomOut = React.useCallback(() => changeZoom(zoom / zoomInMultiplier), [zoom, zoomInMultiplier, changeZoom]);
327
+ const zoomIn = React.useCallback(() => {
328
+ const targetZoom = zoom * zoomInMultiplier;
329
+ changeZoom(zoom < 1 && targetZoom > 1 ? 1 : targetZoom);
330
+ }, [zoom, zoomInMultiplier, changeZoom]);
331
+ const zoomOut = React.useCallback(() => {
332
+ const targetZoom = zoom / zoomInMultiplier;
333
+ changeZoom(zoom > 1 && targetZoom < 1 ? 1 : targetZoom);
334
+ }, [zoom, zoomInMultiplier, changeZoom]);
319
335
  return { zoom, offsetX, offsetY, disabled, changeOffsets, changeZoom, zoomIn, zoomOut };
320
336
  }
321
337
 
@@ -324,12 +340,13 @@ const useZoom = makeUseContext("useZoom", "ZoomControllerContext", ZoomControlle
324
340
  function ZoomContextProvider({ children }) {
325
341
  const [zoomWrapper, setZoomWrapper] = React.useState();
326
342
  const { slideRect } = useController();
343
+ const { ref, minZoom } = useZoomProps();
327
344
  const { imageRect, maxZoom } = useZoomImageRect(slideRect, zoomWrapper === null || zoomWrapper === void 0 ? void 0 : zoomWrapper.imageDimensions);
328
345
  const { zoom, offsetX, offsetY, disabled, changeZoom, changeOffsets, zoomIn, zoomOut } = useZoomState(imageRect, maxZoom, zoomWrapper === null || zoomWrapper === void 0 ? void 0 : zoomWrapper.zoomWrapperRef);
329
346
  useZoomCallback(zoom, disabled);
330
- useZoomSensors(zoom, maxZoom, disabled, changeZoom, changeOffsets, zoomWrapper === null || zoomWrapper === void 0 ? void 0 : zoomWrapper.zoomWrapperRef);
331
- const zoomRef = React.useMemo(() => ({ zoom, maxZoom, offsetX, offsetY, disabled, zoomIn, zoomOut, changeZoom }), [zoom, maxZoom, offsetX, offsetY, disabled, zoomIn, zoomOut, changeZoom]);
332
- React.useImperativeHandle(useZoomProps().ref, () => zoomRef, [zoomRef]);
347
+ useZoomSensors(zoom, minZoom, maxZoom, disabled, zoomIn, zoomOut, changeZoom, changeOffsets, zoomWrapper === null || zoomWrapper === void 0 ? void 0 : zoomWrapper.zoomWrapperRef);
348
+ const zoomRef = React.useMemo(() => ({ zoom, minZoom, maxZoom, offsetX, offsetY, disabled, zoomIn, zoomOut, changeZoom }), [zoom, minZoom, maxZoom, offsetX, offsetY, disabled, zoomIn, zoomOut, changeZoom]);
349
+ React.useImperativeHandle(ref, () => zoomRef, [zoomRef]);
333
350
  const context = React.useMemo(() => ({ ...zoomRef, setZoomWrapper }), [zoomRef, setZoomWrapper]);
334
351
  return React.createElement(ZoomControllerContext.Provider, { value: context }, children);
335
352
  }
@@ -341,9 +358,9 @@ const ZoomOutIcon = createIcon("ZoomOut", React.createElement("path", { d: "M15.
341
358
  const ZoomButton = React.forwardRef(function ZoomButton({ zoomIn, onLoseFocus }, ref) {
342
359
  const wasEnabled = React.useRef(false);
343
360
  const wasFocused = React.useRef(false);
344
- const { zoom, maxZoom, zoomIn: zoomInCallback, zoomOut: zoomOutCallback, disabled: zoomDisabled } = useZoom();
361
+ const { zoom, minZoom, maxZoom, zoomIn: zoomInCallback, zoomOut: zoomOutCallback, disabled: zoomDisabled, } = useZoom();
345
362
  const { render } = useLightboxProps();
346
- const disabled = zoomDisabled || (zoomIn ? zoom >= maxZoom : zoom <= 1);
363
+ const disabled = zoomDisabled || (zoomIn ? zoom >= maxZoom : zoom <= minZoom);
347
364
  React.useEffect(() => {
348
365
  if (disabled && wasEnabled.current && wasFocused.current) {
349
366
  onLoseFocus();
package/dist/types.d.ts CHANGED
@@ -229,7 +229,7 @@ interface ControllerSettings {
229
229
  focus: boolean;
230
230
  /** @deprecated for internal use only */
231
231
  touchAction: "none" | "pan-y";
232
- /** if `true`, set ARIA attributes on the controller div */
232
+ /** @deprecated for internal use only */
233
233
  aria: boolean;
234
234
  /** if `true`, close the lightbox on pull-up gesture */
235
235
  closeOnPullUp: boolean;
@@ -378,11 +378,30 @@ interface Callbacks {
378
378
  /** a callback called when the portal closes */
379
379
  exited?: Callback;
380
380
  }
381
- /** Custom UI labels / translations */
381
+ /** Custom UI labels / translations / localization */
382
382
  interface Labels {
383
+ /** `Previous` button title */
383
384
  Previous?: string;
385
+ /** `Next` button title */
384
386
  Next?: string;
387
+ /** `Close` button title */
385
388
  Close?: string;
389
+ /** Slide ARIA role description */
390
+ Slide?: string;
391
+ /** Carousel ARIA role description */
392
+ Carousel?: string;
393
+ /** Lightbox ARIA label */
394
+ Lightbox?: string;
395
+ /** Carousel ARIA label */
396
+ "Photo gallery"?: string;
397
+ /**
398
+ * Slide ARIA label
399
+ *
400
+ * The value is a template string supporting the following placeholders:
401
+ * - {index} - current slide index
402
+ * - {total} - total number of slides
403
+ */
404
+ "{index} of {total}"?: string;
386
405
  }
387
406
  type Label = keyof Labels;
388
407
  /** Toolbar settings */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yet-another-react-lightbox",
3
- "version": "3.23.4",
3
+ "version": "3.25.0",
4
4
  "description": "Modern React lightbox component",
5
5
  "author": "Igor Danchenko",
6
6
  "license": "MIT",