@djangocfg/ui-tools 2.1.206 → 2.1.208

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -36,7 +36,7 @@ This package contains heavy components that are loaded lazily to keep your initi
36
36
  | `AudioPlayer` | ~200KB | Audio player with WaveSurfer.js |
37
37
  | `VideoPlayer` | ~150KB | Professional video player with Vidstack |
38
38
  | `JsonTree` | ~100KB | JSON visualization with modes (full/compact/inline) |
39
- | `ImageViewer` | ~50KB | Image viewer with zoom/pan/rotate |
39
+ | `ImageViewer` | ~50KB | Image viewer with zoom/pan/rotate/flip and gallery navigation |
40
40
  | `CronScheduler` | ~15KB | Cron expression builder with intuitive UI |
41
41
 
42
42
  ## Tree-Shakeable Imports
@@ -559,6 +559,52 @@ import {
559
559
  } from '@djangocfg/ui-tools';
560
560
  ```
561
561
 
562
+ ## Image Viewer
563
+
564
+ Image viewer with zoom, pan, rotate, flip and gallery navigation.
565
+
566
+ ```tsx
567
+ import { ImageViewer } from '@djangocfg/ui-tools';
568
+
569
+ // Single image
570
+ <div className="w-full h-[500px]">
571
+ <ImageViewer
572
+ images={[{ file: { name: 'photo.jpg', path: '/images/photo.jpg' }, src: '/images/photo.jpg' }]}
573
+ />
574
+ </div>
575
+
576
+ // Gallery — pass multiple images, keyboard ←/→ navigation enabled automatically
577
+ <div className="w-full h-[500px]">
578
+ <ImageViewer
579
+ images={[
580
+ { file: { name: 'Photo 1', path: 'p1' }, src: 'https://example.com/1.jpg' },
581
+ { file: { name: 'Photo 2', path: 'p2' }, src: 'https://example.com/2.jpg' },
582
+ { file: { name: 'Photo 3', path: 'p3' }, src: 'https://example.com/3.jpg' },
583
+ ]}
584
+ initialIndex={0}
585
+ />
586
+ </div>
587
+ ```
588
+
589
+ ### Props
590
+
591
+ | Prop | Type | Default | Description |
592
+ |------|------|---------|-------------|
593
+ | `images` | `ImageItem[]` | required | Array of images to display |
594
+ | `initialIndex` | `number` | `0` | Index of the image to show first |
595
+ | `inDialog` | `boolean` | `false` | Hide expand button (for nested usage) |
596
+
597
+ ### Keyboard Shortcuts
598
+
599
+ | Key | Action |
600
+ |-----|--------|
601
+ | `+` / `=` | Zoom in |
602
+ | `-` | Zoom out |
603
+ | `0` | Reset to fit |
604
+ | `R` | Rotate 90° |
605
+ | `←` | Previous image (gallery) |
606
+ | `→` | Next image (gallery) |
607
+
562
608
  ## JSON Tree
563
609
 
564
610
  JSON visualization with three display modes:
@@ -1,10 +1,11 @@
1
1
  import { useImageCache, useMediaCacheStore, generateContentKey } from './chunk-5LBDYFWH.mjs';
2
2
  import { __name } from './chunk-CGILA3WO.mjs';
3
3
  import { useMemo, useState, useEffect, useCallback, useRef } from 'react';
4
- import { ZoomOut, ZoomIn, Maximize2, FlipHorizontal, FlipVertical, RotateCw, Expand, AlertCircle, ImageIcon } from 'lucide-react';
4
+ import { ZoomOut, ZoomIn, Maximize2, FlipHorizontal, FlipVertical, RotateCw, Expand, ImageIcon, AlertCircle, ChevronLeft, ChevronRight } from 'lucide-react';
5
5
  import { useControls, TransformWrapper, TransformComponent } from 'react-zoom-pan-pinch';
6
6
  import { Alert, AlertDescription, cn as cn$1, Dialog, DialogContent, DialogTitle } from '@djangocfg/ui-core';
7
7
  import { useTypedT } from '@djangocfg/i18n';
8
+ import { useHotkey } from '@djangocfg/ui-core/hooks';
8
9
  import { Button, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from '@djangocfg/ui-core/components';
9
10
  import { createMediaLogger, cn } from '@djangocfg/ui-core/lib';
10
11
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
@@ -368,8 +369,20 @@ function useImageLoading(options) {
368
369
  };
369
370
  }
370
371
  __name(useImageLoading, "useImageLoading");
371
- function ImageViewer({ file, content, src: directSrc, inDialog = false }) {
372
+ function ImageViewer({
373
+ images,
374
+ initialIndex = 0,
375
+ inDialog = false
376
+ }) {
372
377
  const t = useTypedT();
378
+ const [currentIndex, setCurrentIndex] = useState(
379
+ () => Math.max(0, Math.min(initialIndex, images.length - 1))
380
+ );
381
+ useEffect(() => {
382
+ setCurrentIndex(Math.max(0, Math.min(initialIndex, images.length - 1)));
383
+ }, [initialIndex, images.length]);
384
+ const current = images[currentIndex];
385
+ const hasMultiple = images.length > 1;
373
386
  const [scale, setScale] = useState(1);
374
387
  const [dialogOpen, setDialogOpen] = useState(false);
375
388
  const [loadError, setLoadError] = useState(false);
@@ -387,29 +400,34 @@ function ImageViewer({ file, content, src: directSrc, inDialog = false }) {
387
400
  useProgressiveLoading,
388
401
  error,
389
402
  hasContent
390
- } = useImageLoading({ content, mimeType: file.mimeType, src: directSrc });
403
+ } = useImageLoading({
404
+ content: current?.content ?? "",
405
+ mimeType: current?.file.mimeType,
406
+ src: current?.src
407
+ });
391
408
  useEffect(() => {
392
409
  setLoadError(false);
393
410
  }, [src]);
394
411
  const { transform, rotate, flipH, flipV, transformStyle } = useImageTransform({
395
- resetKey: file.path
412
+ resetKey: current?.file.path ?? ""
396
413
  });
397
414
  const handleZoomPreset = useCallback((value) => {
398
415
  if (!controlsRef.current) return;
399
- if (value === "fit") {
400
- controlsRef.current.resetTransform();
401
- } else {
402
- controlsRef.current.setTransform(0, 0, value);
403
- }
404
- }, []);
405
- const handleExpand = useCallback(() => {
406
- setDialogOpen(true);
416
+ if (value === "fit") controlsRef.current.resetTransform();
417
+ else controlsRef.current.setTransform(0, 0, value);
407
418
  }, []);
419
+ const handleExpand = useCallback(() => setDialogOpen(true), []);
420
+ const prev = useCallback(
421
+ () => setCurrentIndex((i) => (i - 1 + images.length) % images.length),
422
+ [images.length]
423
+ );
424
+ const next = useCallback(
425
+ () => setCurrentIndex((i) => (i + 1) % images.length),
426
+ [images.length]
427
+ );
408
428
  useEffect(() => {
409
429
  const handleKeyDown = /* @__PURE__ */ __name((e) => {
410
- if (!containerRef.current?.contains(document.activeElement) && document.activeElement !== containerRef.current) {
411
- return;
412
- }
430
+ if (!containerRef.current?.contains(document.activeElement) && document.activeElement !== containerRef.current) return;
413
431
  const controls = controlsRef.current;
414
432
  if (!controls) return;
415
433
  switch (e.key) {
@@ -437,6 +455,14 @@ function ImageViewer({ file, content, src: directSrc, inDialog = false }) {
437
455
  window.addEventListener("keydown", handleKeyDown);
438
456
  return () => window.removeEventListener("keydown", handleKeyDown);
439
457
  }, [rotate]);
458
+ useHotkey("ArrowLeft", prev, { enabled: hasMultiple, preventDefault: true });
459
+ useHotkey("ArrowRight", next, { enabled: hasMultiple, preventDefault: true });
460
+ if (!current) {
461
+ return /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col items-center justify-center gap-2 bg-muted/30", children: [
462
+ /* @__PURE__ */ jsx(ImageIcon, { className: "w-12 h-12 text-muted-foreground/50" }),
463
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: labels.noImage })
464
+ ] });
465
+ }
440
466
  if (error || loadError) {
441
467
  return /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col items-center justify-center gap-3 bg-muted/30 p-4", children: [
442
468
  /* @__PURE__ */ jsx(AlertCircle, { className: "w-12 h-12 text-destructive/70" }),
@@ -514,12 +540,7 @@ function ImageViewer({ file, content, src: directSrc, inDialog = false }) {
514
540
  alt: "",
515
541
  "aria-hidden": "true",
516
542
  className: "absolute inset-0 max-w-full max-h-full object-contain select-none",
517
- style: {
518
- transform: transformStyle,
519
- filter: "blur(20px)",
520
- transition: "opacity 0.3s ease-out",
521
- opacity: isFullyLoaded ? 0 : 1
522
- },
543
+ style: { transform: transformStyle, filter: "blur(20px)", transition: "opacity 0.3s ease-out", opacity: isFullyLoaded ? 0 : 1 },
523
544
  draggable: false
524
545
  }
525
546
  ),
@@ -527,7 +548,7 @@ function ImageViewer({ file, content, src: directSrc, inDialog = false }) {
527
548
  "img",
528
549
  {
529
550
  src,
530
- alt: file.name,
551
+ alt: current.file.name,
531
552
  className: "max-w-full max-h-full object-contain select-none",
532
553
  style: {
533
554
  transform: transformStyle,
@@ -545,10 +566,35 @@ function ImageViewer({ file, content, src: directSrc, inDialog = false }) {
545
566
  ]
546
567
  }
547
568
  ),
569
+ hasMultiple && /* @__PURE__ */ jsxs(Fragment, { children: [
570
+ /* @__PURE__ */ jsx(
571
+ "button",
572
+ {
573
+ type: "button",
574
+ onClick: prev,
575
+ className: "absolute left-2 top-1/2 -translate-y-1/2 z-10 bg-black/50 hover:bg-black/70 text-white rounded-full p-1.5 transition-colors",
576
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-5 w-5" })
577
+ }
578
+ ),
579
+ /* @__PURE__ */ jsx(
580
+ "button",
581
+ {
582
+ type: "button",
583
+ onClick: next,
584
+ className: "absolute right-2 top-1/2 -translate-y-1/2 z-10 bg-black/50 hover:bg-black/70 text-white rounded-full p-1.5 transition-colors",
585
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-5 w-5" })
586
+ }
587
+ ),
588
+ /* @__PURE__ */ jsxs("div", { className: "absolute bottom-2 left-1/2 -translate-x-1/2 z-10 bg-black/50 text-white text-xs px-2 py-0.5 rounded-full pointer-events-none", children: [
589
+ currentIndex + 1,
590
+ " / ",
591
+ images.length
592
+ ] })
593
+ ] }),
548
594
  !inDialog && /* @__PURE__ */ jsx(Dialog, { open: dialogOpen, onOpenChange: setDialogOpen, children: /* @__PURE__ */ jsxs(DialogContent, { className: "max-w-[95vw] max-h-[95vh] w-[95vw] h-[95vh] p-0 overflow-hidden [&>button]:hidden flex flex-col", children: [
549
- /* @__PURE__ */ jsx(DialogTitle, { className: "sr-only", children: file.name }),
550
- /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between px-4 py-2 border-b shrink-0", children: /* @__PURE__ */ jsx("span", { className: "text-sm font-medium truncate", children: file.name }) }),
551
- /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsx(ImageViewer, { file, content, src: directSrc, inDialog: true }) })
595
+ /* @__PURE__ */ jsx(DialogTitle, { className: "sr-only", children: current.file.name }),
596
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between px-4 py-2 border-b shrink-0", children: /* @__PURE__ */ jsx("span", { className: "text-sm font-medium truncate", children: current.file.name }) }),
597
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsx(ImageViewer, { images, initialIndex: currentIndex, inDialog: true }) })
552
598
  ] }) })
553
599
  ]
554
600
  }
@@ -557,5 +603,5 @@ function ImageViewer({ file, content, src: directSrc, inDialog = false }) {
557
603
  __name(ImageViewer, "ImageViewer");
558
604
 
559
605
  export { ImageInfo, ImageToolbar, ImageViewer };
560
- //# sourceMappingURL=chunk-MADKYFI3.mjs.map
561
- //# sourceMappingURL=chunk-MADKYFI3.mjs.map
606
+ //# sourceMappingURL=chunk-BEURMR25.mjs.map
607
+ //# sourceMappingURL=chunk-BEURMR25.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/tools/ImageViewer/utils/constants.ts","../src/tools/ImageViewer/utils/lqip.ts","../src/tools/ImageViewer/utils/debug.ts","../src/tools/ImageViewer/components/ImageToolbar.tsx","../src/tools/ImageViewer/components/ImageInfo.tsx","../src/tools/ImageViewer/hooks/useImageTransform.ts","../src/tools/ImageViewer/hooks/useImageLoading.ts","../src/tools/ImageViewer/components/ImageViewer.tsx"],"names":["jsxs","useState","useEffect","useMemo","contentKey","url","useTypedT","useRef","useCallback","jsx","cn","Fragment"],"mappings":"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","file":"chunk-BEURMR25.mjs","sourcesContent":["/**\n * ImageViewer constants\n */\n\nimport type { ZoomPreset, ImageTransform } from '../types';\n\n// =============================================================================\n// SIZE LIMITS\n// =============================================================================\n\n/** Maximum image size before blocking (50MB) */\nexport const MAX_IMAGE_SIZE = 50 * 1024 * 1024;\n\n/** Image size threshold for warning (10MB) */\nexport const WARNING_IMAGE_SIZE = 10 * 1024 * 1024;\n\n/** Progressive loading threshold - use LQIP for images > 500KB */\nexport const PROGRESSIVE_LOADING_THRESHOLD = 500 * 1024;\n\n// =============================================================================\n// LQIP CONFIGURATION\n// =============================================================================\n\n/** Low-quality placeholder size (32x32) */\nexport const LQIP_SIZE = 32;\n\n/** LQIP JPEG quality */\nexport const LQIP_QUALITY = 0.5;\n\n// =============================================================================\n// ZOOM CONFIGURATION\n// =============================================================================\n\n/** Minimum zoom level */\nexport const MIN_ZOOM = 0.1;\n\n/** Maximum zoom level */\nexport const MAX_ZOOM = 8;\n\n/** Available zoom presets */\nexport const ZOOM_PRESETS: readonly ZoomPreset[] = [\n { label: 'Fit', value: 'fit' },\n { label: '25%', value: 0.25 },\n { label: '50%', value: 0.5 },\n { label: '100%', value: 1 },\n { label: '200%', value: 2 },\n { label: '400%', value: 4 },\n] as const;\n\n// =============================================================================\n// DEFAULT VALUES\n// =============================================================================\n\n/** Default transform state */\nexport const DEFAULT_TRANSFORM: ImageTransform = {\n rotation: 0,\n flipH: false,\n flipV: false,\n};\n","/**\n * LQIP (Low-Quality Image Placeholder) generator\n *\n * Creates a tiny blurred preview image for progressive loading.\n */\n\nimport { LQIP_QUALITY, LQIP_SIZE } from './constants';\n\n/**\n * Create a low-quality image placeholder from source URL\n *\n * @param imageSrc - Full quality image URL\n * @returns Data URL of tiny preview, or null on error\n */\nexport async function createLQIP(imageSrc: string): Promise<string | null> {\n try {\n // Load the full image\n const img = new Image();\n img.crossOrigin = 'anonymous';\n\n await new Promise<void>((resolve, reject) => {\n img.onload = () => resolve();\n img.onerror = () => reject(new Error('Failed to load image for LQIP'));\n img.src = imageSrc;\n });\n\n // Calculate aspect ratio preserving dimensions\n const aspect = img.naturalWidth / img.naturalHeight;\n const width = aspect >= 1 ? LQIP_SIZE : Math.round(LQIP_SIZE * aspect);\n const height = aspect >= 1 ? Math.round(LQIP_SIZE / aspect) : LQIP_SIZE;\n\n // Create canvas for downscaling\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n const ctx = canvas.getContext('2d');\n if (!ctx) return null;\n\n // Draw scaled down image\n ctx.drawImage(img, 0, 0, width, height);\n\n // Return as data URL (very small, ~1-2KB)\n return canvas.toDataURL('image/jpeg', LQIP_QUALITY);\n } catch {\n return null;\n }\n}\n","'use client';\n\n/**\n * ImageViewer Debug Logger\n *\n * Uses universal logger with media-specific helpers.\n * Logs go to both console (dev) and zustand store (for Console panel).\n */\n\nimport { createMediaLogger } from '@djangocfg/ui-core/lib';\n\nexport const imageDebug = createMediaLogger('ImageViewer');\n\nexport default imageDebug;\n","'use client';\n\n/**\n * ImageToolbar - Floating toolbar for image controls\n */\n\nimport { useMemo } from 'react';\nimport { ZoomIn, ZoomOut, RotateCw, FlipHorizontal, FlipVertical, Maximize2, Expand } from 'lucide-react';\nimport { useControls } from 'react-zoom-pan-pinch';\nimport { Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@djangocfg/ui-core/components';\nimport { useTypedT, type I18nTranslations } from '@djangocfg/i18n';\nimport { cn } from '@djangocfg/ui-core/lib';\nimport { ZOOM_PRESETS } from '../utils';\nimport type { ImageToolbarProps } from '../types';\n\n// =============================================================================\n// COMPONENT\n// =============================================================================\n\nexport function ImageToolbar({\n scale,\n transform,\n onRotate,\n onFlipH,\n onFlipV,\n onZoomPreset,\n onExpand,\n}: ImageToolbarProps) {\n const t = useTypedT<I18nTranslations>();\n const { zoomIn, zoomOut, resetTransform } = useControls();\n\n const labels = useMemo(() => ({\n zoomIn: t('tools.image.zoomIn'),\n zoomOut: t('tools.image.zoomOut'),\n fitToView: t('tools.image.fitToView'),\n flipHorizontal: t('tools.image.flipHorizontal'),\n flipVertical: t('tools.image.flipVertical'),\n rotate: t('tools.image.rotate'),\n fullscreen: t('tools.image.fullscreen'),\n }), [t]);\n\n const zoomLabel = useMemo(() => {\n const percent = Math.round(scale * 100);\n return `${percent}%`;\n }, [scale]);\n\n return (\n <div className=\"absolute bottom-4 left-1/2 -translate-x-1/2 z-10 flex items-center gap-0.5 bg-background/90 backdrop-blur-sm border rounded-lg p-1 shadow-lg\">\n {/* Zoom controls */}\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-7 w-7\"\n onClick={() => zoomOut()}\n title={labels.zoomOut}\n >\n <ZoomOut className=\"h-3.5 w-3.5\" />\n </Button>\n\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"ghost\" size=\"sm\" className=\"h-7 px-2 min-w-[52px] font-mono text-xs\">\n {zoomLabel}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"center\" className=\"min-w-[80px]\">\n {ZOOM_PRESETS.map((preset) => (\n <DropdownMenuItem\n key={preset.label}\n onClick={() => onZoomPreset(preset.value)}\n className=\"text-xs justify-center\"\n >\n {preset.label}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-7 w-7\"\n onClick={() => zoomIn()}\n title={labels.zoomIn}\n >\n <ZoomIn className=\"h-3.5 w-3.5\" />\n </Button>\n\n <div className=\"w-px h-4 bg-border mx-1\" />\n\n {/* Fit to view */}\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-7 w-7\"\n onClick={() => resetTransform()}\n title={labels.fitToView}\n >\n <Maximize2 className=\"h-3.5 w-3.5\" />\n </Button>\n\n <div className=\"w-px h-4 bg-border mx-1\" />\n\n {/* Transform controls */}\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className={cn('h-7 w-7', transform.flipH && 'bg-accent')}\n onClick={onFlipH}\n title={labels.flipHorizontal}\n >\n <FlipHorizontal className=\"h-3.5 w-3.5\" />\n </Button>\n\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className={cn('h-7 w-7', transform.flipV && 'bg-accent')}\n onClick={onFlipV}\n title={labels.flipVertical}\n >\n <FlipVertical className=\"h-3.5 w-3.5\" />\n </Button>\n\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-7 w-7\"\n onClick={onRotate}\n title={labels.rotate}\n >\n <RotateCw className=\"h-3.5 w-3.5\" />\n </Button>\n\n {transform.rotation !== 0 && (\n <span className=\"text-[10px] text-muted-foreground font-mono pl-1\">\n {transform.rotation}°\n </span>\n )}\n\n {onExpand && (\n <>\n <div className=\"w-px h-4 bg-border mx-1\" />\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-7 w-7\"\n onClick={onExpand}\n title={labels.fullscreen}\n >\n <Expand className=\"h-3.5 w-3.5\" />\n </Button>\n </>\n )}\n </div>\n );\n}\n","'use client';\n\n/**\n * ImageInfo - Displays image dimensions badge\n */\n\nimport { useEffect, useState } from 'react';\nimport { useImageCache } from '../../../stores/mediaCache';\nimport type { ImageInfoProps } from '../types';\n\n// =============================================================================\n// COMPONENT\n// =============================================================================\n\nexport function ImageInfo({ src }: ImageInfoProps) {\n const { getDimensions, cacheDimensions } = useImageCache();\n const [dimensions, setDimensions] = useState<{ w: number; h: number } | null>(null);\n\n useEffect(() => {\n // Check cache first\n const cached = getDimensions(src);\n if (cached) {\n setDimensions({ w: cached.width, h: cached.height });\n return;\n }\n\n // Load and cache dimensions\n const img = new Image();\n img.onload = () => {\n const dims = { w: img.naturalWidth, h: img.naturalHeight };\n setDimensions(dims);\n cacheDimensions(src, { width: dims.w, height: dims.h });\n };\n img.src = src;\n }, [src, getDimensions, cacheDimensions]);\n\n if (!dimensions) return null;\n\n return (\n <div className=\"absolute top-3 right-3 z-10 px-2 py-1 bg-background/80 backdrop-blur-sm border rounded text-[10px] text-muted-foreground font-mono\">\n {dimensions.w} × {dimensions.h}\n </div>\n );\n}\n","'use client';\n\n/**\n * useImageTransform - Manages image rotation and flip state\n */\n\nimport { useCallback, useEffect, useMemo, useState } from 'react';\n\nimport { DEFAULT_TRANSFORM } from '../utils';\n\nimport type { ImageTransform } from '../types';\n// =============================================================================\n// TYPES\n// =============================================================================\n\nexport interface UseImageTransformOptions {\n /** Reset transform when this key changes */\n resetKey?: string;\n}\n\nexport interface UseImageTransformReturn {\n /** Current transform state */\n transform: ImageTransform;\n /** Rotate 90 degrees clockwise */\n rotate: () => void;\n /** Toggle horizontal flip */\n flipH: () => void;\n /** Toggle vertical flip */\n flipV: () => void;\n /** Reset all transforms */\n reset: () => void;\n /** CSS transform string for applying to image */\n transformStyle: string;\n}\n\n// =============================================================================\n// HOOK\n// =============================================================================\n\nexport function useImageTransform(\n options: UseImageTransformOptions = {}\n): UseImageTransformReturn {\n const { resetKey } = options;\n\n const [transform, setTransform] = useState<ImageTransform>(DEFAULT_TRANSFORM);\n\n // Reset transform when key changes\n useEffect(() => {\n setTransform(DEFAULT_TRANSFORM);\n }, [resetKey]);\n\n const rotate = useCallback(() => {\n setTransform((prev) => ({\n ...prev,\n rotation: (prev.rotation + 90) % 360,\n }));\n }, []);\n\n const flipH = useCallback(() => {\n setTransform((prev) => ({\n ...prev,\n flipH: !prev.flipH,\n }));\n }, []);\n\n const flipV = useCallback(() => {\n setTransform((prev) => ({\n ...prev,\n flipV: !prev.flipV,\n }));\n }, []);\n\n const reset = useCallback(() => {\n setTransform(DEFAULT_TRANSFORM);\n }, []);\n\n // Build CSS transform string\n const transformStyle = useMemo(() => {\n const transforms: string[] = [];\n\n if (transform.rotation !== 0) {\n transforms.push(`rotate(${transform.rotation}deg)`);\n }\n if (transform.flipH) {\n transforms.push('scaleX(-1)');\n }\n if (transform.flipV) {\n transforms.push('scaleY(-1)');\n }\n\n return transforms.join(' ') || 'none';\n }, [transform]);\n\n return {\n transform,\n rotate,\n flipH,\n flipV,\n reset,\n transformStyle,\n };\n}\n","'use client';\n\n/**\n * useImageLoading - Manages image loading state with LQIP\n */\n\nimport { useEffect, useRef, useState } from 'react';\n\nimport { generateContentKey, useMediaCacheStore } from '../../../stores/mediaCache';\nimport {\n createLQIP, imageDebug, MAX_IMAGE_SIZE, PROGRESSIVE_LOADING_THRESHOLD, WARNING_IMAGE_SIZE\n} from '../utils';\n\n// =============================================================================\n// TYPES\n// =============================================================================\n\nexport interface UseImageLoadingOptions {\n /** Image content (ArrayBuffer or string) */\n content: string | ArrayBuffer;\n /** MIME type for blob creation */\n mimeType?: string;\n /**\n * Direct image URL (bypasses content→blob conversion).\n * When provided, content is ignored and URL is used directly.\n */\n src?: string;\n}\n\nexport interface UseImageLoadingReturn {\n /** Blob URL source for the image */\n src: string | null;\n /** Low-quality placeholder URL */\n lqip: string | null;\n /** Whether full image is loaded */\n isFullyLoaded: boolean;\n /** Whether to use progressive loading */\n useProgressiveLoading: boolean;\n /** Error message if any */\n error: string | null;\n /** Content key for caching */\n contentKey: string | null;\n /** Image size in bytes */\n size: number;\n /** Whether content exists */\n hasContent: boolean;\n}\n\n// =============================================================================\n// HOOK\n// =============================================================================\n\nexport function useImageLoading(options: UseImageLoadingOptions): UseImageLoadingReturn {\n const { content, mimeType, src: directSrc } = options;\n\n // Get stable function references from store (not from hook to avoid re-renders)\n const getOrCreateBlobUrl = useMediaCacheStore.getState().getOrCreateBlobUrl;\n const releaseBlobUrl = useMediaCacheStore.getState().releaseBlobUrl;\n\n const [src, setSrc] = useState<string | null>(null);\n const [lqip, setLqip] = useState<string | null>(null);\n const [isFullyLoaded, setIsFullyLoaded] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const contentKeyRef = useRef<string | null>(null);\n const isMountedRef = useRef(true);\n\n // Calculate size and flags\n const size = content ? (typeof content === 'string' ? content.length : content.byteLength) : 0;\n // When directSrc is provided, we have content (the URL itself)\n const hasContent = directSrc ? true : size > 0;\n const useProgressiveLoading = directSrc ? false : size > PROGRESSIVE_LOADING_THRESHOLD;\n\n // Track unmount for cleanup\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n // Release blob URL only on actual unmount\n if (contentKeyRef.current) {\n useMediaCacheStore.getState().releaseBlobUrl(contentKeyRef.current);\n contentKeyRef.current = null;\n }\n };\n }, []);\n\n // Create blob URL with caching and size validation\n useEffect(() => {\n // Reset error state\n setError(null);\n\n // Direct URL mode - use as-is without blob conversion\n if (directSrc) {\n imageDebug.load(directSrc, 'url');\n setSrc(directSrc);\n setIsFullyLoaded(true);\n return;\n }\n\n if (!hasContent) {\n setSrc(null);\n return;\n }\n\n // Size validation - reject oversized images\n if (size > MAX_IMAGE_SIZE) {\n const sizeMB = (size / 1024 / 1024).toFixed(1);\n const errorMsg = `Image too large: ${sizeMB}MB (maximum: 50MB)`;\n imageDebug.error(errorMsg, { size, sizeMB, maxSize: MAX_IMAGE_SIZE });\n setError(errorMsg);\n setSrc(null);\n return;\n }\n\n // Warn about large images\n if (size > WARNING_IMAGE_SIZE) {\n const sizeMB = (size / 1024 / 1024).toFixed(1);\n imageDebug.warn(`Large image: ${sizeMB}MB - may impact performance`);\n }\n\n // Handle string content (data URLs or binary strings)\n if (typeof content === 'string') {\n // Pass through data URLs directly\n if (content.startsWith('data:')) {\n imageDebug.load(content.slice(0, 50) + '...', 'data-url');\n setSrc(content);\n return;\n }\n\n // Convert binary string to ArrayBuffer and use Blob URL\n const encoder = new TextEncoder();\n const buffer = encoder.encode(content).buffer;\n const contentKey = generateContentKey(buffer);\n\n // Release previous blob URL if content changed\n if (contentKeyRef.current && contentKeyRef.current !== contentKey) {\n releaseBlobUrl(contentKeyRef.current);\n }\n\n contentKeyRef.current = contentKey;\n const url = getOrCreateBlobUrl(contentKey, buffer, mimeType || 'image/png');\n imageDebug.load(url, 'blob');\n imageDebug.state('loaded', { size, mimeType, contentKey });\n setSrc(url);\n return;\n }\n\n // Handle ArrayBuffer with cached blob URL\n const contentKey = generateContentKey(content);\n\n // Release previous blob URL if content changed\n if (contentKeyRef.current && contentKeyRef.current !== contentKey) {\n releaseBlobUrl(contentKeyRef.current);\n }\n\n contentKeyRef.current = contentKey;\n const url = getOrCreateBlobUrl(contentKey, content, mimeType || 'image/png');\n imageDebug.load(url, 'blob');\n imageDebug.state('loaded', { size, mimeType, contentKey });\n setSrc(url);\n\n // No cleanup here - cleanup happens in unmount effect above\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [content, mimeType, hasContent, size, directSrc]);\n\n // Create LQIP for progressive loading\n useEffect(() => {\n if (!src || !useProgressiveLoading) {\n setLqip(null);\n setIsFullyLoaded(true);\n return;\n }\n\n setIsFullyLoaded(false);\n imageDebug.state('progressive loading', { size });\n\n // Create low-quality placeholder\n createLQIP(src).then((placeholder) => {\n if (placeholder) {\n imageDebug.debug('LQIP created');\n setLqip(placeholder);\n }\n });\n\n // Pre-load full image\n const img = new Image();\n img.onload = () => {\n imageDebug.state('fully loaded');\n setIsFullyLoaded(true);\n };\n img.onerror = () => {\n imageDebug.error('Failed to load full image');\n };\n img.src = src;\n }, [src, useProgressiveLoading, size]);\n\n return {\n src,\n lqip,\n isFullyLoaded,\n useProgressiveLoading,\n error,\n contentKey: contentKeyRef.current,\n size,\n hasContent,\n };\n}\n","'use client';\n\n/**\n * ImageViewer - Image viewer with zoom, pan, rotate, flip, gallery navigation\n *\n * Features:\n * - Zoom with mouse wheel and presets\n * - Pan with drag\n * - Rotate 90°\n * - Flip horizontal/vertical\n * - Fullscreen dialog\n * - Keyboard shortcuts (+/-, 0, r, ←/→ for gallery)\n * - Gallery mode: pass images[] with multiple items\n */\n\nimport { useEffect, useState, useRef, useCallback, useMemo } from 'react';\nimport { ImageIcon, AlertCircle, ChevronLeft, ChevronRight } from 'lucide-react';\nimport { TransformWrapper, TransformComponent, useControls } from 'react-zoom-pan-pinch';\nimport { cn, Dialog, DialogContent, DialogTitle, Alert, AlertDescription } from '@djangocfg/ui-core';\nimport { useTypedT, type I18nTranslations } from '@djangocfg/i18n';\nimport { useHotkey } from '@djangocfg/ui-core/hooks';\n\nimport { ImageToolbar } from './ImageToolbar';\nimport { ImageInfo } from './ImageInfo';\nimport { useImageTransform, useImageLoading } from '../hooks';\nimport type { ImageViewerProps, ImageItem } from '../types';\n\n// =============================================================================\n// COMPONENT\n// =============================================================================\n\nexport function ImageViewer({\n images,\n initialIndex = 0,\n inDialog = false,\n}: ImageViewerProps) {\n const t = useTypedT<I18nTranslations>();\n\n const [currentIndex, setCurrentIndex] = useState(() =>\n Math.max(0, Math.min(initialIndex, images.length - 1))\n );\n\n // Reset index when initialIndex changes (e.g. opening different image)\n useEffect(() => {\n setCurrentIndex(Math.max(0, Math.min(initialIndex, images.length - 1)));\n }, [initialIndex, images.length]);\n\n const current = images[currentIndex];\n const hasMultiple = images.length > 1;\n\n const [scale, setScale] = useState(1);\n const [dialogOpen, setDialogOpen] = useState(false);\n const [loadError, setLoadError] = useState(false);\n const containerRef = useRef<HTMLDivElement>(null);\n const controlsRef = useRef<ReturnType<typeof useControls> | null>(null);\n\n const labels = useMemo(() => ({\n noImage: t('tools.image.noImage'),\n failedToLoad: t('tools.image.failedToLoad'),\n loading: t('ui.form.loading'),\n }), [t]);\n\n const {\n src,\n lqip,\n isFullyLoaded,\n useProgressiveLoading,\n error,\n hasContent,\n } = useImageLoading({\n content: current?.content ?? '',\n mimeType: current?.file.mimeType,\n src: current?.src,\n });\n\n useEffect(() => { setLoadError(false); }, [src]);\n\n const { transform, rotate, flipH, flipV, transformStyle } = useImageTransform({\n resetKey: current?.file.path ?? '',\n });\n\n const handleZoomPreset = useCallback((value: number | 'fit') => {\n if (!controlsRef.current) return;\n if (value === 'fit') controlsRef.current.resetTransform();\n else controlsRef.current.setTransform(0, 0, value);\n }, []);\n\n const handleExpand = useCallback(() => setDialogOpen(true), []);\n\n const prev = useCallback(() =>\n setCurrentIndex((i) => (i - 1 + images.length) % images.length),\n [images.length]\n );\n\n const next = useCallback(() =>\n setCurrentIndex((i) => (i + 1) % images.length),\n [images.length]\n );\n\n // Keyboard: zoom/rotate (only when container focused)\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n if (!containerRef.current?.contains(document.activeElement) &&\n document.activeElement !== containerRef.current) return;\n const controls = controlsRef.current;\n if (!controls) return;\n switch (e.key) {\n case '+': case '=': e.preventDefault(); controls.zoomIn(); break;\n case '-': e.preventDefault(); controls.zoomOut(); break;\n case '0': e.preventDefault(); controls.resetTransform(); break;\n case 'r': if (!e.metaKey && !e.ctrlKey) { e.preventDefault(); rotate(); } break;\n }\n };\n window.addEventListener('keydown', handleKeyDown);\n return () => window.removeEventListener('keydown', handleKeyDown);\n }, [rotate]);\n\n // Keyboard: gallery navigation (global when open)\n useHotkey('ArrowLeft', prev, { enabled: hasMultiple, preventDefault: true });\n useHotkey('ArrowRight', next, { enabled: hasMultiple, preventDefault: true });\n\n if (!current) {\n return (\n <div className=\"flex-1 flex flex-col items-center justify-center gap-2 bg-muted/30\">\n <ImageIcon className=\"w-12 h-12 text-muted-foreground/50\" />\n <p className=\"text-sm text-muted-foreground\">{labels.noImage}</p>\n </div>\n );\n }\n\n if (error || loadError) {\n return (\n <div className=\"flex-1 flex flex-col items-center justify-center gap-3 bg-muted/30 p-4\">\n <AlertCircle className=\"w-12 h-12 text-destructive/70\" />\n <Alert variant=\"destructive\" className=\"max-w-md\">\n <AlertCircle className=\"h-4 w-4\" />\n <AlertDescription>{error || labels.failedToLoad}</AlertDescription>\n </Alert>\n </div>\n );\n }\n\n if (!hasContent) {\n return (\n <div className=\"flex-1 flex flex-col items-center justify-center gap-2 bg-muted/30\">\n <ImageIcon className=\"w-12 h-12 text-muted-foreground/50\" />\n <p className=\"text-sm text-muted-foreground\">{labels.noImage}</p>\n </div>\n );\n }\n\n return (\n <div\n ref={containerRef}\n tabIndex={0}\n className={cn(\n 'flex-1 h-full relative overflow-hidden outline-none',\n 'bg-[length:16px_16px]',\n '[background-color:hsl(var(--muted)/0.2)]',\n '[background-image:linear-gradient(45deg,hsl(var(--muted)/0.4)_25%,transparent_25%),linear-gradient(-45deg,hsl(var(--muted)/0.4)_25%,transparent_25%),linear-gradient(45deg,transparent_75%,hsl(var(--muted)/0.4)_75%),linear-gradient(-45deg,transparent_75%,hsl(var(--muted)/0.4)_75%)]',\n '[background-position:0_0,0_8px,8px_-8px,-8px_0px]'\n )}\n >\n {src && <ImageInfo src={src} />}\n\n {useProgressiveLoading && !isFullyLoaded && (\n <div className=\"absolute top-3 left-3 z-10 px-2 py-1 bg-background/80 backdrop-blur-sm border rounded text-[10px] text-muted-foreground font-mono flex items-center gap-1.5\">\n <div className=\"w-2 h-2 bg-blue-500 rounded-full animate-pulse\" />\n {labels.loading}\n </div>\n )}\n\n <TransformWrapper\n initialScale={1}\n minScale={0.1}\n maxScale={8}\n centerOnInit\n centerZoomedOut\n onTransformed={(ref, state) => { setScale(state.scale); controlsRef.current = ref; }}\n onInit={(ref) => { controlsRef.current = ref; }}\n wheel={{ step: 0.1 }}\n doubleClick={{ mode: 'toggle', step: 2 }}\n panning={{ velocityDisabled: false }}\n >\n <ImageToolbar\n scale={scale}\n transform={transform}\n onRotate={rotate}\n onFlipH={flipH}\n onFlipV={flipV}\n onZoomPreset={handleZoomPreset}\n onExpand={!inDialog ? handleExpand : undefined}\n />\n\n <TransformComponent\n wrapperClass=\"!w-full !h-full cursor-grab active:cursor-grabbing\"\n contentClass=\"!w-full !h-full flex items-center justify-center\"\n >\n <div className=\"relative\">\n {useProgressiveLoading && lqip && !isFullyLoaded && (\n <img\n src={lqip}\n alt=\"\"\n aria-hidden=\"true\"\n className=\"absolute inset-0 max-w-full max-h-full object-contain select-none\"\n style={{ transform: transformStyle, filter: 'blur(20px)', transition: 'opacity 0.3s ease-out', opacity: isFullyLoaded ? 0 : 1 }}\n draggable={false}\n />\n )}\n {src && (\n <img\n src={src}\n alt={current.file.name}\n className=\"max-w-full max-h-full object-contain select-none\"\n style={{\n transform: transformStyle,\n transition: useProgressiveLoading ? 'transform 0.15s ease-out, opacity 0.3s ease-out' : 'transform 0.15s ease-out',\n opacity: useProgressiveLoading && !isFullyLoaded ? 0 : 1,\n }}\n draggable={false}\n crossOrigin=\"anonymous\"\n onError={() => setLoadError(true)}\n />\n )}\n </div>\n </TransformComponent>\n </TransformWrapper>\n\n {/* Gallery navigation */}\n {hasMultiple && (\n <>\n <button\n type=\"button\"\n onClick={prev}\n className=\"absolute left-2 top-1/2 -translate-y-1/2 z-10 bg-black/50 hover:bg-black/70 text-white rounded-full p-1.5 transition-colors\"\n >\n <ChevronLeft className=\"h-5 w-5\" />\n </button>\n <button\n type=\"button\"\n onClick={next}\n className=\"absolute right-2 top-1/2 -translate-y-1/2 z-10 bg-black/50 hover:bg-black/70 text-white rounded-full p-1.5 transition-colors\"\n >\n <ChevronRight className=\"h-5 w-5\" />\n </button>\n <div className=\"absolute bottom-2 left-1/2 -translate-x-1/2 z-10 bg-black/50 text-white text-xs px-2 py-0.5 rounded-full pointer-events-none\">\n {currentIndex + 1} / {images.length}\n </div>\n </>\n )}\n\n {/* Fullscreen dialog */}\n {!inDialog && (\n <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>\n <DialogContent className=\"max-w-[95vw] max-h-[95vh] w-[95vw] h-[95vh] p-0 overflow-hidden [&>button]:hidden flex flex-col\">\n <DialogTitle className=\"sr-only\">{current.file.name}</DialogTitle>\n <div className=\"flex items-center justify-between px-4 py-2 border-b shrink-0\">\n <span className=\"text-sm font-medium truncate\">{current.file.name}</span>\n </div>\n <div className=\"flex-1 min-h-0\">\n <ImageViewer images={images} initialIndex={currentIndex} inDialog />\n </div>\n </DialogContent>\n </Dialog>\n )}\n </div>\n );\n}\n"]}
@@ -7,6 +7,7 @@ var lucideReact = require('lucide-react');
7
7
  var reactZoomPanPinch = require('react-zoom-pan-pinch');
8
8
  var uiCore = require('@djangocfg/ui-core');
9
9
  var i18n = require('@djangocfg/i18n');
10
+ var hooks = require('@djangocfg/ui-core/hooks');
10
11
  var components = require('@djangocfg/ui-core/components');
11
12
  var lib = require('@djangocfg/ui-core/lib');
12
13
  var jsxRuntime = require('react/jsx-runtime');
@@ -370,8 +371,20 @@ function useImageLoading(options) {
370
371
  };
371
372
  }
372
373
  chunkWGEGR3DF_cjs.__name(useImageLoading, "useImageLoading");
373
- function ImageViewer({ file, content, src: directSrc, inDialog = false }) {
374
+ function ImageViewer({
375
+ images,
376
+ initialIndex = 0,
377
+ inDialog = false
378
+ }) {
374
379
  const t = i18n.useTypedT();
380
+ const [currentIndex, setCurrentIndex] = react.useState(
381
+ () => Math.max(0, Math.min(initialIndex, images.length - 1))
382
+ );
383
+ react.useEffect(() => {
384
+ setCurrentIndex(Math.max(0, Math.min(initialIndex, images.length - 1)));
385
+ }, [initialIndex, images.length]);
386
+ const current = images[currentIndex];
387
+ const hasMultiple = images.length > 1;
375
388
  const [scale, setScale] = react.useState(1);
376
389
  const [dialogOpen, setDialogOpen] = react.useState(false);
377
390
  const [loadError, setLoadError] = react.useState(false);
@@ -389,29 +402,34 @@ function ImageViewer({ file, content, src: directSrc, inDialog = false }) {
389
402
  useProgressiveLoading,
390
403
  error,
391
404
  hasContent
392
- } = useImageLoading({ content, mimeType: file.mimeType, src: directSrc });
405
+ } = useImageLoading({
406
+ content: current?.content ?? "",
407
+ mimeType: current?.file.mimeType,
408
+ src: current?.src
409
+ });
393
410
  react.useEffect(() => {
394
411
  setLoadError(false);
395
412
  }, [src]);
396
413
  const { transform, rotate, flipH, flipV, transformStyle } = useImageTransform({
397
- resetKey: file.path
414
+ resetKey: current?.file.path ?? ""
398
415
  });
399
416
  const handleZoomPreset = react.useCallback((value) => {
400
417
  if (!controlsRef.current) return;
401
- if (value === "fit") {
402
- controlsRef.current.resetTransform();
403
- } else {
404
- controlsRef.current.setTransform(0, 0, value);
405
- }
406
- }, []);
407
- const handleExpand = react.useCallback(() => {
408
- setDialogOpen(true);
418
+ if (value === "fit") controlsRef.current.resetTransform();
419
+ else controlsRef.current.setTransform(0, 0, value);
409
420
  }, []);
421
+ const handleExpand = react.useCallback(() => setDialogOpen(true), []);
422
+ const prev = react.useCallback(
423
+ () => setCurrentIndex((i) => (i - 1 + images.length) % images.length),
424
+ [images.length]
425
+ );
426
+ const next = react.useCallback(
427
+ () => setCurrentIndex((i) => (i + 1) % images.length),
428
+ [images.length]
429
+ );
410
430
  react.useEffect(() => {
411
431
  const handleKeyDown = /* @__PURE__ */ chunkWGEGR3DF_cjs.__name((e) => {
412
- if (!containerRef.current?.contains(document.activeElement) && document.activeElement !== containerRef.current) {
413
- return;
414
- }
432
+ if (!containerRef.current?.contains(document.activeElement) && document.activeElement !== containerRef.current) return;
415
433
  const controls = controlsRef.current;
416
434
  if (!controls) return;
417
435
  switch (e.key) {
@@ -439,6 +457,14 @@ function ImageViewer({ file, content, src: directSrc, inDialog = false }) {
439
457
  window.addEventListener("keydown", handleKeyDown);
440
458
  return () => window.removeEventListener("keydown", handleKeyDown);
441
459
  }, [rotate]);
460
+ hooks.useHotkey("ArrowLeft", prev, { enabled: hasMultiple, preventDefault: true });
461
+ hooks.useHotkey("ArrowRight", next, { enabled: hasMultiple, preventDefault: true });
462
+ if (!current) {
463
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col items-center justify-center gap-2 bg-muted/30", children: [
464
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ImageIcon, { className: "w-12 h-12 text-muted-foreground/50" }),
465
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: labels.noImage })
466
+ ] });
467
+ }
442
468
  if (error || loadError) {
443
469
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col items-center justify-center gap-3 bg-muted/30 p-4", children: [
444
470
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircle, { className: "w-12 h-12 text-destructive/70" }),
@@ -516,12 +542,7 @@ function ImageViewer({ file, content, src: directSrc, inDialog = false }) {
516
542
  alt: "",
517
543
  "aria-hidden": "true",
518
544
  className: "absolute inset-0 max-w-full max-h-full object-contain select-none",
519
- style: {
520
- transform: transformStyle,
521
- filter: "blur(20px)",
522
- transition: "opacity 0.3s ease-out",
523
- opacity: isFullyLoaded ? 0 : 1
524
- },
545
+ style: { transform: transformStyle, filter: "blur(20px)", transition: "opacity 0.3s ease-out", opacity: isFullyLoaded ? 0 : 1 },
525
546
  draggable: false
526
547
  }
527
548
  ),
@@ -529,7 +550,7 @@ function ImageViewer({ file, content, src: directSrc, inDialog = false }) {
529
550
  "img",
530
551
  {
531
552
  src,
532
- alt: file.name,
553
+ alt: current.file.name,
533
554
  className: "max-w-full max-h-full object-contain select-none",
534
555
  style: {
535
556
  transform: transformStyle,
@@ -547,10 +568,35 @@ function ImageViewer({ file, content, src: directSrc, inDialog = false }) {
547
568
  ]
548
569
  }
549
570
  ),
571
+ hasMultiple && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
572
+ /* @__PURE__ */ jsxRuntime.jsx(
573
+ "button",
574
+ {
575
+ type: "button",
576
+ onClick: prev,
577
+ className: "absolute left-2 top-1/2 -translate-y-1/2 z-10 bg-black/50 hover:bg-black/70 text-white rounded-full p-1.5 transition-colors",
578
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { className: "h-5 w-5" })
579
+ }
580
+ ),
581
+ /* @__PURE__ */ jsxRuntime.jsx(
582
+ "button",
583
+ {
584
+ type: "button",
585
+ onClick: next,
586
+ className: "absolute right-2 top-1/2 -translate-y-1/2 z-10 bg-black/50 hover:bg-black/70 text-white rounded-full p-1.5 transition-colors",
587
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "h-5 w-5" })
588
+ }
589
+ ),
590
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute bottom-2 left-1/2 -translate-x-1/2 z-10 bg-black/50 text-white text-xs px-2 py-0.5 rounded-full pointer-events-none", children: [
591
+ currentIndex + 1,
592
+ " / ",
593
+ images.length
594
+ ] })
595
+ ] }),
550
596
  !inDialog && /* @__PURE__ */ jsxRuntime.jsx(uiCore.Dialog, { open: dialogOpen, onOpenChange: setDialogOpen, children: /* @__PURE__ */ jsxRuntime.jsxs(uiCore.DialogContent, { className: "max-w-[95vw] max-h-[95vh] w-[95vw] h-[95vh] p-0 overflow-hidden [&>button]:hidden flex flex-col", children: [
551
- /* @__PURE__ */ jsxRuntime.jsx(uiCore.DialogTitle, { className: "sr-only", children: file.name }),
552
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-between px-4 py-2 border-b shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium truncate", children: file.name }) }),
553
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntime.jsx(ImageViewer, { file, content, src: directSrc, inDialog: true }) })
597
+ /* @__PURE__ */ jsxRuntime.jsx(uiCore.DialogTitle, { className: "sr-only", children: current.file.name }),
598
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-between px-4 py-2 border-b shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium truncate", children: current.file.name }) }),
599
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntime.jsx(ImageViewer, { images, initialIndex: currentIndex, inDialog: true }) })
554
600
  ] }) })
555
601
  ]
556
602
  }
@@ -561,5 +607,5 @@ chunkWGEGR3DF_cjs.__name(ImageViewer, "ImageViewer");
561
607
  exports.ImageInfo = ImageInfo;
562
608
  exports.ImageToolbar = ImageToolbar;
563
609
  exports.ImageViewer = ImageViewer;
564
- //# sourceMappingURL=chunk-6RG7HOVF.cjs.map
565
- //# sourceMappingURL=chunk-6RG7HOVF.cjs.map
610
+ //# sourceMappingURL=chunk-IPRNIM7L.cjs.map
611
+ //# sourceMappingURL=chunk-IPRNIM7L.cjs.map