diff --git a/apps/web/src/components/chat/ExpandedImageDialog.tsx b/apps/web/src/components/chat/ExpandedImageDialog.tsx
index 10031b48cde..75a117b97f8 100644
--- a/apps/web/src/components/chat/ExpandedImageDialog.tsx
+++ b/apps/web/src/components/chat/ExpandedImageDialog.tsx
@@ -1,4 +1,4 @@
-import { memo, useCallback, useEffect, useState } from "react";
+import { useEffect, useEffectEvent, useState } from "react";
import { ChevronLeftIcon, ChevronRightIcon, XIcon } from "lucide-react";
import { Button } from "../ui/button";
import type { ExpandedImagePreview } from "./ExpandedImagePreview";
@@ -8,52 +8,78 @@ interface ExpandedImageDialogProps {
onClose: () => void;
}
-export const ExpandedImageDialog = memo(function ExpandedImageDialog({
- preview: initialPreview,
- onClose,
-}: ExpandedImageDialogProps) {
- const [preview, setPreview] = useState(initialPreview);
-
- // Sync when the parent hands us a new preview reference.
- useEffect(() => {
- setPreview(initialPreview);
- }, [initialPreview]);
+export function ExpandedImageDialog({ preview, onClose }: ExpandedImageDialogProps) {
+ return (
+
+ );
+}
- const navigateImage = useCallback((direction: -1 | 1) => {
- setPreview((existing) => {
- if (existing.images.length <= 1) return existing;
- const nextIndex =
- (existing.index + direction + existing.images.length) % existing.images.length;
- if (nextIndex === existing.index) return existing;
- return { ...existing, index: nextIndex };
- });
- }, []);
+function getPreviewResetKey(preview: ExpandedImagePreview): string {
+ return [
+ preview.index,
+ preview.images.length,
+ ...preview.images.map((image) => `${image.src}\u0000${image.name}`),
+ ].join("\u0001");
+}
- useEffect(() => {
- const onKeyDown = (event: globalThis.KeyboardEvent) => {
- if (event.key === "Escape") {
- event.preventDefault();
- event.stopPropagation();
- onClose();
- return;
- }
- if (preview.images.length <= 1) return;
- if (event.key === "ArrowLeft") {
- event.preventDefault();
- event.stopPropagation();
- navigateImage(-1);
- return;
- }
- if (event.key !== "ArrowRight") return;
+function useExpandedImageKeyboardShortcuts(input: {
+ readonly canNavigate: boolean;
+ readonly onClose: () => void;
+ readonly onNext: () => void;
+ readonly onPrevious: () => void;
+}) {
+ const handleKeyDown = useEffectEvent((event: globalThis.KeyboardEvent) => {
+ if (event.key === "Escape") {
+ event.preventDefault();
+ event.stopPropagation();
+ input.onClose();
+ return;
+ }
+ if (!input.canNavigate) return;
+ if (event.key === "ArrowLeft") {
event.preventDefault();
event.stopPropagation();
- navigateImage(1);
- };
+ input.onPrevious();
+ return;
+ }
+ if (event.key !== "ArrowRight") return;
+ event.preventDefault();
+ event.stopPropagation();
+ input.onNext();
+ });
+
+ useEffect(() => {
+ const onKeyDown = (event: globalThis.KeyboardEvent) => handleKeyDown(event);
window.addEventListener("keydown", onKeyDown);
return () => window.removeEventListener("keydown", onKeyDown);
- }, [navigateImage, onClose, preview.images.length]);
+ }, []);
+}
- const item = preview.images[preview.index];
+function ExpandedImageDialogContent({ preview, onClose }: ExpandedImageDialogProps) {
+ const [indexOffset, setIndexOffset] = useState(0);
+ const images = preview.images;
+ const canNavigate = images.length > 1;
+ const currentIndex = canNavigate
+ ? (preview.index + indexOffset + images.length) % images.length
+ : preview.index;
+
+ const navigateImage = (direction: -1 | 1) => {
+ if (!canNavigate) return;
+ setIndexOffset((existingOffset) => existingOffset + direction);
+ };
+
+ useExpandedImageKeyboardShortcuts({
+ canNavigate,
+ onClose,
+ onNext: () => navigateImage(1),
+ onPrevious: () => navigateImage(-1),
+ });
+
+ const item = images[currentIndex];
if (!item) return null;
return (
@@ -69,7 +95,7 @@ export const ExpandedImageDialog = memo(function ExpandedImageDialog({
aria-label="Close image preview"
onClick={onClose}
/>
- {preview.images.length > 1 && (
+ {canNavigate && (
{item.name}
- {preview.images.length > 1 ? ` (${preview.index + 1}/${preview.images.length})` : ""}
+ {canNavigate ? ` (${currentIndex + 1}/${images.length})` : ""}
- {preview.images.length > 1 && (
+ {canNavigate && (