@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.
@@ -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
- // Derived measurements
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
- // Helper function to adjust image size based on natural dimensions
151
- function adjustImageSize(img: HTMLImageElement, availableHeightPx: number) {
152
- if (img.naturalHeight > 0) {
153
- const maxHeight = Math.min(img.naturalHeight, availableHeightPx)
154
- img.style.maxHeight = `${maxHeight}px`
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
- // Image size and positioning
159
- const calculateImageSize = useDebounceFn(() => {
160
- if (!imageContainerRef.value) return
142
+ function updateControlsHeight() {
143
+ if (!topControlsRef.value) return
161
144
 
162
- nextTick(() => {
163
- const imageElements = imageContainerRef.value?.querySelectorAll('.image-display img, .image-display .video-component') as NodeListOf<HTMLElement>
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
- // Update all layout measurements when any relevant state changes
213
- const updateLayout = useDebounceFn(() => {
214
- // Recalculate image size with a small delay to ensure all DOM updates are processed
215
- setTimeout(() => {
216
- calculateImageSize()
217
- }, 10)
218
- }, 50)
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
- updateLayout()
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
- updateLayout()
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
- updateLayout()
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
- updateLayout()
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
- updateLayout()
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
- updateLayout()
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 layout
510
- nextTick(() => {
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, updateLayout)
448
+ useResizeObserver(topControlsRef.value, updateImageSizes)
517
449
  }
518
450
 
519
451
  if (infoPanelRef.value) {
520
- useResizeObserver(infoPanelRef.value, updateLayout)
452
+ useResizeObserver(infoPanelRef.value, updateImageSizes)
521
453
  }
522
454
 
523
455
  if (sidePanelRef.value) {
524
- useResizeObserver(sidePanelRef.value, updateLayout)
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
- updateLayout()
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 h-auto object-contain video-component"
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 h-auto object-contain"
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="updateLayout"
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 h-auto object-contain"
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-2 lg:hidden bg-gradient-to-t from-fv-neutral-900/90 to-transparent backdrop-blur-sm z-45"
843
- :class="{ 'pb-20': 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 }"
773
+ @touchstart.stop
774
+ @touchmove.stop
775
+ @touchend.stop
844
776
  >
845
- <div class="overflow-x-auto flex space-x-2 pb-1 px-1">
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-display img,
1007
- .image-display .video-component {
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>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fy-/fws-vue",
3
- "version": "2.3.16",
3
+ "version": "2.3.18",
4
4
  "author": "Florian 'Fy' Gasquez <m@fy.to>",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/fy-to/FWJS#readme",