@fy-/fws-vue 2.3.17 → 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 +70 -148
- 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,82 +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
|
-
const maxHeight = Math.min(img.naturalHeight, availableHeightPx)
|
|
154
|
-
img.style.maxHeight = `${maxHeight}px`
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Image size and positioning - improved to prevent overflow
|
|
159
|
-
const calculateImageSize = useDebounceFn(() => {
|
|
160
|
-
if (!imageContainerRef.value) return
|
|
161
|
-
|
|
162
|
-
// Execute immediately without waiting for nextTick to prevent delayed resizing
|
|
163
|
-
const imageElements = imageContainerRef.value?.querySelectorAll('.image-display img, .image-display .video-component') as NodeListOf<HTMLElement>
|
|
164
|
-
|
|
165
|
-
if (!imageElements || imageElements.length === 0) return
|
|
134
|
+
// Update CSS variables for layout consistency
|
|
135
|
+
function updateInfoHeight() {
|
|
136
|
+
if (!infoPanelRef.value) return
|
|
166
137
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
// Apply sizing to all images
|
|
172
|
-
imageElements.forEach((img) => {
|
|
173
|
-
// Reset immediately to ensure proper recalculation
|
|
174
|
-
img.style.maxHeight = '0'
|
|
175
|
-
img.style.maxWidth = '0'
|
|
138
|
+
const height = infoPanelRef.value.offsetHeight || 0
|
|
139
|
+
document.documentElement.style.setProperty('--info-height', `${height}px`)
|
|
140
|
+
}
|
|
176
141
|
|
|
177
|
-
|
|
178
|
-
|
|
142
|
+
function updateControlsHeight() {
|
|
143
|
+
if (!topControlsRef.value) return
|
|
179
144
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
// Increase padding for reliable containment - prevent overflow
|
|
184
|
-
const availableHeightPx = windowHeight.value - topHeight - infoHeight - 48
|
|
145
|
+
const height = topControlsRef.value.offsetHeight || 0
|
|
146
|
+
document.documentElement.style.setProperty('--controls-height', `${height}px`)
|
|
147
|
+
}
|
|
185
148
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
else {
|
|
192
|
-
// Desktop sizing - account for sidebar if present
|
|
193
|
-
const sidebarWidth = sidePanel.value ? sidePanelRef.value?.offsetWidth || 256 : 0
|
|
194
|
-
// More conservative width calculation to prevent overflow
|
|
195
|
-
const availableWidthPx = windowWidth.value - sidebarWidth - 64
|
|
149
|
+
// CSS variable-based image sizing
|
|
150
|
+
const updateImageSizes = useDebounceFn(() => {
|
|
151
|
+
// Update CSS variables first
|
|
152
|
+
updateInfoHeight()
|
|
153
|
+
updateControlsHeight()
|
|
196
154
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
155
|
+
// Set sidebar width variable
|
|
156
|
+
const sidebarWidthValue = sidePanel.value ? '16rem' : '0px'
|
|
157
|
+
document.documentElement.style.setProperty('--sidebar-width', sidebarWidthValue)
|
|
200
158
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if (img.complete) {
|
|
204
|
-
adjustImageSize(img, availableHeightPx)
|
|
205
|
-
}
|
|
206
|
-
else {
|
|
207
|
-
img.onload = () => adjustImageSize(img, availableHeightPx)
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
})
|
|
211
|
-
}, 10) // Reduced debounce time for faster response
|
|
212
|
-
|
|
213
|
-
// Update all layout measurements when any relevant state changes
|
|
214
|
-
// More aggressive calculation to prevent layout issues
|
|
215
|
-
const updateLayout = useDebounceFn(() => {
|
|
216
|
-
// Calculate immediately
|
|
217
|
-
calculateImageSize()
|
|
218
|
-
|
|
219
|
-
// Then again after a small delay to catch any delayed DOM updates
|
|
220
|
-
setTimeout(() => {
|
|
221
|
-
calculateImageSize()
|
|
222
|
-
// And one more time for extra safety after all transitions
|
|
223
|
-
setTimeout(() => calculateImageSize(), 100)
|
|
224
|
-
}, 10)
|
|
225
|
-
}, 10) // Reduced debounce time for faster response
|
|
159
|
+
// No need to manipulate image elements directly - CSS variables will handle it
|
|
160
|
+
}, 10)
|
|
226
161
|
|
|
227
162
|
// Modal controls
|
|
228
163
|
function setModal(value: boolean) {
|
|
@@ -275,18 +210,14 @@ const openGalleryImage = useDebounceFn((index: number | undefined) => {
|
|
|
275
210
|
|
|
276
211
|
// Update layout after opening
|
|
277
212
|
nextTick(() => {
|
|
278
|
-
|
|
213
|
+
updateImageSizes()
|
|
279
214
|
})
|
|
280
215
|
}, 50)
|
|
281
216
|
|
|
282
|
-
// Navigation functions
|
|
217
|
+
// Navigation functions
|
|
283
218
|
function goNextImage() {
|
|
284
219
|
direction.value = 'next'
|
|
285
220
|
|
|
286
|
-
// Calculate size immediately before changing the image
|
|
287
|
-
// This ensures we have proper sizing before transition starts
|
|
288
|
-
calculateImageSize()
|
|
289
|
-
|
|
290
221
|
if (modelValue.value < props.images.length - 1) {
|
|
291
222
|
modelValue.value++
|
|
292
223
|
}
|
|
@@ -294,24 +225,11 @@ function goNextImage() {
|
|
|
294
225
|
modelValue.value = 0
|
|
295
226
|
}
|
|
296
227
|
resetControlsTimer()
|
|
297
|
-
|
|
298
|
-
// Force immediate layout update when image changes
|
|
299
|
-
calculateImageSize()
|
|
300
|
-
|
|
301
|
-
// Another calculation after DOM is updated to ensure correct sizing
|
|
302
|
-
nextTick(() => {
|
|
303
|
-
calculateImageSize()
|
|
304
|
-
// Second calculation after a small delay to catch any late DOM changes
|
|
305
|
-
setTimeout(() => calculateImageSize(), 50)
|
|
306
|
-
})
|
|
307
228
|
}
|
|
308
229
|
|
|
309
230
|
function goPrevImage() {
|
|
310
231
|
direction.value = 'prev'
|
|
311
232
|
|
|
312
|
-
// Calculate size immediately before changing the image
|
|
313
|
-
calculateImageSize()
|
|
314
|
-
|
|
315
233
|
if (modelValue.value > 0) {
|
|
316
234
|
modelValue.value--
|
|
317
235
|
}
|
|
@@ -319,16 +237,6 @@ function goPrevImage() {
|
|
|
319
237
|
modelValue.value = props.images.length - 1 > 0 ? props.images.length - 1 : 0
|
|
320
238
|
}
|
|
321
239
|
resetControlsTimer()
|
|
322
|
-
|
|
323
|
-
// Force immediate layout update when image changes
|
|
324
|
-
calculateImageSize()
|
|
325
|
-
|
|
326
|
-
// Another calculation after DOM is updated to ensure correct sizing
|
|
327
|
-
nextTick(() => {
|
|
328
|
-
calculateImageSize()
|
|
329
|
-
// Second calculation after a small delay to catch any late DOM changes
|
|
330
|
-
setTimeout(() => calculateImageSize(), 50)
|
|
331
|
-
})
|
|
332
240
|
}
|
|
333
241
|
|
|
334
242
|
// UI control functions
|
|
@@ -360,7 +268,7 @@ function toggleInfoPanel() {
|
|
|
360
268
|
|
|
361
269
|
// Update layout after panel toggle
|
|
362
270
|
nextTick(() => {
|
|
363
|
-
|
|
271
|
+
updateImageSizes()
|
|
364
272
|
})
|
|
365
273
|
}
|
|
366
274
|
|
|
@@ -370,7 +278,7 @@ function toggleSidePanel() {
|
|
|
370
278
|
|
|
371
279
|
// Update layout after panel toggle
|
|
372
280
|
nextTick(() => {
|
|
373
|
-
|
|
281
|
+
updateImageSizes()
|
|
374
282
|
})
|
|
375
283
|
}
|
|
376
284
|
|
|
@@ -383,7 +291,7 @@ function toggleFullscreen() {
|
|
|
383
291
|
// Give browser time to adjust fullscreen before updating sizing
|
|
384
292
|
if (fullscreenResizeTimeout) clearTimeout(fullscreenResizeTimeout)
|
|
385
293
|
fullscreenResizeTimeout = window.setTimeout(() => {
|
|
386
|
-
|
|
294
|
+
updateImageSizes()
|
|
387
295
|
}, 50)
|
|
388
296
|
})
|
|
389
297
|
.catch(() => {})
|
|
@@ -395,7 +303,7 @@ function toggleFullscreen() {
|
|
|
395
303
|
isFullscreen.value = false
|
|
396
304
|
if (fullscreenResizeTimeout) clearTimeout(fullscreenResizeTimeout)
|
|
397
305
|
fullscreenResizeTimeout = window.setTimeout(() => {
|
|
398
|
-
|
|
306
|
+
updateImageSizes()
|
|
399
307
|
}, 50)
|
|
400
308
|
})
|
|
401
309
|
.catch(() => {})
|
|
@@ -522,7 +430,7 @@ watch(
|
|
|
522
430
|
modelValue, // Watch for model value changes to update layout
|
|
523
431
|
],
|
|
524
432
|
() => {
|
|
525
|
-
|
|
433
|
+
updateImageSizes()
|
|
526
434
|
},
|
|
527
435
|
)
|
|
528
436
|
|
|
@@ -532,29 +440,27 @@ onMounted(() => {
|
|
|
532
440
|
eventBus.on(`${props.id}Gallery`, openGalleryImage)
|
|
533
441
|
eventBus.on(`${props.id}GalleryClose`, closeGallery)
|
|
534
442
|
|
|
535
|
-
// Initialize
|
|
536
|
-
|
|
537
|
-
updateLayout()
|
|
538
|
-
})
|
|
443
|
+
// Initialize CSS variables
|
|
444
|
+
updateImageSizes()
|
|
539
445
|
|
|
540
446
|
// Set up observers for dynamic resizing
|
|
541
447
|
if (topControlsRef.value) {
|
|
542
|
-
useResizeObserver(topControlsRef.value,
|
|
448
|
+
useResizeObserver(topControlsRef.value, updateImageSizes)
|
|
543
449
|
}
|
|
544
450
|
|
|
545
451
|
if (infoPanelRef.value) {
|
|
546
|
-
useResizeObserver(infoPanelRef.value,
|
|
452
|
+
useResizeObserver(infoPanelRef.value, updateImageSizes)
|
|
547
453
|
}
|
|
548
454
|
|
|
549
455
|
if (sidePanelRef.value) {
|
|
550
|
-
useResizeObserver(sidePanelRef.value,
|
|
456
|
+
useResizeObserver(sidePanelRef.value, updateImageSizes)
|
|
551
457
|
}
|
|
552
458
|
|
|
553
459
|
// Listen for fullscreen changes
|
|
554
460
|
useEventListener(document, 'fullscreenchange', () => {
|
|
555
461
|
isFullscreen.value = !!document.fullscreenElement
|
|
556
462
|
nextTick(() => {
|
|
557
|
-
|
|
463
|
+
updateImageSizes()
|
|
558
464
|
})
|
|
559
465
|
})
|
|
560
466
|
})
|
|
@@ -658,11 +564,10 @@ onUnmounted(() => {
|
|
|
658
564
|
<div
|
|
659
565
|
ref="galleryContentRef"
|
|
660
566
|
class="w-full h-full flex flex-col lg:flex-row"
|
|
661
|
-
style="margin-top: var(--controls-height, 0px)"
|
|
662
567
|
>
|
|
663
568
|
<!-- Main Image Area - Fills available space -->
|
|
664
569
|
<div
|
|
665
|
-
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)]"
|
|
666
571
|
:class="{ 'lg:pr-64': sidePanel, 'lg:max-w-[calc(100%-16rem)]': sidePanel }"
|
|
667
572
|
style="max-width: 100%;"
|
|
668
573
|
>
|
|
@@ -693,18 +598,18 @@ onUnmounted(() => {
|
|
|
693
598
|
<div
|
|
694
599
|
ref="imageContainerRef"
|
|
695
600
|
class="image-container flex-grow flex items-center justify-center"
|
|
696
|
-
:class="{ 'has-info': infoPanel }"
|
|
697
601
|
@touchstart="touchStart"
|
|
698
602
|
@touchend="touchEnd"
|
|
699
603
|
>
|
|
700
604
|
<transition
|
|
701
605
|
:name="direction === 'next' ? 'slide-next' : 'slide-prev'"
|
|
702
606
|
mode="out-in"
|
|
607
|
+
@before-enter="updateImageSizes"
|
|
608
|
+
@after-leave="updateImageSizes"
|
|
703
609
|
>
|
|
704
610
|
<div
|
|
705
611
|
:key="`image-display-${modelValue}`"
|
|
706
612
|
class="image-display relative w-full h-full flex flex-col items-center justify-center"
|
|
707
|
-
@transitionend="calculateImageSize"
|
|
708
613
|
>
|
|
709
614
|
<!-- Actual Image/Video Content -->
|
|
710
615
|
<template v-if="videoComponent && isVideo(images[modelValue])">
|
|
@@ -712,19 +617,17 @@ onUnmounted(() => {
|
|
|
712
617
|
<component
|
|
713
618
|
:is="videoComponent"
|
|
714
619
|
:src="isVideo(images[modelValue])"
|
|
715
|
-
class="shadow max-w-full
|
|
716
|
-
:style="{ maxHeight: availableHeight }"
|
|
620
|
+
class="shadow max-w-full video-component gallery-image"
|
|
717
621
|
/>
|
|
718
622
|
</ClientOnly>
|
|
719
623
|
</template>
|
|
720
624
|
<template v-else>
|
|
721
625
|
<img
|
|
722
626
|
v-if="modelValueSrc && imageComponent === 'img'"
|
|
723
|
-
class="shadow max-w-full
|
|
724
|
-
:style="{ maxHeight: availableHeight }"
|
|
627
|
+
class="shadow max-w-full gallery-image"
|
|
725
628
|
:src="modelValueSrc"
|
|
726
629
|
:alt="`Gallery image ${modelValue + 1}`"
|
|
727
|
-
@load="
|
|
630
|
+
@load="updateImageSizes"
|
|
728
631
|
>
|
|
729
632
|
<component
|
|
730
633
|
:is="imageComponent"
|
|
@@ -732,8 +635,7 @@ onUnmounted(() => {
|
|
|
732
635
|
:image="modelValueSrc.image"
|
|
733
636
|
:variant="modelValueSrc.variant"
|
|
734
637
|
:alt="modelValueSrc.alt"
|
|
735
|
-
class="shadow max-w-full
|
|
736
|
-
:style="{ maxHeight: availableHeight }"
|
|
638
|
+
class="shadow max-w-full gallery-image"
|
|
737
639
|
:likes="modelValueSrc.likes"
|
|
738
640
|
:show-likes="modelValueSrc.showLikes"
|
|
739
641
|
:is-author="modelValueSrc.isAuthor"
|
|
@@ -781,6 +683,7 @@ onUnmounted(() => {
|
|
|
781
683
|
v-if="infoPanel && images[modelValue]"
|
|
782
684
|
ref="infoPanelRef"
|
|
783
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"
|
|
784
687
|
>
|
|
785
688
|
<slot :value="images[modelValue]" />
|
|
786
689
|
</div>
|
|
@@ -799,8 +702,7 @@ onUnmounted(() => {
|
|
|
799
702
|
<div
|
|
800
703
|
v-if="sidePanel"
|
|
801
704
|
ref="sidePanelRef"
|
|
802
|
-
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"
|
|
803
|
-
:style="{ 'padding-top': `${topControlsHeight + 8}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)]"
|
|
804
706
|
>
|
|
805
707
|
<!-- Paging Controls if needed -->
|
|
806
708
|
<div v-if="paging" class="flex items-center justify-center pt-2">
|
|
@@ -866,8 +768,8 @@ onUnmounted(() => {
|
|
|
866
768
|
>
|
|
867
769
|
<div
|
|
868
770
|
v-if="showControls && images.length > 1 && !sidePanel"
|
|
869
|
-
class="absolute bottom-0 left-0 right-0 p-1
|
|
870
|
-
:class="{ 'pb-4': infoPanel }"
|
|
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 }"
|
|
871
773
|
@touchstart.stop
|
|
872
774
|
@touchmove.stop
|
|
873
775
|
@touchend.stop
|
|
@@ -1002,6 +904,13 @@ onUnmounted(() => {
|
|
|
1002
904
|
</template>
|
|
1003
905
|
|
|
1004
906
|
<style scoped>
|
|
907
|
+
/* CSS variables for dimensions */
|
|
908
|
+
:root {
|
|
909
|
+
--controls-height: 0px;
|
|
910
|
+
--info-height: 0px;
|
|
911
|
+
--sidebar-width: 16rem;
|
|
912
|
+
}
|
|
913
|
+
|
|
1005
914
|
/* Ensure controls stay fixed at top */
|
|
1006
915
|
.controls-bar {
|
|
1007
916
|
height: auto;
|
|
@@ -1032,11 +941,24 @@ onUnmounted(() => {
|
|
|
1032
941
|
border-top-right-radius: 0.5rem;
|
|
1033
942
|
}
|
|
1034
943
|
|
|
1035
|
-
/* Image sizing in different contexts */
|
|
1036
|
-
.image
|
|
1037
|
-
|
|
1038
|
-
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;
|
|
1039
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));
|
|
1040
962
|
}
|
|
1041
963
|
|
|
1042
964
|
/* Transition styles for next (right) navigation */
|