Skip to content

Commit

Permalink
fix: transfer focus from offscreen slides
Browse files Browse the repository at this point in the history
  • Loading branch information
igordanchenko committed Sep 12, 2024
1 parent 0dfd33f commit 100ff49
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 32 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Lightweight React lightbox component. This is a trimmed-down version of the
[yet-another-react-lightbox](https://github.com/igordanchenko/yet-another-react-lightbox)
that provides essential lightbox features and slick UX with just 4.4KB bundle
that provides essential lightbox features and slick UX with just 4.6KB bundle
size.

## Overview
Expand Down
80 changes: 49 additions & 31 deletions src/components/Carousel.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,52 @@
import { useEffect, useRef } from "react";

import ImageSlide from "./ImageSlide";
import { useZoom, useZoomInternal } from "./Zoom";
import { useLightboxContext } from "./LightboxContext";
import ImageSlide from "./ImageSlide";
import { cssClass, isImageSlide, round } from "../utils";
import { SlideImage } from "../types";
import { RenderSlideProps, SlideImage } from "../types";

function CarouselSlide({ slide, rect, current }: Pick<RenderSlideProps, "slide" | "rect" | "current">) {
const ref = useRef<HTMLDivElement | null>(null);

const { zoom, offsetX, offsetY } = useZoom();
const { styles, render: { slide: renderSlide, slideHeader, slideFooter } = {} } = useLightboxContext();

useEffect(() => {
if (!current && ref.current?.contains(document.activeElement)) {
ref.current.closest<HTMLElement>('[tabindex="-1"]')?.focus();
}
}, [current]);

const context = { slide, rect, current, zoom: round(current ? zoom : 1, 3) };

return (
<div
ref={ref}
role="group"
aria-roledescription="slide"
className={cssClass("slide")}
hidden={!current}
style={{
transform:
current && zoom > 1
? `translateX(${round(offsetX, 3)}px) translateY(${round(offsetY, 3)}px) scale(${round(zoom, 3)})`
: undefined,
...styles?.slide,
}}
>
{slideHeader?.(context)}
{renderSlide?.(context) ??
(isImageSlide(slide) && <ImageSlide {...(context as typeof context & { slide: SlideImage })} />)}
{slideFooter?.(context)}
</div>
);
}

export default function Carousel() {
const {
slides,
index,
styles,
carousel: { preload = 2 } = {},
render: { slide: renderSlide, slideHeader, slideFooter } = {},
} = useLightboxContext();
const { rect, zoom, offsetX, offsetY } = useZoom();
const { slides, index, styles, carousel: { preload = 2 } = {} } = useLightboxContext();
const { setCarouselRef } = useZoomInternal();
const { rect } = useZoom();

return (
<div ref={setCarouselRef} style={styles?.carousel} className={cssClass("carousel")}>
Expand All @@ -24,29 +57,14 @@ export default function Carousel() {
if (slideIndex < 0 || slideIndex >= slides.length) return null;

const slide = slides[slideIndex];
const current = slideIndex === index;
const context = { slide, rect, current, zoom: round(current ? zoom : 1, 3) };

return (
<div
key={slide.key ?? `${slideIndex}-${isImageSlide(slide) ? slide.src : undefined}`}
role="group"
aria-roledescription="slide"
className={cssClass("slide")}
hidden={!current}
style={{
transform:
current && zoom > 1
? `translateX(${round(offsetX, 3)}px) translateY(${round(offsetY, 3)}px) scale(${round(zoom, 3)})`
: undefined,
...styles?.slide,
}}
>
{slideHeader?.(context)}
{renderSlide?.(context) ??
(isImageSlide(slide) && <ImageSlide {...(context as typeof context & { slide: SlideImage })} />)}
{slideFooter?.(context)}
</div>
<CarouselSlide
key={slide.key ?? [`${slideIndex}`, isImageSlide(slide) && slide.src].filter(Boolean).join("|")}
rect={rect}
slide={slide}
current={slideIndex === index}
/>
);
})}
</div>
Expand Down
17 changes: 17 additions & 0 deletions test/Lightbox.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
expectToBeZoomedIn,
expectToBeZoomedOut,
getCloseButton,
getController,
getCurrentSlide,
getCurrentSlideImage,
getNextButton,
Expand Down Expand Up @@ -438,4 +439,20 @@ describe("Lightbox", () => {
renderLightbox({ carousel: { preload: 0 } });
expect(getSlidesCount()).toBe(1);
});

it("transfers focus from offscreen slides", () => {
const { getByTestId } = renderLightbox({
slides: [{ type: "custom-slide" }, ...slides],
render: {
slide: ({ slide }) => (slide.type === "custom-slide" ? <input data-testid="custom-slide-input" /> : undefined),
},
});

const target = getByTestId("custom-slide-input");
target.focus();
expect(document.activeElement).toBe(target);

clickButtonNext();
expect(document.activeElement).toBe(getController());
});
});

0 comments on commit 100ff49

Please sign in to comment.