Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions docs/plugins/zoom.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ Zoom plugin adds the following `Lightbox` properties.
<td>
&#123;<br />
&nbsp;&nbsp;ref?: React.ForwardedRef&#8203;&lt;ZoomRef&gt;;<br />
&nbsp;&nbsp;minZoom?: number;<br />
&nbsp;&nbsp;maxZoomPixelRatio?: number;<br />
&nbsp;&nbsp;zoomInMultiplier?: number;<br />
&nbsp;&nbsp;doubleTapDelay?: number;<br />
Expand All @@ -122,6 +123,7 @@ Zoom plugin adds the following `Lightbox` properties.
<p>Zoom plugin settings:</p>
<ul>
<li>`ref` - Zoom plugin ref. See [Zoom Ref](#ZoomRef) for details.</li>
<li>`minZoom` - override minimum zoom level (default: 1.0)</li>
<li>`maxZoomPixelRatio` - ratio of image pixels to physical pixels at maximum zoom level</li>
<li>`zoomInMultiplier` - zoom-in multiplier</li>
<li>`doubleTapDelay` - double-tap maximum time delay (deprecated)</li>
Expand All @@ -133,9 +135,9 @@ Zoom plugin adds the following `Lightbox` properties.
<li>`scrollToZoom` - if `true`, enables image zoom via scroll gestures for mouse and trackpad users</li>
</ul>
<p>
Default: <span class="font-mono">&#123; maxZoomPixelRatio: 1, zoomInMultiplier: 2, doubleTapDelay: 300, doubleClickDelay: 500,
doubleClickMaxStops: 2, keyboardMoveDistance: 50, wheelZoomDistanceFactor: 100, pinchZoomDistanceFactor:
100, scrollToZoom: false &#125;</span>
Default: <span class="font-mono">&#123; minZoom: 1, maxZoomPixelRatio: 1, zoomInMultiplier: 2,
doubleTapDelay: 300, doubleClickDelay: 500, doubleClickMaxStops: 2, keyboardMoveDistance: 50,
wheelZoomDistanceFactor: 100, pinchZoomDistanceFactor: 100, scrollToZoom: false &#125;</span>
</p>
</td>
</tr>
Expand Down
11 changes: 9 additions & 2 deletions src/plugins/zoom/ZoomButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,17 @@ export const ZoomButton = React.forwardRef<HTMLButtonElement, ZoomButtonProps>(f
const wasEnabled = React.useRef(false);
const wasFocused = React.useRef(false);

const { zoom, maxZoom, zoomIn: zoomInCallback, zoomOut: zoomOutCallback, disabled: zoomDisabled } = useZoom();
const {
zoom,
minZoom,
maxZoom,
zoomIn: zoomInCallback,
zoomOut: zoomOutCallback,
disabled: zoomDisabled,
} = useZoom();
const { render } = useLightboxProps();

const disabled = zoomDisabled || (zoomIn ? zoom >= maxZoom : zoom <= 1);
const disabled = zoomDisabled || (zoomIn ? zoom >= maxZoom : zoom <= minZoom);

React.useEffect(() => {
if (disabled && wasEnabled.current && wasFocused.current) {
Expand Down
19 changes: 15 additions & 4 deletions src/plugins/zoom/ZoomController.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export function ZoomContextProvider({ children }: ComponentProps) {
const [zoomWrapper, setZoomWrapper] = React.useState<ActiveZoomWrapper>();

const { slideRect } = useController();
const { ref, minZoom } = useZoomProps();
const { imageRect, maxZoom } = useZoomImageRect(slideRect, zoomWrapper?.imageDimensions);

const { zoom, offsetX, offsetY, disabled, changeZoom, changeOffsets, zoomIn, zoomOut } = useZoomState(
Expand All @@ -30,14 +31,24 @@ export function ZoomContextProvider({ children }: ComponentProps) {

useZoomCallback(zoom, disabled);

useZoomSensors(zoom, maxZoom, disabled, changeZoom, changeOffsets, zoomWrapper?.zoomWrapperRef);
useZoomSensors(
zoom,
minZoom,
maxZoom,
disabled,
zoomIn,
zoomOut,
changeZoom,
changeOffsets,
zoomWrapper?.zoomWrapperRef,
);

const zoomRef = React.useMemo(
() => ({ zoom, maxZoom, offsetX, offsetY, disabled, zoomIn, zoomOut, changeZoom }),
[zoom, maxZoom, offsetX, offsetY, disabled, zoomIn, zoomOut, changeZoom],
() => ({ zoom, minZoom, maxZoom, offsetX, offsetY, disabled, zoomIn, zoomOut, changeZoom }),
[zoom, minZoom, maxZoom, offsetX, offsetY, disabled, zoomIn, zoomOut, changeZoom],
);

React.useImperativeHandle(useZoomProps().ref, () => zoomRef, [zoomRef]);
React.useImperativeHandle(ref, () => zoomRef, [zoomRef]);

const context = React.useMemo(() => ({ ...zoomRef, setZoomWrapper }), [zoomRef, setZoomWrapper]);

Expand Down
33 changes: 20 additions & 13 deletions src/plugins/zoom/hooks/useZoomSensors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@ function scaleZoom(value: number, delta: number, factor = 100, clamp = 2) {

export function useZoomSensors(
zoom: number,
minZoom: number,
maxZoom: number,
disabled: boolean,
zoomIn: ReturnType<typeof useZoomState>["zoomIn"],
zoomOut: ReturnType<typeof useZoomState>["zoomOut"],
changeZoom: ReturnType<typeof useZoomState>["changeZoom"],
changeOffsets: ReturnType<typeof useZoomState>["changeOffsets"],
zoomWrapperRef?: React.RefObject<HTMLDivElement | null>,
Expand Down Expand Up @@ -86,17 +89,15 @@ export function useZoomSensors(
}
}

const handleChangeZoom = (zoomValue: number) => {
preventDefault();
changeZoom(zoomValue);
};

if (key === "+" || (meta && key === "=")) {
handleChangeZoom(zoom * zoomInMultiplier);
preventDefault();
zoomIn();
} else if (key === "-" || (meta && key === "_")) {
handleChangeZoom(zoom / zoomInMultiplier);
preventDefault();
zoomOut();
} else if (meta && key === "0") {
handleChangeZoom(1);
preventDefault();
changeZoom(1);
}
});

Expand Down Expand Up @@ -156,11 +157,17 @@ export function useZoomSensors(
timeStamp - lastPointerDown.current < (event.pointerType === "touch" ? doubleTapDelay : doubleClickDelay)
) {
lastPointerDown.current = 0;
changeZoom(
zoom !== maxZoom ? zoom * Math.max(maxZoom ** (1 / doubleClickMaxStops), zoomInMultiplier) : 1,
false,
...translateCoordinates(event),
);

const targetZoom =
zoom >= 1
? zoom !== maxZoom
? zoom * Math.max(maxZoom ** (1 / doubleClickMaxStops), zoomInMultiplier)
: 1
: zoom !== minZoom
? zoom / Math.max(minZoom ** (-1 / doubleClickMaxStops), zoomInMultiplier)
: 1;

changeZoom(targetZoom, false, ...translateCoordinates(event));
} else {
lastPointerDown.current = timeStamp;
}
Expand Down
19 changes: 14 additions & 5 deletions src/plugins/zoom/hooks/useZoomState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function useZoomState(

const { currentSlide, globalIndex } = useLightboxState();
const { containerRect, slideRect } = useController();
const { zoomInMultiplier } = useZoomProps();
const { minZoom, zoomInMultiplier } = useZoomProps();

const currentSource = currentSlide && isImageSlide(currentSlide) ? currentSlide.src : undefined;
const disabled = !currentSource || !zoomWrapperRef?.current;
Expand Down Expand Up @@ -54,7 +54,10 @@ export function useZoomState(

const changeZoom = React.useCallback(
(targetZoom: number, rapid?: boolean, dx?: number, dy?: number) => {
const newZoom = round(Math.min(Math.max(targetZoom + 0.001 < maxZoom ? targetZoom : maxZoom, 1), maxZoom), 5);
const newZoom = round(
Math.min(Math.max(targetZoom + 0.001 < maxZoom ? targetZoom : maxZoom, minZoom), maxZoom),
5,
);

if (newZoom === zoom) return;

Expand All @@ -66,7 +69,7 @@ export function useZoomState(

setZoom(newZoom);
},
[zoom, maxZoom, changeOffsets, animate],
[zoom, minZoom, maxZoom, changeOffsets, animate],
);

const handleControllerRectChange = useEventCallback(() => {
Expand All @@ -81,9 +84,15 @@ export function useZoomState(

useLayoutEffect(handleControllerRectChange, [containerRect.width, containerRect.height, handleControllerRectChange]);

const zoomIn = React.useCallback(() => changeZoom(zoom * zoomInMultiplier), [zoom, zoomInMultiplier, changeZoom]);
const zoomIn = React.useCallback(() => {
const targetZoom = zoom * zoomInMultiplier;
changeZoom(zoom < 1 && targetZoom > 1 ? 1 : targetZoom);
}, [zoom, zoomInMultiplier, changeZoom]);

const zoomOut = React.useCallback(() => changeZoom(zoom / zoomInMultiplier), [zoom, zoomInMultiplier, changeZoom]);
const zoomOut = React.useCallback(() => {
const targetZoom = zoom / zoomInMultiplier;
changeZoom(zoom > 1 && targetZoom < 1 ? 1 : targetZoom);
}, [zoom, zoomInMultiplier, changeZoom]);

return { zoom, offsetX, offsetY, disabled, changeOffsets, changeZoom, zoomIn, zoomOut };
}
4 changes: 4 additions & 0 deletions src/plugins/zoom/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ declare module "../../types.js" {
zoom?: {
/** Zoom plugin ref */
ref?: React.ForwardedRef<ZoomRef>;
/** override minimum zoom level (default: 1.0) */
minZoom?: number;
/** ratio of image pixels to physical pixels at maximum zoom level */
maxZoomPixelRatio?: number;
/** zoom-in multiplier */
Expand Down Expand Up @@ -81,6 +83,8 @@ declare module "../../types.js" {
interface ZoomRef {
/** current zoom level */
zoom: number;
/** minimum zoom level */
minZoom: number;
/** maximum zoom level */
maxZoom: number;
/** horizontal offset */
Expand Down
13 changes: 9 additions & 4 deletions src/plugins/zoom/props.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { LightboxProps } from "../../index.js";

export const defaultZoomProps = {
minZoom: 1,
maxZoomPixelRatio: 1,
zoomInMultiplier: 2,
doubleTapDelay: 300,
Expand All @@ -12,7 +13,11 @@ export const defaultZoomProps = {
scrollToZoom: false,
};

export const resolveZoomProps = (zoom: LightboxProps["zoom"]) => ({
...defaultZoomProps,
...zoom,
});
function validateMinZoom(minZoom: number) {
return Math.min(Math.max(minZoom, Number.EPSILON), 1);
}

export function resolveZoomProps(zoom: LightboxProps["zoom"]) {
const { minZoom, ...rest } = { ...defaultZoomProps, ...zoom };
return { minZoom: validateMinZoom(minZoom), ...rest };
}
Loading