@fy-/fws-vue 2.3.16 → 2.3.18
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/components/ui/DefaultGallery.vue +87 -125
- package/package.json +1 -1
|
@@ -46,23 +46,7 @@ const { width: windowWidth, height: windowHeight } = useWindowSize()
|
|
|
46
46
|
const { height: topControlsHeight } = useElementSize(topControlsRef)
|
|
47
47
|
const { height: infoPanelHeight } = useElementSize(infoPanelRef)
|
|
48
48
|
|
|
49
|
-
//
|
|
50
|
-
const availableHeight = computed(() => {
|
|
51
|
-
let height = isFullscreen.value
|
|
52
|
-
? windowHeight.value * 0.95 // 95% of viewport in fullscreen
|
|
53
|
-
: windowHeight.value * 0.85 // 85% of viewport in normal mode
|
|
54
|
-
|
|
55
|
-
// Subtract top controls
|
|
56
|
-
height -= topControlsHeight.value
|
|
57
|
-
|
|
58
|
-
// Subtract info panel if visible
|
|
59
|
-
if (infoPanel.value && infoPanelHeight.value > 0) {
|
|
60
|
-
height -= infoPanelHeight.value
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Use direct pixel values
|
|
64
|
-
return `${height}px`
|
|
65
|
-
})
|
|
49
|
+
// We no longer need derived measurements as we use CSS variables instead
|
|
66
50
|
|
|
67
51
|
// Use VueUse's useFullscreen for better fullscreen handling
|
|
68
52
|
const { isFullscreen: isElementFullscreen, enter: enterFullscreen, exit: exitFullscreen } = useFullscreen(galleryRef)
|
|
@@ -147,75 +131,33 @@ const currentImage = computed(() => {
|
|
|
147
131
|
const imageCount = computed(() => props.images.length)
|
|
148
132
|
const currentIndex = computed(() => modelValue.value + 1)
|
|
149
133
|
|
|
150
|
-
//
|
|
151
|
-
function
|
|
152
|
-
if (
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
134
|
+
// Update CSS variables for layout consistency
|
|
135
|
+
function updateInfoHeight() {
|
|
136
|
+
if (!infoPanelRef.value) return
|
|
137
|
+
|
|
138
|
+
const height = infoPanelRef.value.offsetHeight || 0
|
|
139
|
+
document.documentElement.style.setProperty('--info-height', `${height}px`)
|
|
156
140
|
}
|
|
157
141
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
if (!imageContainerRef.value) return
|
|
142
|
+
function updateControlsHeight() {
|
|
143
|
+
if (!topControlsRef.value) return
|
|
161
144
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if (!imageElements || imageElements.length === 0) return
|
|
166
|
-
|
|
167
|
-
// Get current panel heights for accurate calculations
|
|
168
|
-
const topControlsCurrentHeight = topControlsRef.value?.offsetHeight || 0
|
|
169
|
-
const infoPanelCurrentHeight = infoPanel.value && infoPanelRef.value ? infoPanelRef.value.offsetHeight : 0
|
|
170
|
-
|
|
171
|
-
// Prepare for image sizing calculation
|
|
172
|
-
|
|
173
|
-
imageElements.forEach((img) => {
|
|
174
|
-
// Reset to ensure proper recalculation
|
|
175
|
-
img.style.maxHeight = ''
|
|
176
|
-
img.style.maxWidth = ''
|
|
177
|
-
// Force browser to recalculate styles
|
|
178
|
-
void img.offsetHeight
|
|
179
|
-
|
|
180
|
-
// Apply exact pixel measurements based on actual UI elements
|
|
181
|
-
const topHeight = topControlsCurrentHeight || 0
|
|
182
|
-
const infoHeight = infoPanelCurrentHeight || 0
|
|
183
|
-
const availableHeightPx = windowHeight.value - topHeight - infoHeight - 32 // 32px for padding
|
|
184
|
-
|
|
185
|
-
if (windowWidth.value <= 768) {
|
|
186
|
-
// Mobile specific sizing - use pixel values directly
|
|
187
|
-
img.style.maxHeight = `${availableHeightPx}px`
|
|
188
|
-
img.style.maxWidth = '90vw'
|
|
189
|
-
}
|
|
190
|
-
else {
|
|
191
|
-
// Desktop sizing - account for sidebar if present
|
|
192
|
-
const sidebarWidth = sidePanel.value ? sidePanelRef.value?.offsetWidth || 256 : 0
|
|
193
|
-
const availableWidthPx = windowWidth.value - sidebarWidth - 48 // 48px for padding
|
|
194
|
-
|
|
195
|
-
img.style.maxHeight = `${availableHeightPx}px`
|
|
196
|
-
img.style.maxWidth = `${availableWidthPx}px`
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// For images, add special handling for natural dimensions
|
|
200
|
-
if (img instanceof HTMLImageElement) {
|
|
201
|
-
if (img.complete) {
|
|
202
|
-
adjustImageSize(img, availableHeightPx)
|
|
203
|
-
}
|
|
204
|
-
else {
|
|
205
|
-
img.onload = () => adjustImageSize(img, availableHeightPx)
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
})
|
|
209
|
-
})
|
|
210
|
-
}, 50)
|
|
145
|
+
const height = topControlsRef.value.offsetHeight || 0
|
|
146
|
+
document.documentElement.style.setProperty('--controls-height', `${height}px`)
|
|
147
|
+
}
|
|
211
148
|
|
|
212
|
-
//
|
|
213
|
-
const
|
|
214
|
-
//
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
149
|
+
// CSS variable-based image sizing
|
|
150
|
+
const updateImageSizes = useDebounceFn(() => {
|
|
151
|
+
// Update CSS variables first
|
|
152
|
+
updateInfoHeight()
|
|
153
|
+
updateControlsHeight()
|
|
154
|
+
|
|
155
|
+
// Set sidebar width variable
|
|
156
|
+
const sidebarWidthValue = sidePanel.value ? '16rem' : '0px'
|
|
157
|
+
document.documentElement.style.setProperty('--sidebar-width', sidebarWidthValue)
|
|
158
|
+
|
|
159
|
+
// No need to manipulate image elements directly - CSS variables will handle it
|
|
160
|
+
}, 10)
|
|
219
161
|
|
|
220
162
|
// Modal controls
|
|
221
163
|
function setModal(value: boolean) {
|
|
@@ -268,13 +210,14 @@ const openGalleryImage = useDebounceFn((index: number | undefined) => {
|
|
|
268
210
|
|
|
269
211
|
// Update layout after opening
|
|
270
212
|
nextTick(() => {
|
|
271
|
-
|
|
213
|
+
updateImageSizes()
|
|
272
214
|
})
|
|
273
215
|
}, 50)
|
|
274
216
|
|
|
275
217
|
// Navigation functions
|
|
276
218
|
function goNextImage() {
|
|
277
219
|
direction.value = 'next'
|
|
220
|
+
|
|
278
221
|
if (modelValue.value < props.images.length - 1) {
|
|
279
222
|
modelValue.value++
|
|
280
223
|
}
|
|
@@ -282,15 +225,11 @@ function goNextImage() {
|
|
|
282
225
|
modelValue.value = 0
|
|
283
226
|
}
|
|
284
227
|
resetControlsTimer()
|
|
285
|
-
|
|
286
|
-
// Force layout update when image changes
|
|
287
|
-
nextTick(() => {
|
|
288
|
-
updateLayout()
|
|
289
|
-
})
|
|
290
228
|
}
|
|
291
229
|
|
|
292
230
|
function goPrevImage() {
|
|
293
231
|
direction.value = 'prev'
|
|
232
|
+
|
|
294
233
|
if (modelValue.value > 0) {
|
|
295
234
|
modelValue.value--
|
|
296
235
|
}
|
|
@@ -298,11 +237,6 @@ function goPrevImage() {
|
|
|
298
237
|
modelValue.value = props.images.length - 1 > 0 ? props.images.length - 1 : 0
|
|
299
238
|
}
|
|
300
239
|
resetControlsTimer()
|
|
301
|
-
|
|
302
|
-
// Force layout update when image changes
|
|
303
|
-
nextTick(() => {
|
|
304
|
-
updateLayout()
|
|
305
|
-
})
|
|
306
240
|
}
|
|
307
241
|
|
|
308
242
|
// UI control functions
|
|
@@ -334,7 +268,7 @@ function toggleInfoPanel() {
|
|
|
334
268
|
|
|
335
269
|
// Update layout after panel toggle
|
|
336
270
|
nextTick(() => {
|
|
337
|
-
|
|
271
|
+
updateImageSizes()
|
|
338
272
|
})
|
|
339
273
|
}
|
|
340
274
|
|
|
@@ -344,7 +278,7 @@ function toggleSidePanel() {
|
|
|
344
278
|
|
|
345
279
|
// Update layout after panel toggle
|
|
346
280
|
nextTick(() => {
|
|
347
|
-
|
|
281
|
+
updateImageSizes()
|
|
348
282
|
})
|
|
349
283
|
}
|
|
350
284
|
|
|
@@ -357,7 +291,7 @@ function toggleFullscreen() {
|
|
|
357
291
|
// Give browser time to adjust fullscreen before updating sizing
|
|
358
292
|
if (fullscreenResizeTimeout) clearTimeout(fullscreenResizeTimeout)
|
|
359
293
|
fullscreenResizeTimeout = window.setTimeout(() => {
|
|
360
|
-
|
|
294
|
+
updateImageSizes()
|
|
361
295
|
}, 50)
|
|
362
296
|
})
|
|
363
297
|
.catch(() => {})
|
|
@@ -369,7 +303,7 @@ function toggleFullscreen() {
|
|
|
369
303
|
isFullscreen.value = false
|
|
370
304
|
if (fullscreenResizeTimeout) clearTimeout(fullscreenResizeTimeout)
|
|
371
305
|
fullscreenResizeTimeout = window.setTimeout(() => {
|
|
372
|
-
|
|
306
|
+
updateImageSizes()
|
|
373
307
|
}, 50)
|
|
374
308
|
})
|
|
375
309
|
.catch(() => {})
|
|
@@ -496,7 +430,7 @@ watch(
|
|
|
496
430
|
modelValue, // Watch for model value changes to update layout
|
|
497
431
|
],
|
|
498
432
|
() => {
|
|
499
|
-
|
|
433
|
+
updateImageSizes()
|
|
500
434
|
},
|
|
501
435
|
)
|
|
502
436
|
|
|
@@ -506,29 +440,27 @@ onMounted(() => {
|
|
|
506
440
|
eventBus.on(`${props.id}Gallery`, openGalleryImage)
|
|
507
441
|
eventBus.on(`${props.id}GalleryClose`, closeGallery)
|
|
508
442
|
|
|
509
|
-
// Initialize
|
|
510
|
-
|
|
511
|
-
updateLayout()
|
|
512
|
-
})
|
|
443
|
+
// Initialize CSS variables
|
|
444
|
+
updateImageSizes()
|
|
513
445
|
|
|
514
446
|
// Set up observers for dynamic resizing
|
|
515
447
|
if (topControlsRef.value) {
|
|
516
|
-
useResizeObserver(topControlsRef.value,
|
|
448
|
+
useResizeObserver(topControlsRef.value, updateImageSizes)
|
|
517
449
|
}
|
|
518
450
|
|
|
519
451
|
if (infoPanelRef.value) {
|
|
520
|
-
useResizeObserver(infoPanelRef.value,
|
|
452
|
+
useResizeObserver(infoPanelRef.value, updateImageSizes)
|
|
521
453
|
}
|
|
522
454
|
|
|
523
455
|
if (sidePanelRef.value) {
|
|
524
|
-
useResizeObserver(sidePanelRef.value,
|
|
456
|
+
useResizeObserver(sidePanelRef.value, updateImageSizes)
|
|
525
457
|
}
|
|
526
458
|
|
|
527
459
|
// Listen for fullscreen changes
|
|
528
460
|
useEventListener(document, 'fullscreenchange', () => {
|
|
529
461
|
isFullscreen.value = !!document.fullscreenElement
|
|
530
462
|
nextTick(() => {
|
|
531
|
-
|
|
463
|
+
updateImageSizes()
|
|
532
464
|
})
|
|
533
465
|
})
|
|
534
466
|
})
|
|
@@ -632,11 +564,10 @@ onUnmounted(() => {
|
|
|
632
564
|
<div
|
|
633
565
|
ref="galleryContentRef"
|
|
634
566
|
class="w-full h-full flex flex-col lg:flex-row"
|
|
635
|
-
style="margin-top: var(--controls-height, 0px)"
|
|
636
567
|
>
|
|
637
568
|
<!-- Main Image Area - Fills available space -->
|
|
638
569
|
<div
|
|
639
|
-
class="relative flex-1 h-full flex items-center justify-center"
|
|
570
|
+
class="relative flex-1 h-full flex items-center justify-center pt-[var(--controls-height)]"
|
|
640
571
|
:class="{ 'lg:pr-64': sidePanel, 'lg:max-w-[calc(100%-16rem)]': sidePanel }"
|
|
641
572
|
style="max-width: 100%;"
|
|
642
573
|
>
|
|
@@ -667,13 +598,14 @@ onUnmounted(() => {
|
|
|
667
598
|
<div
|
|
668
599
|
ref="imageContainerRef"
|
|
669
600
|
class="image-container flex-grow flex items-center justify-center"
|
|
670
|
-
:class="{ 'has-info': infoPanel }"
|
|
671
601
|
@touchstart="touchStart"
|
|
672
602
|
@touchend="touchEnd"
|
|
673
603
|
>
|
|
674
604
|
<transition
|
|
675
605
|
:name="direction === 'next' ? 'slide-next' : 'slide-prev'"
|
|
676
606
|
mode="out-in"
|
|
607
|
+
@before-enter="updateImageSizes"
|
|
608
|
+
@after-leave="updateImageSizes"
|
|
677
609
|
>
|
|
678
610
|
<div
|
|
679
611
|
:key="`image-display-${modelValue}`"
|
|
@@ -685,19 +617,17 @@ onUnmounted(() => {
|
|
|
685
617
|
<component
|
|
686
618
|
:is="videoComponent"
|
|
687
619
|
:src="isVideo(images[modelValue])"
|
|
688
|
-
class="shadow max-w-full
|
|
689
|
-
:style="{ maxHeight: availableHeight }"
|
|
620
|
+
class="shadow max-w-full video-component gallery-image"
|
|
690
621
|
/>
|
|
691
622
|
</ClientOnly>
|
|
692
623
|
</template>
|
|
693
624
|
<template v-else>
|
|
694
625
|
<img
|
|
695
626
|
v-if="modelValueSrc && imageComponent === 'img'"
|
|
696
|
-
class="shadow max-w-full
|
|
697
|
-
:style="{ maxHeight: availableHeight }"
|
|
627
|
+
class="shadow max-w-full gallery-image"
|
|
698
628
|
:src="modelValueSrc"
|
|
699
629
|
:alt="`Gallery image ${modelValue + 1}`"
|
|
700
|
-
@load="
|
|
630
|
+
@load="updateImageSizes"
|
|
701
631
|
>
|
|
702
632
|
<component
|
|
703
633
|
:is="imageComponent"
|
|
@@ -705,8 +635,7 @@ onUnmounted(() => {
|
|
|
705
635
|
:image="modelValueSrc.image"
|
|
706
636
|
:variant="modelValueSrc.variant"
|
|
707
637
|
:alt="modelValueSrc.alt"
|
|
708
|
-
class="shadow max-w-full
|
|
709
|
-
:style="{ maxHeight: availableHeight }"
|
|
638
|
+
class="shadow max-w-full gallery-image"
|
|
710
639
|
:likes="modelValueSrc.likes"
|
|
711
640
|
:show-likes="modelValueSrc.showLikes"
|
|
712
641
|
:is-author="modelValueSrc.isAuthor"
|
|
@@ -754,6 +683,7 @@ onUnmounted(() => {
|
|
|
754
683
|
v-if="infoPanel && images[modelValue]"
|
|
755
684
|
ref="infoPanelRef"
|
|
756
685
|
class="info-panel absolute bottom-0 left-0 right-0 px-4 py-3 backdrop-blur-md bg-fv-neutral-900/70 z-45"
|
|
686
|
+
@transitionend="updateImageSizes"
|
|
757
687
|
>
|
|
758
688
|
<slot :value="images[modelValue]" />
|
|
759
689
|
</div>
|
|
@@ -772,8 +702,7 @@ onUnmounted(() => {
|
|
|
772
702
|
<div
|
|
773
703
|
v-if="sidePanel"
|
|
774
704
|
ref="sidePanelRef"
|
|
775
|
-
class="side-panel hidden lg:block absolute right-0 top-0 bottom-0 w-64 bg-fv-neutral-800/90 backdrop-blur-md overflow-y-auto z-40 cool-scroll"
|
|
776
|
-
:style="{ 'padding-top': `${topControlsHeight}px` }"
|
|
705
|
+
class="side-panel hidden lg:block absolute right-0 top-0 bottom-0 w-64 bg-fv-neutral-800/90 backdrop-blur-md overflow-y-auto z-40 cool-scroll pt-[calc(var(--controls-height)+8px)]"
|
|
777
706
|
>
|
|
778
707
|
<!-- Paging Controls if needed -->
|
|
779
708
|
<div v-if="paging" class="flex items-center justify-center pt-2">
|
|
@@ -839,10 +768,13 @@ onUnmounted(() => {
|
|
|
839
768
|
>
|
|
840
769
|
<div
|
|
841
770
|
v-if="showControls && images.length > 1 && !sidePanel"
|
|
842
|
-
class="absolute bottom-0 left-0 right-0 p-
|
|
843
|
-
:class="{ 'pb-
|
|
771
|
+
class="absolute bottom-0 left-0 right-0 p-1 lg:hidden bg-gradient-to-t from-fv-neutral-900/90 to-transparent backdrop-blur-sm z-45"
|
|
772
|
+
:class="{ 'pb-4': infoPanel, 'pb-2': !infoPanel }"
|
|
773
|
+
@touchstart.stop
|
|
774
|
+
@touchmove.stop
|
|
775
|
+
@touchend.stop
|
|
844
776
|
>
|
|
845
|
-
<div class="overflow-x-auto flex space-x-2
|
|
777
|
+
<div class="overflow-x-auto flex space-x-2 px-1 no-scrollbar">
|
|
846
778
|
<div
|
|
847
779
|
v-for="(image, idx) in images"
|
|
848
780
|
:key="`mobile_thumb_${id}_${idx}`"
|
|
@@ -972,6 +904,13 @@ onUnmounted(() => {
|
|
|
972
904
|
</template>
|
|
973
905
|
|
|
974
906
|
<style scoped>
|
|
907
|
+
/* CSS variables for dimensions */
|
|
908
|
+
:root {
|
|
909
|
+
--controls-height: 0px;
|
|
910
|
+
--info-height: 0px;
|
|
911
|
+
--sidebar-width: 16rem;
|
|
912
|
+
}
|
|
913
|
+
|
|
975
914
|
/* Ensure controls stay fixed at top */
|
|
976
915
|
.controls-bar {
|
|
977
916
|
height: auto;
|
|
@@ -1002,11 +941,24 @@ onUnmounted(() => {
|
|
|
1002
941
|
border-top-right-radius: 0.5rem;
|
|
1003
942
|
}
|
|
1004
943
|
|
|
1005
|
-
/* Image sizing in different contexts */
|
|
1006
|
-
.image
|
|
1007
|
-
|
|
1008
|
-
transition: max-height 0.3s ease-out, max-width 0.3s ease-out;
|
|
944
|
+
/* Image sizing in different contexts - simplified approach like old component */
|
|
945
|
+
.gallery-image {
|
|
946
|
+
height: auto;
|
|
1009
947
|
object-fit: contain;
|
|
948
|
+
max-width: 92vw;
|
|
949
|
+
max-height: calc(80vh - var(--controls-height) - var(--info-height, 0px));
|
|
950
|
+
transition: max-height 0.3s ease-out, max-width 0.3s ease-out;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
@media (min-width: 1024px) {
|
|
954
|
+
.gallery-image {
|
|
955
|
+
max-width: calc(92vw - var(--sidebar-width) - 48px);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
/* Fullscreen mode sizing */
|
|
960
|
+
:is(.gallery-container[style*="fullscreen"]) .gallery-image {
|
|
961
|
+
max-height: calc(92vh - var(--controls-height) - var(--info-height, 0px));
|
|
1010
962
|
}
|
|
1011
963
|
|
|
1012
964
|
/* Transition styles for next (right) navigation */
|
|
@@ -1172,4 +1124,14 @@ onUnmounted(() => {
|
|
|
1172
1124
|
font-size: 0.75rem;
|
|
1173
1125
|
z-index: 10;
|
|
1174
1126
|
}
|
|
1127
|
+
|
|
1128
|
+
/* Special class to hide scrollbars on mobile */
|
|
1129
|
+
.no-scrollbar {
|
|
1130
|
+
-ms-overflow-style: none; /* IE and Edge */
|
|
1131
|
+
scrollbar-width: none; /* Firefox */
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
.no-scrollbar::-webkit-scrollbar {
|
|
1135
|
+
display: none; /* Chrome, Safari and Opera */
|
|
1136
|
+
}
|
|
1175
1137
|
</style>
|