@fy-/fws-vue 2.3.49 → 2.3.51

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.
@@ -48,6 +48,9 @@ const state = reactive({
48
48
  PublicBio: userData.value?.UserProfile?.PublicBio || false,
49
49
  PublicBirthdate: userData.value?.UserProfile?.PublicBirthdate || false,
50
50
  },
51
+ usernameUpdate: {
52
+ Username: userData.value?.UserProfile?.Username || '',
53
+ },
51
54
  })
52
55
  watchEffect(() => {
53
56
  state.userData = {
@@ -78,6 +81,11 @@ const rules = {
78
81
  PublicBio: {},
79
82
  PublicBirthdate: {},
80
83
  },
84
+ usernameUpdate: {
85
+ Username: {
86
+ required,
87
+ },
88
+ },
81
89
  }
82
90
  const v$ = useVuelidate(rules, state)
83
91
 
@@ -111,6 +119,36 @@ const cropResult = reactive({
111
119
  blobURL: '',
112
120
  })
113
121
  const uploader = ref(new Uploader())
122
+ const canUpdateUsername = computed(() => {
123
+ // If user and UserProfile and UserProfile.UsernameChangedAt > 30 days
124
+ if (userData.value?.UserProfile?.UsernameChangedAt?.unixms) {
125
+ const date = new Date(userData.value?.UserProfile?.UsernameChangedAt.unixms)
126
+ const now = new Date()
127
+ const diff = Math.abs(now.getTime() - date.getTime())
128
+ const diffDays = Math.ceil(diff / (1000 * 3600 * 24))
129
+ return diffDays > 30
130
+ }
131
+ return true
132
+ })
133
+ const lastUsernameError = ref('')
134
+ async function updateUsername() {
135
+ eventBus.emit('main-loading', true)
136
+ if (await v$.value.usernameUpdate.$validate()) {
137
+ const data = { ...state.usernameUpdate }
138
+ const response = await rest('User/_Username', 'PATCH', data).catch((err) => {
139
+ eventBus.emit('main-loading', false)
140
+ lastUsernameError.value = err.message
141
+ })
142
+ if (response && response.result === 'success') {
143
+ if (props.onCompleted) {
144
+ props.onCompleted(response)
145
+ }
146
+ eventBus.emit('user:refresh', true)
147
+ eventBus.emit('updateUsernameModal', false)
148
+ }
149
+ }
150
+ eventBus.emit('main-loading', false)
151
+ }
114
152
 
115
153
  async function getCropResult() {
116
154
  if (!cropper) return
@@ -161,6 +199,30 @@ function selectFile(e: Event) {
161
199
 
162
200
  <template>
163
201
  <form class="space-y-4" @submit.prevent="patchUser">
202
+ <DefaultModal id="updateUsername" :title="$t('fws_username_update_title')">
203
+ <div class="flex flex-col gap-4">
204
+ <DefaultInput
205
+ id="usernameFWS"
206
+ v-model="state.usernameUpdate.Username"
207
+ type="text"
208
+ :label="$t('fws_username_label')"
209
+ :help="$t('fws_username_help')"
210
+ :error-vuelidate="v$.usernameUpdate.Username.$errors"
211
+ :disabled="!canUpdateUsername"
212
+ />
213
+ <div v-if="lastUsernameError" class="text-xs text-red-500">
214
+ {{ lastUsernameError }}
215
+ </div>
216
+ <div class="flex justify-end pt-2">
217
+ <button type="submit" class="btn defaults primary flex items-center gap-2" @click="updateUsername">
218
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
219
+ <path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
220
+ </svg>
221
+ {{ $t("fws_save_user_cta") }}
222
+ </button>
223
+ </div>
224
+ </div>
225
+ </DefaultModal>
164
226
  <div class="bg-white dark:bg-fv-neutral-900 p-4 sm:p-6 rounded-lg shadow-sm border border-fv-neutral-200 dark:border-fv-neutral-700">
165
227
  <h3 class="text-lg font-semibold text-fv-neutral-900 dark:text-white mb-4 pb-2 border-b border-fv-neutral-200 dark:border-fv-neutral-700">
166
228
  {{ $t('fws_profile_heading') || $t('fws_your_profile') }}
@@ -221,6 +283,18 @@ function selectFile(e: Event) {
221
283
  :error-vuelidate="v$.userData.Username.$errors"
222
284
  :disabled="userData?.UserProfile?.HasUsernameAndSlug ? true : false"
223
285
  />
286
+ <div v-if="!canUpdateUsername" class="text-xs text-fv-neutral-500 dark:text-fv-neutral-400 mt-1">
287
+ {{ $t('fws_username_update_help') }}
288
+ </div>
289
+ <div v-else>
290
+ <button
291
+ type="button"
292
+ class="btn small primary mt-1"
293
+ @click="$eventBus.emit('updateUsernameModal', true)"
294
+ >
295
+ {{ $t('fws_username_update_cta') }}
296
+ </button>
297
+ </div>
224
298
  </div>
225
299
  </div>
226
300
 
@@ -1,4 +1,3 @@
1
- # This file has been completely rewritten and optimized
2
1
  <script setup lang="ts">
3
2
  import type { Component } from 'vue'
4
3
  import type { APIPaging } from '../../composables/rest'
@@ -129,29 +128,46 @@ const currentImage = computed(() => {
129
128
  const imageCount = computed(() => props.images.length)
130
129
  const currentIndex = computed(() => modelValue.value + 1)
131
130
 
132
- // Simple image sizing that relies on flexbox layout to handle the info panel
131
+ /**
132
+ * Dynamically update the size of the displayed image/video
133
+ * so it fits inside the viewport, taking into account:
134
+ * - Top controls
135
+ * - Info panel (if open)
136
+ * - Side panel (if open)
137
+ * - A little padding
138
+ */
133
139
  const updateImageSizes = useDebounceFn(() => {
134
- // Get the main image
135
- const mainImage = document.querySelector('.image-display img') as HTMLImageElement
136
- if (!mainImage) return
140
+ // Only adjust if gallery is open
141
+ if (!isGalleryOpen.value) return
142
+
143
+ // Find the container and the main media element (img or video)
144
+ const container = document.querySelector('.image-display') as HTMLDivElement
145
+ if (!container) return
146
+ const mainMedia = container.querySelector('img, video') as HTMLElement | null
147
+ if (!mainMedia) return
137
148
 
138
149
  // Standard padding
139
150
  const padding = 24
140
151
 
141
- // Handle width constraints
152
+ // Side panel width if visible
142
153
  const sidebarWidth = sidePanel.value ? 256 : 0
154
+
155
+ // Calculate available width
143
156
  const availableWidth = windowWidth.value - sidebarWidth - padding * 2
144
157
 
145
- // Set width constraints
146
- mainImage.style.maxWidth = windowWidth.value <= 768
147
- ? '90vw'
148
- : `${availableWidth}px`
158
+ // Set max width
159
+ mainMedia.style.maxWidth
160
+ = windowWidth.value <= 768 ? '90vw' : `${availableWidth}px`
149
161
 
150
- // Let height be auto to preserve aspect ratio and fit in flexbox container
151
- mainImage.style.height = 'auto'
162
+ // Preserve aspect ratio; let height be auto
163
+ mainMedia.style.height = 'auto'
152
164
 
153
- // Set a reasonable max-height if needed
154
- mainImage.style.maxHeight = '75vh'
165
+ // Deduct top controls height + info panel height (if open) + padding
166
+ const topSpace = topControlsHeight.value || 0
167
+ const infoSpace = infoPanel.value ? (infoPanelHeight.value || 0) : 0
168
+ const availableHeight = windowHeight.value - topSpace - infoSpace - padding * 2
169
+
170
+ mainMedia.style.maxHeight = `${availableHeight}px`
155
171
  }, 50)
156
172
 
157
173
  // Modal controls
@@ -164,8 +180,6 @@ function setModal(value: boolean) {
164
180
  useEventListener(document, 'keydown', handleKeyboardInput)
165
181
  useEventListener(document, 'keyup', handleKeyboardRelease)
166
182
  }
167
-
168
- // No longer auto-hide controls on mobile
169
183
  }
170
184
  else {
171
185
  if (props.onClose) props.onClose()
@@ -185,7 +199,6 @@ function setModal(value: boolean) {
185
199
  }
186
200
  isGalleryOpen.value = value
187
201
  showControls.value = true
188
- // Don't reset info panel state when opening/closing
189
202
  }
190
203
 
191
204
  // Open gallery with debounce to prevent accidental double-clicks
@@ -207,7 +220,6 @@ const openGalleryImage = useDebounceFn((index: number | undefined) => {
207
220
  // Navigation functions
208
221
  function goNextImage() {
209
222
  direction.value = 'next'
210
-
211
223
  if (modelValue.value < props.images.length - 1) {
212
224
  modelValue.value++
213
225
  }
@@ -216,7 +228,6 @@ function goNextImage() {
216
228
  }
217
229
  resetControlsTimer()
218
230
 
219
- // Force image sizing update after navigation
220
231
  nextTick(() => {
221
232
  updateImageSizes()
222
233
  })
@@ -224,7 +235,6 @@ function goNextImage() {
224
235
 
225
236
  function goPrevImage() {
226
237
  direction.value = 'prev'
227
-
228
238
  if (modelValue.value > 0) {
229
239
  modelValue.value--
230
240
  }
@@ -233,7 +243,6 @@ function goPrevImage() {
233
243
  }
234
244
  resetControlsTimer()
235
245
 
236
- // Force image sizing update after navigation
237
246
  nextTick(() => {
238
247
  updateImageSizes()
239
248
  })
@@ -245,11 +254,6 @@ function resetControlsTimer() {
245
254
  showControls.value = true
246
255
  }
247
256
 
248
- // eslint-disable-next-line unused-imports/no-unused-vars
249
- function toggleControls() {
250
- showControls.value = !showControls.value
251
- }
252
-
253
257
  function toggleInfoPanel() {
254
258
  infoPanel.value = !infoPanel.value
255
259
  resetControlsTimer()
@@ -257,10 +261,8 @@ function toggleInfoPanel() {
257
261
  // Update layout immediately AND after nextTick to ensure DOM updates
258
262
  updateImageSizes()
259
263
 
260
- // Schedule multiple updates to handle any transition effects
261
264
  nextTick(() => {
262
265
  updateImageSizes()
263
-
264
266
  // Additional delayed updates to catch transitions
265
267
  setTimeout(() => updateImageSizes(), 50)
266
268
  setTimeout(() => updateImageSizes(), 300)
@@ -271,7 +273,6 @@ function toggleSidePanel() {
271
273
  sidePanel.value = !sidePanel.value
272
274
  resetControlsTimer()
273
275
 
274
- // Update layout after panel toggle
275
276
  nextTick(() => {
276
277
  updateImageSizes()
277
278
  })
@@ -283,7 +284,6 @@ function toggleFullscreen() {
283
284
  enterFullscreen()
284
285
  .then(() => {
285
286
  isFullscreen.value = true
286
- // Give browser time to adjust fullscreen before updating sizing
287
287
  if (fullscreenResizeTimeout) clearTimeout(fullscreenResizeTimeout)
288
288
  fullscreenResizeTimeout = window.setTimeout(() => {
289
289
  updateImageSizes()
@@ -311,12 +311,11 @@ const touchStart = useDebounceFn((event: TouchEvent) => {
311
311
  const touch = event.touches[0]
312
312
  const targetElement = touch.target as HTMLElement
313
313
 
314
- // Store start time for tap detection
315
314
  touchStartTime.value = Date.now()
316
315
 
317
- // Check if the touch started on an interactive element
316
+ // Ignore swipes if starting on an interactive element
318
317
  if (targetElement.closest('button, a, input, textarea, select')) {
319
- return // Don't handle swipe if interacting with controls
318
+ return
320
319
  }
321
320
 
322
321
  start.x = touch.screenX
@@ -328,9 +327,9 @@ const touchEnd = useDebounceFn((event: TouchEvent) => {
328
327
  const targetElement = touch.target as HTMLElement
329
328
  const touchDuration = Date.now() - touchStartTime.value
330
329
 
331
- // Check if the touch ended on an interactive element
330
+ // Ignore swipes if ending on an interactive element
332
331
  if (targetElement.closest('button, a, input, textarea, select')) {
333
- return // Don't handle swipe if interacting with controls
332
+ return
334
333
  }
335
334
 
336
335
  const end = { x: touch.screenX, y: touch.screenY }
@@ -338,12 +337,12 @@ const touchEnd = useDebounceFn((event: TouchEvent) => {
338
337
  const diffX = start.x - end.x
339
338
  const diffY = start.y - end.y
340
339
 
341
- // For taps, we don't toggle controls anymore - they always stay visible
340
+ // If it's a quick tap (not a swipe), do nothing
342
341
  if (Math.abs(diffX) < 10 && Math.abs(diffY) < 10 && touchDuration < 300) {
343
342
  return
344
343
  }
345
344
 
346
- // Add a threshold to prevent accidental swipes
345
+ // Left/right swipe
347
346
  if (Math.abs(diffX) > Math.abs(diffY) && Math.abs(diffX) > 50) {
348
347
  if (diffX > 0) {
349
348
  goNextImage()
@@ -401,14 +400,14 @@ function closeGallery() {
401
400
  setModal(false)
402
401
  }
403
402
 
404
- // Click outside gallery content to close - with debounce to prevent accidental closes
403
+ // Click outside gallery content to close - with debounce
405
404
  const handleBackdropClick = useDebounceFn((event: MouseEvent) => {
406
405
  if (event.target === event.currentTarget) {
407
406
  setModal(false)
408
407
  }
409
408
  }, 200)
410
409
 
411
- // Watch for image changes, fullscreen, or panel visibility changes
410
+ // Watch for changes that affect sizing
412
411
  watch(
413
412
  [
414
413
  currentImage,
@@ -462,7 +461,7 @@ onUnmounted(() => {
462
461
  eventBus.off(`${props.id}GalleryClose`, closeGallery)
463
462
 
464
463
  if (!import.meta.env.SSR) {
465
- document.body.style.overflow = '' // Ensure body scrolling is restored
464
+ document.body.style.overflow = '' // Restore scrolling
466
465
  }
467
466
 
468
467
  // Clear any remaining timeouts
@@ -500,7 +499,7 @@ onUnmounted(() => {
500
499
  aria-modal="true"
501
500
  @click="handleBackdropClick"
502
501
  >
503
- <!-- Top Controls Bar - Fixed at top -->
502
+ <!-- Top Controls Bar -->
504
503
  <transition
505
504
  enter-active-class="transition-opacity duration-300"
506
505
  enter-from-class="opacity-0"
@@ -551,19 +550,19 @@ onUnmounted(() => {
551
550
  </div>
552
551
  </transition>
553
552
 
554
- <!-- Main Gallery Content - Flexbox layout -->
553
+ <!-- Main Gallery Content -->
555
554
  <div
556
555
  ref="galleryContentRef"
557
556
  class="w-full h-full flex flex-col lg:flex-row"
558
557
  >
559
- <!-- Main Image Area with flex column layout -->
558
+ <!-- Main Image Area -->
560
559
  <div
561
560
  class="relative flex-1 h-full flex flex-col"
562
561
  :style="{ paddingTop: `${topControlsHeight}px` }"
563
562
  :class="{ 'lg:pr-64': sidePanel, 'lg:max-w-[calc(100%-16rem)]': sidePanel }"
564
563
  style="max-width: 100%;"
565
564
  >
566
- <!-- Image Navigation Controls - Left -->
565
+ <!-- Left Navigation (Previous) -->
567
566
  <transition
568
567
  enter-active-class="transition-opacity duration-300"
569
568
  enter-from-class="opacity-0"
@@ -586,7 +585,7 @@ onUnmounted(() => {
586
585
  </div>
587
586
  </transition>
588
587
 
589
- <!-- Image Container - flex-grow to fill available space -->
588
+ <!-- Image/Video Container -->
590
589
  <div
591
590
  ref="imageContainerRef"
592
591
  class="flex-grow flex items-center justify-center"
@@ -603,17 +602,19 @@ onUnmounted(() => {
603
602
  :key="`image-display-${modelValue}`"
604
603
  class="image-display relative w-full h-full flex flex-col items-center justify-center"
605
604
  >
606
- <!-- Actual Image/Video Content -->
605
+ <!-- If video -->
607
606
  <template v-if="videoComponent && isVideo(images[modelValue])">
608
607
  <ClientOnly>
609
608
  <component
610
609
  :is="videoComponent"
611
610
  :src="isVideo(images[modelValue])"
612
611
  class="shadow max-w-full h-auto object-contain video-component"
613
- @load="updateImageSizes"
612
+ @loadedmetadata="updateImageSizes"
613
+ @loadeddata="updateImageSizes"
614
614
  />
615
615
  </ClientOnly>
616
616
  </template>
617
+ <!-- Otherwise, image -->
617
618
  <template v-else>
618
619
  <img
619
620
  v-if="modelValueSrc && imageComponent === 'img'"
@@ -635,7 +636,7 @@ onUnmounted(() => {
635
636
  </transition>
636
637
  </div>
637
638
 
638
- <!-- Image Navigation Controls - Right -->
639
+ <!-- Right Navigation (Next) -->
639
640
  <transition
640
641
  enter-active-class="transition-opacity duration-300"
641
642
  enter-from-class="opacity-0"
@@ -659,7 +660,7 @@ onUnmounted(() => {
659
660
  </div>
660
661
  </transition>
661
662
 
662
- <!-- Info Panel directly in flex column flow -->
663
+ <!-- Info Panel -->
663
664
  <transition
664
665
  enter-active-class="transition-all duration-300 ease-out"
665
666
  enter-from-class="opacity-0 transform translate-y-4"
@@ -678,7 +679,7 @@ onUnmounted(() => {
678
679
  </transition>
679
680
  </div>
680
681
 
681
- <!-- Side Thumbnails Panel -->
682
+ <!-- Side Thumbnails Panel (Desktop) -->
682
683
  <transition
683
684
  enter-active-class="transform transition ease-in-out duration-300"
684
685
  enter-from-class="translate-x-full"
@@ -693,12 +694,12 @@ onUnmounted(() => {
693
694
  class="side-panel hidden lg:block absolute right-0 top-0 bottom-0 w-64 overflow-y-auto z-40 cool-scroll"
694
695
  :style="{ paddingTop: `${topControlsHeight + 8}px` }"
695
696
  >
696
- <!-- Paging Controls if needed -->
697
+ <!-- Paging Controls -->
697
698
  <div v-if="paging" class="flex items-center justify-center pt-2">
698
699
  <DefaultPaging :id="id" :items="paging" />
699
700
  </div>
700
701
 
701
- <!-- Thumbnail Grid -->
702
+ <!-- Thumbnails -->
702
703
  <div class="grid grid-cols-2 gap-2 p-2">
703
704
  <div
704
705
  v-for="i in images.length"
@@ -746,7 +747,7 @@ onUnmounted(() => {
746
747
  </div>
747
748
  </transition>
748
749
 
749
- <!-- Mobile Thumbnail Preview (bottom of screen on mobile) -->
750
+ <!-- Mobile Thumbnail Row (Bottom) -->
750
751
  <transition
751
752
  enter-active-class="transition-transform duration-300 ease-out"
752
753
  enter-from-class="translate-y-full"
@@ -799,7 +800,7 @@ onUnmounted(() => {
799
800
  </div>
800
801
  </transition>
801
802
 
802
- <!-- Thumbnail Grid/Mason/Custom Layouts for non-opened gallery -->
803
+ <!-- Thumbnail Grid/Masonry/Custom Layouts if gallery is not open -->
803
804
  <div v-if="mode === 'grid' || mode === 'mason' || mode === 'custom'" class="gallery-grid">
804
805
  <div
805
806
  :class="{
@@ -809,8 +810,11 @@ onUnmounted(() => {
809
810
  }"
810
811
  >
811
812
  <slot name="thumbnail" />
813
+
814
+ <!-- Iterate images -->
812
815
  <template v-for="i in images.length" :key="`g_${id}_${i}`">
813
816
  <template v-if="mode === 'mason'">
817
+ <!-- Example naive "masonry" approach -->
814
818
  <div
815
819
  v-if="i + (1 % gridHeight) === 0"
816
820
  class="masonry-column relative"
@@ -818,7 +822,6 @@ onUnmounted(() => {
818
822
  <div v-if="ranking" class="img-gallery-ranking">
819
823
  {{ i }}
820
824
  </div>
821
-
822
825
  <template v-for="j in gridHeight" :key="`gi_${id}_${i + j}`">
823
826
  <div class="masonry-item">
824
827
  <img
@@ -893,12 +896,10 @@ onUnmounted(() => {
893
896
  </template>
894
897
 
895
898
  <style scoped>
896
- /* Ensure controls stay fixed at top */
897
899
  .controls-bar {
898
900
  height: auto;
899
901
  }
900
902
 
901
- /* Layout container for main image and info panel */
902
903
  .image-container {
903
904
  position: relative;
904
905
  display: flex;
@@ -909,87 +910,74 @@ onUnmounted(() => {
909
910
  width: 100%;
910
911
  }
911
912
 
912
- /* Side panel positioning */
913
913
  .side-panel {
914
914
  height: 100vh;
915
915
  overflow-y: auto;
916
916
  overflow-x: hidden;
917
917
  }
918
918
 
919
- /* Info panel styling */
920
919
  .info-panel {
921
920
  width: 100%;
922
921
  border-top-left-radius: 0.5rem;
923
922
  border-top-right-radius: 0.5rem;
924
923
  }
925
924
 
926
- /* Transition styles for next (right) navigation */
925
+ /* Transitions for next/prev images */
927
926
  .slide-next-enter-active,
928
- .slide-next-leave-active {
927
+ .slide-next-leave-active,
928
+ .slide-prev-enter-active,
929
+ .slide-prev-leave-active {
929
930
  transition:
930
931
  opacity 0.15s,
931
932
  transform 0.15s,
932
933
  filter 0.15s;
933
934
  }
934
935
 
936
+ /* Next (slide from right) */
935
937
  .slide-next-enter-from {
936
938
  opacity: 0;
937
939
  transform: translateX(30px);
938
940
  filter: blur(8px);
939
941
  }
940
-
941
942
  .slide-next-enter-to {
942
943
  opacity: 1;
943
944
  transform: translateX(0);
944
945
  filter: blur(0);
945
946
  }
946
-
947
947
  .slide-next-leave-from {
948
948
  opacity: 1;
949
949
  transform: translateX(0);
950
950
  filter: blur(0);
951
951
  }
952
-
953
952
  .slide-next-leave-to {
954
953
  opacity: 0;
955
954
  transform: translateX(-30px);
956
955
  filter: blur(8px);
957
956
  }
958
957
 
959
- /* Transition styles for prev (left) navigation */
960
- .slide-prev-enter-active,
961
- .slide-prev-leave-active {
962
- transition:
963
- opacity 0.15s,
964
- transform 0.15s,
965
- filter 0.15s;
966
- }
967
-
958
+ /* Prev (slide from left) */
968
959
  .slide-prev-enter-from {
969
960
  opacity: 0;
970
961
  transform: translateX(-30px);
971
962
  filter: blur(8px);
972
963
  }
973
-
974
964
  .slide-prev-enter-to {
975
965
  opacity: 1;
976
966
  transform: translateX(0);
977
967
  filter: blur(0);
978
968
  }
979
-
980
969
  .slide-prev-leave-from {
981
970
  opacity: 1;
982
971
  transform: translateX(0);
983
972
  filter: blur(0);
984
973
  }
985
-
986
974
  .slide-prev-leave-to {
987
975
  opacity: 0;
988
976
  transform: translateX(30px);
989
977
  filter: blur(8px);
990
978
  }
991
979
 
992
- /* Grid layouts for thumbnails */
980
+ /* Grid layouts */
993
981
  .gallery-grid {
994
982
  min-height: 200px;
995
983
  }
@@ -999,32 +987,27 @@ onUnmounted(() => {
999
987
  grid-template-columns: repeat(1, 1fr);
1000
988
  gap: 0.75rem;
1001
989
  }
1002
-
1003
990
  @media (min-width: 480px) {
1004
991
  .standard-grid {
1005
992
  grid-template-columns: repeat(2, 1fr);
1006
993
  }
1007
994
  }
1008
-
1009
995
  @media (min-width: 768px) {
1010
996
  .standard-grid {
1011
997
  grid-template-columns: repeat(3, 1fr);
1012
998
  gap: 1rem;
1013
999
  }
1014
1000
  }
1015
-
1016
1001
  @media (min-width: 1024px) {
1017
1002
  .standard-grid {
1018
1003
  grid-template-columns: repeat(4, 1fr);
1019
1004
  }
1020
1005
  }
1021
-
1022
1006
  @media (min-width: 1280px) {
1023
1007
  .standard-grid {
1024
1008
  grid-template-columns: repeat(5, 1fr);
1025
1009
  }
1026
1010
  }
1027
-
1028
1011
  @media (min-width: 1536px) {
1029
1012
  .standard-grid {
1030
1013
  grid-template-columns: repeat(6, 1fr);
@@ -1036,20 +1019,17 @@ onUnmounted(() => {
1036
1019
  grid-template-columns: repeat(1, 1fr);
1037
1020
  gap: 0.75rem;
1038
1021
  }
1039
-
1040
1022
  @media (min-width: 480px) {
1041
1023
  .masonry-grid {
1042
1024
  grid-template-columns: repeat(2, 1fr);
1043
1025
  }
1044
1026
  }
1045
-
1046
1027
  @media (min-width: 768px) {
1047
1028
  .masonry-grid {
1048
1029
  grid-template-columns: repeat(3, 1fr);
1049
1030
  gap: 1rem;
1050
1031
  }
1051
1032
  }
1052
-
1053
1033
  @media (min-width: 1024px) {
1054
1034
  .masonry-grid {
1055
1035
  grid-template-columns: repeat(4, 1fr);
@@ -1087,13 +1067,12 @@ onUnmounted(() => {
1087
1067
  z-index: 10;
1088
1068
  }
1089
1069
 
1090
- /* Special class to hide scrollbars on mobile */
1070
+ /* Hide scrollbars on mobile */
1091
1071
  .no-scrollbar {
1092
1072
  -ms-overflow-style: none; /* IE and Edge */
1093
1073
  scrollbar-width: none; /* Firefox */
1094
1074
  }
1095
-
1096
1075
  .no-scrollbar::-webkit-scrollbar {
1097
- display: none; /* Chrome, Safari and Opera */
1076
+ display: none; /* Chrome, Safari, and Opera */
1098
1077
  }
1099
1078
  </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fy-/fws-vue",
3
- "version": "2.3.49",
3
+ "version": "2.3.51",
4
4
  "author": "Florian 'Fy' Gasquez <m@fy.to>",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/fy-to/FWJS#readme",