11<!-- ./extensions/layouts/super-layout-table/components/CellRenderers/ImageCell.vue -->
22<template >
3- <div class =" image-cell" >
4- <div v-if =" imageSrc" class =" image-wrapper" >
3+ <div class =" image-cell" :class =" `align-${alignment || 'center'}`" >
4+ <div
5+ v-if =" imageSrc"
6+ class =" image-wrapper"
7+ @mouseenter =" handleMouseEnter"
8+ @mouseleave =" showPreview = false"
9+ >
510 <img :src =" imageSrc" :alt =" alt || 'Preview'" class =" preview-image" @error =" onImageError" />
611 </div >
712 <span v-else class =" image-empty" >—</span >
13+
14+ <!-- Hover preview außerhalb des Wrappers mit v-show -->
15+ <Teleport to="body" v-if =" imageSrc " >
16+ <div
17+ v-show =" showPreview"
18+ class =" image-hover-preview"
19+ :style =" previewStyle"
20+ @mouseenter =" showPreview = true"
21+ @mouseleave =" showPreview = false"
22+ >
23+ <img :src =" imageSrc" :alt =" alt || 'Preview'" />
24+ </div >
25+ </Teleport >
826 </div >
927</template >
1028
@@ -21,9 +39,13 @@ const props = defineProps<{
2139 value? : string | ImageValue | null ;
2240 item? : any ;
2341 field? : string ;
42+ alignment? : ' left' | ' center' | ' right' ;
2443}>();
2544
2645const imageError = ref (false );
46+ const showPreview = ref (false );
47+ const mouseX = ref (0 );
48+ const mouseY = ref (0 );
2749
2850const imageSrc = computed (() => {
2951 if (! props .value || imageError .value ) return null ;
@@ -48,6 +70,42 @@ const alt = computed(() => {
4870 return props .field || ' Image' ;
4971});
5072
73+ // Dynamische Position basierend auf Mausposition mit Offset
74+ const previewStyle = computed (() => {
75+ const offset = 20 ; // Offset von der Maus
76+ const previewWidth = 400 ;
77+ const previewHeight = 400 ;
78+ const windowWidth = window .innerWidth ;
79+ const windowHeight = window .innerHeight ;
80+
81+ let left = mouseX .value + offset ;
82+ let top = mouseY .value - previewHeight / 2 ; // Vertikal zentriert zur Maus
83+
84+ // Wenn Preview rechts aus dem Fenster ragt, links von der Maus anzeigen
85+ if (left + previewWidth > windowWidth - 20 ) {
86+ left = mouseX .value - previewWidth - offset ;
87+ }
88+
89+ // Sicherstellen dass Preview nicht aus dem Fenster ragt
90+ if (left < 20 ) left = 20 ;
91+ if (top < 20 ) top = 20 ;
92+ if (top + previewHeight > windowHeight - 20 ) {
93+ top = windowHeight - previewHeight - 20 ;
94+ }
95+
96+ return {
97+ left: ` ${left }px ` ,
98+ top: ` ${top }px ` ,
99+ transform: ' none' , // Kein transform mehr nötig
100+ };
101+ });
102+
103+ function handleMouseEnter(event : MouseEvent ) {
104+ showPreview .value = true ;
105+ mouseX .value = event .clientX ;
106+ mouseY .value = event .clientY ;
107+ }
108+
51109function onImageError() {
52110 imageError .value = true ;
53111}
@@ -57,44 +115,95 @@ function onImageError() {
57115.image-cell {
58116 display : flex ;
59117 align-items : center ;
60- justify-content : center ;
61118 width : 100% ;
62119 padding : 2px ;
63- /* Contained height */
64- height : fit-content ;
120+ height : 100% ;
121+ }
122+
123+ /* Alignment variations */
124+ .image-cell.align-left {
125+ justify-content : flex-start ;
126+ }
127+
128+ .image-cell.align-center {
129+ justify-content : center ;
130+ }
131+
132+ .image-cell.align-right {
133+ justify-content : flex-end ;
65134}
66135
67136.image-wrapper {
68137 position : relative ;
69138 border-radius : 4px ;
70- overflow : hidden ;
71139 background : var (--background-subdued );
72140 border : 1px solid var (--border-color );
73- /* Start small, grow with column width */
74- width : min (100% , max (60px , 80% ));
75- /* Maintain aspect ratio */
76- aspect-ratio : 16 /10 ;
77- /* Start with reasonable height */
78- height : auto ;
79- min-height : 32px ;
80- max-height : 150px ;
141+ /* Kompakter Thumbnail der in normale Rows passt */
142+ width : 60px ;
143+ height : 40px ;
144+ display : flex ;
145+ align-items : center ;
146+ justify-content : center ;
147+ overflow : visible ; /* Allow hover preview to overflow */
81148}
82149
83150.preview-image {
84151 display : block ;
85- width : 100% ;
86- height : 100% ;
87- object-fit : cover ;
88- transition : transform 0.2s ease ;
152+ max-width : calc (100% - 4px );
153+ max-height : calc (100% - 4px );
154+ width : auto ;
155+ height : auto ;
156+ object-fit : contain ; /* Zeigt das komplette Bild ohne Abschneiden */
157+ object-position : center ;
158+ }
159+
160+ /* Hover preview tooltip - Fixed positioning */
161+ .image-hover-preview {
162+ position : fixed ;
163+ width : 400px ;
164+ height : 400px ;
165+ background : white ;
166+ border : 1px solid #e0e0e0 ;
167+ border-radius : 8px ;
168+ box-shadow : 0 4px 24px rgba (0 , 0 , 0 , 0.15 );
169+ display : flex ;
170+ align-items : center ;
171+ justify-content : center ;
172+ z-index : 9999 ;
173+ padding : 12px ;
174+ pointer-events : auto ; /* Wichtig für mouseenter/leave */
175+ transition : opacity 0.2s ease ;
176+ }
177+
178+ .image-hover-preview img {
179+ max-width : 100% ;
180+ max-height : 100% ;
181+ object-fit : contain ;
182+ border-radius : 4px ;
89183}
90184
91- .image-wrapper :hover .preview-image {
185+ .image-wrapper :hover {
186+ cursor : zoom-in ;
187+ border-color : var (--primary );
92188 transform : scale (1.05 );
93- cursor : pointer ;
189+ transition : all 0.2 s ease ;
94190}
95191
96192.image-empty {
97193 color : var (--foreground-subdued );
98194 font-style : italic ;
99195}
196+
197+ /* Bessere Responsivität für schmale Spalten */
198+ @media (max-width : 768px ) {
199+ .image-wrapper {
200+ width : 80px ;
201+ height : 60px ;
202+ }
203+
204+ .image-hover-preview {
205+ width : 300px ;
206+ height : 300px ;
207+ }
208+ }
100209 </style >
0 commit comments