/**
* ImageViewer — a picture that opens out of the page to fill the screen.
*
* Pressing a trigger lifts its image from where it sits, grows it to the whole
* picture in the middle of the screen, and blurs the page behind it. From there
* it can be pinched and double-tapped to zoom, swiped sideways to the next
* image under the same root, and dragged away to put it back.
*
* ```tsx
*
* {photos.map((photo) => (
*
* ))}
*
* ```
*
* ## The image travels; nothing appears
*
* The viewer never fades a second copy of the picture in over the first. The
* thumbnail is hidden and the same image is drawn in a frame that starts at the
* thumbnail's exact rectangle and corner radius and ends at the fitted one. The
* picture inside that frame is always drawn at the size that *covers* it, so the
* crop a thumbnail shows opens out into the whole image as the frame grows — and
* closes back into the crop on the way home. That continuity is the point: the
* picture on screen is the one that was pressed, not a picture of it.
*
* The frame animates its bounds rather than a transform, because a transform
* cannot change a crop. It is one view with one child, so the layout pass it
* costs per frame is small.
*
* ## Every trigger under one root is a page
*
* Triggers register themselves with the root as they mount, in render order or
* by an explicit `index`. Opening one opens the gallery at its page, and the
* thumbnail hidden underneath follows the page — close on the third image and it
* flies back into the third thumbnail.
*
* ## One gesture surface, decided at the start
*
* Pinching, panning a zoomed image, turning the page and dragging to dismiss all
* begin as a one- or two-finger drag, and which one it is gets decided the
* moment it starts: two fingers zoom, one finger on a zoomed image pans it, one
* finger moving sideways across a gallery turns the page, and anything else
* drags the image away. Deciding once keeps a drag from changing its mind
* halfway, which is what makes a viewer feel loose.
*/
import { type ReactNode } from 'react';
import { type ImageSourcePropType, type PressableProps } from 'react-native';
export interface ImageViewerProps {
children?: ReactNode;
/** Controlled open state. */
open?: boolean;
/** Initial open state when uncontrolled. */
defaultOpen?: boolean;
onOpenChange?: (open: boolean) => void;
/** Controlled page — which trigger's image is showing, in page order. */
index?: number;
/** Initial page when uncontrolled. */
defaultIndex?: number;
onIndexChange?: (index: number) => void;
/**
* Blur the page behind the picture. Needs the optional `expo-blur`, and dims
* instead without it. Reduce Transparency draws an opaque backdrop.
*/
blur?: boolean;
/** How far a pinch can zoom in, as a multiple of the fitted size. */
maxScale?: number;
/** Where a double tap zooms to, as a multiple of the fitted size. */
doubleTapScale?: number;
/** Tick as a drag to dismiss begins. Needs the optional `expo-haptics`. */
haptics?: boolean;
/** Draw the close button. Tapping outside the picture and dragging it away still close it. */
showClose?: boolean;
/** Read out for the close button. */
closeLabel?: string;
/**
* Space kept between the open picture and the edges of the screen, in points.
* Measured from the safe area at the top and bottom, and the larger of the
* two is used for both, so the picture stays centred. `0` fills the screen.
*/
inset?: number;
/**
* Corner radius of the open picture, in points. The corners stay this size on
* screen while the picture is zoomed or dragged. `0` squares them.
*/
cornerRadius?: number;
}
declare function ImageViewerRoot({ children, open, defaultOpen, onOpenChange, index, defaultIndex, onIndexChange, blur, maxScale, doubleTapScale, haptics, showClose, closeLabel, inset, cornerRadius, }: ImageViewerProps): import("react").JSX.Element;
export interface ImageViewerTriggerProps extends Omit {
/**
* Classes on the pressable. Without `children` the image fills it, so give it
* a size — `h-48 w-full`, `aspect-square flex-1`.
*/
className?: string;
/** The picture. Shown in the page and, until `fullSource` loads, in the viewer. */
source: ImageSourcePropType;
/**
* A larger copy to show once the viewer is open. It loads when the viewer
* opens and replaces `source` when it arrives, so a feed can carry thumbnails.
*/
fullSource?: ImageSourcePropType;
/** Described for a screen reader, on the trigger and in the viewer. */
alt?: string;
/** Shown under the picture while it is open. A string is set as text. */
caption?: ReactNode;
/** Page order within the root. Render order when left out. */
index?: number;
/**
* The corner radius the picture has in the page, in points, so the flight
* starts from the same shape. Match it to the `rounded-*` class you gave the
* trigger — a class cannot be read back.
*/
radius?: number;
/**
* The image's own width and height, when known. Saves looking it up, and is
* the only way the viewer knows the proportions before a remote image loads.
*/
width?: number;
height?: number;
/** Nothing opens, and the press is not reported. */
disabled?: boolean;
/**
* Draw the picture yourself — a `Post.Media`, a card. It should show `source`
* cropped to fill, the way the viewer's own flight starts. Left out, the
* trigger draws `source` to fill its box.
*/
children?: ReactNode;
}
/**
* The thing pressed to open the viewer.
*
* It registers its picture with the root, so the gallery is simply every
* trigger under that root. While its picture is out on the screen it is drawn
* transparent — not removed, so the page does not reflow under the blur and the
* flight home has somewhere to land.
*/
declare function ImageViewerTrigger({ className, source, fullSource, alt, caption, index, radius, width, height, disabled, children, onPress, onLayout, ...props }: ImageViewerTriggerProps): import("react").JSX.Element;
export declare const ImageViewer: typeof ImageViewerRoot & {
Trigger: typeof ImageViewerTrigger;
};
export {};
//# sourceMappingURL=index.d.ts.map