Skip to content
Open
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
97 changes: 50 additions & 47 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -321,19 +321,14 @@ const ViewShotComponent = forwardRef<ViewShotRef, ViewShotProperties>(
} = props;

const rootRef = useRef<View | null>(null);
const rafRef = useRef<number | null>(null);
const lastCapturedURIRef = useRef<string | null>(null);
const resolveFirstLayoutRef = useRef<((layout: unknown) => void) | null>(
null,
);

const firstLayoutPromise = useMemo(
() =>
new Promise<unknown>(resolve => {
resolveFirstLayoutRef.current = resolve;
}),
[],
);
const firstLayout = useMemo(() => {
let resolve!: (layout: unknown) => void;
const promise = new Promise<unknown>(r => {
resolve = r;
});
return {promise, resolve: resolve as ((layout: unknown) => void) | null};
}, []);

// Keep latest props in refs so stable callbacks always see fresh values
const onCaptureRef = useRef(onCapture);
Expand All @@ -345,15 +340,18 @@ const ViewShotComponent = forwardRef<ViewShotRef, ViewShotProperties>(

const capture = useCallback(
(): Promise<string> =>
firstLayoutPromise
firstLayout.promise
.then(() => {
if (!rootRef.current) return neverEndingPromise as Promise<never>;
return captureRef(rootRef.current, optionsRef.current);
})
.then(
uri => {
if (!rootRef.current) return uri;
if (lastCapturedURIRef.current) {
if (
lastCapturedURIRef.current &&
lastCapturedURIRef.current !== uri
) {
setTimeout(releaseCapture, 500, lastCapturedURIRef.current);
}
lastCapturedURIRef.current = uri;
Expand All @@ -366,71 +364,76 @@ const ViewShotComponent = forwardRef<ViewShotRef, ViewShotProperties>(
throw e;
},
) as Promise<string>,
[firstLayoutPromise],
[firstLayout],
);

const setRootRef = useCallback(
(node: View | null): void => {
(node: View | null): void | (() => void) => {
rootRef.current = node;
if (node) (node as ViewShotRef).capture = capture;
if (typeof ref === "function") {
ref(node as ViewShotRef | null);
// ForwardedRef still types callback returns as void, even in React 19.
const cleanup: unknown = ref(node as ViewShotRef | null);
if (typeof cleanup === "function") {
return () => {
rootRef.current = null;
cleanup();
};
}
} else if (ref) {
ref.current = node as ViewShotRef | null;
}
},
[capture, ref],
);

const syncCaptureLoop = useCallback(
(mode: ViewShotProperties["captureMode"] | null): void => {
cancelAnimationFrame(rafRef.current as number);
if (mode === "continuous") {
let previousCaptureURI: string | null = "-";
const loop = (): void => {
rafRef.current = requestAnimationFrame(loop);
if (previousCaptureURI === lastCapturedURIRef.current) return;
previousCaptureURI = lastCapturedURIRef.current;
capture();
};
rafRef.current = requestAnimationFrame(loop);
}
},
[capture],
);

const didMountCapture = useRef(false);
useEffect(() => {
if (__DEV__) checkCompatibleProps(props);
if (captureMode === "mount") capture();
if (captureMode === "mount" && !didMountCapture.current) {
didMountCapture.current = true;
void capture().catch(() => {});
}
// Run once: re-firing on prop changes would double-trigger `mount`
// capture; the `[captureMode]` effect below handles loop sync, and the
// no-deps effect below handles `update` mode.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

useEffect(() => {
syncCaptureLoop(captureMode);
return () => syncCaptureLoop(null);
}, [captureMode, syncCaptureLoop]);
if (captureMode !== "continuous") return;
let active = true;
let frame: number;
const schedule = (): void => {
if (active) frame = requestAnimationFrame(loop);
};
const loop = (): void => {
void capture().then(schedule, schedule);
};
schedule();
return () => {
active = false;
cancelAnimationFrame(frame);
};
}, [captureMode, capture]);

const isFirstRender = useRef(true);
const previousProps = useRef(props);
useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false;
return;
if (previousProps.current !== props && captureMode === "update") {
void capture().catch(() => {});
}
if (captureMode === "update") capture();
previousProps.current = props;
});

const onLayoutHandler = useCallback(
(e: LayoutChangeEvent): void => {
if (resolveFirstLayoutRef.current) {
resolveFirstLayoutRef.current(e.nativeEvent.layout);
resolveFirstLayoutRef.current = null;
if (firstLayout.resolve) {
firstLayout.resolve(e.nativeEvent.layout);
firstLayout.resolve = null;
}
if (onLayout) onLayout(e);
},
[onLayout],
[firstLayout, onLayout],
);

return (
Expand Down
Loading