@fy-/fws-vue 2.0.84 → 2.0.86

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.
@@ -1,6 +1,14 @@
1
1
  <script setup lang="ts">
2
2
  import { Dialog, DialogPanel, TransitionRoot } from "@headlessui/vue";
3
- import { ref, onMounted, reactive, onUnmounted, h, computed } from "vue";
3
+ import {
4
+ ref,
5
+ onMounted,
6
+ reactive,
7
+ onUnmounted,
8
+ h,
9
+ computed,
10
+ defineComponent,
11
+ } from "vue";
4
12
  import type { APIPaging } from "../../composables/rest";
5
13
  import { useEventBus } from "../../composables/event-bus";
6
14
  import {
@@ -15,6 +23,8 @@ import type { Component } from "vue";
15
23
  const isGalleryOpen = ref<boolean>(false);
16
24
  const eventBus = useEventBus();
17
25
  const sidePanel = ref<boolean>(true);
26
+ const direction = ref("next"); // Possible values: 'next' or 'prev'
27
+
18
28
  const props = withDefaults(
19
29
  defineProps<{
20
30
  id: string;
@@ -69,20 +79,25 @@ const setModal = (value: boolean) => {
69
79
  isGalleryOpen.value = value;
70
80
  };
71
81
  const openGalleryImage = (index: number | undefined) => {
72
- if (index === undefined) modelValue.value = 0;
73
- else {
82
+ if (index === undefined) {
83
+ modelValue.value = 0;
84
+ } else {
85
+ direction.value = index > modelValue.value ? "next" : "prev";
74
86
  modelValue.value = parseInt(index.toString());
75
87
  }
76
88
  setModal(true);
77
89
  };
78
90
  const goNextImage = () => {
91
+ direction.value = "next";
79
92
  if (modelValue.value < props.images.length - 1) {
80
93
  modelValue.value++;
81
94
  } else {
82
95
  modelValue.value = 0;
83
96
  }
84
97
  };
98
+
85
99
  const goPrevImage = () => {
100
+ direction.value = "prev";
86
101
  if (modelValue.value > 0) {
87
102
  modelValue.value--;
88
103
  } else {
@@ -90,6 +105,7 @@ const goPrevImage = () => {
90
105
  props.images.length - 1 > 0 ? props.images.length - 1 : 0;
91
106
  }
92
107
  };
108
+
93
109
  const modelValueSrc = computed(() => {
94
110
  if (props.images.length == 0) return false;
95
111
  if (props.images[modelValue.value] == undefined) return false;
@@ -114,8 +130,10 @@ const touchEnd = (event: TouchEvent) => {
114
130
  // Add a threshold to prevent accidental swipes
115
131
  if (Math.abs(diffX) > Math.abs(diffY) && Math.abs(diffX) > 50) {
116
132
  if (diffX > 0) {
133
+ direction.value = "next";
117
134
  goNextImage();
118
135
  } else {
136
+ direction.value = "prev";
119
137
  goPrevImage();
120
138
  }
121
139
  }
@@ -133,16 +151,19 @@ const handleKeyboardInput = (event: KeyboardEvent) => {
133
151
  switch (event.key) {
134
152
  case "ArrowRight":
135
153
  isKeyPressed.value = true;
154
+ direction.value = "next";
136
155
  goNextImage();
137
156
  break;
138
157
  case "ArrowLeft":
139
158
  isKeyPressed.value = true;
159
+ direction.value = "prev";
140
160
  goPrevImage();
141
161
  break;
142
162
  default:
143
163
  break;
144
164
  }
145
165
  };
166
+
146
167
  const handleKeyboardRelease = (event: KeyboardEvent) => {
147
168
  if (event.key === "ArrowRight" || event.key === "ArrowLeft") {
148
169
  isKeyPressed.value = false;
@@ -169,6 +190,51 @@ onUnmounted(() => {
169
190
  window.removeEventListener("keyup", handleKeyboardRelease);
170
191
  }
171
192
  });
193
+ const transitionName = computed(() => {
194
+ return direction.value === "next" ? "slide-next" : "slide-prev";
195
+ });
196
+
197
+ const ImageComponent = defineComponent({
198
+ props: ["src", "imageComponent", "modelValueSrc"],
199
+ template: `
200
+ <div v-if="src" class="media-wrapper">
201
+ <img
202
+ v-if="imageComponent === 'img'"
203
+ :src="src"
204
+ class="shadow max-w-full h-auto object-contain max-h-[85vh]"
205
+ />
206
+ <component
207
+ v-else
208
+ :is="imageComponent"
209
+ :image="src.image"
210
+ :variant="src.variant"
211
+ :alt="src.alt"
212
+ class="shadow max-w-full h-auto object-contain max-h-[85vh]"
213
+ />
214
+ </div>
215
+ `,
216
+ });
217
+ const VideoComponentWrapper = defineComponent({
218
+ props: ["src", "videoComponent"],
219
+ template: `
220
+ <div v-if="src" class="media-wrapper">
221
+ <ClientOnly>
222
+ <component
223
+ :is="videoComponent"
224
+ :src="src"
225
+ class="shadow max-w-full h-auto object-contain max-h-[85vh]"
226
+ />
227
+ </ClientOnly>
228
+ </div>
229
+ `,
230
+ });
231
+ const currentMediaComponent = computed(() => {
232
+ if (props.videoComponent && props.isVideo(props.images[modelValue.value])) {
233
+ return VideoComponentWrapper;
234
+ } else {
235
+ return ImageComponent;
236
+ }
237
+ });
172
238
  </script>
173
239
  <template>
174
240
  <div>
@@ -221,36 +287,17 @@ onUnmounted(() => {
221
287
  @touchstart="touchStart"
222
288
  @touchend="touchEnd"
223
289
  >
224
- <div
225
- class="flex-1 w-full max-w-full flex items-center justify-center"
226
- >
227
- <template
228
- v-if="videoComponent && isVideo(images[modelValue])"
229
- >
230
- <ClientOnly>
231
- <component
232
- :is="videoComponent"
233
- :src="isVideo(images[modelValue])"
234
- class="shadow max-w-full h-auto object-contain max-h-[85vh]"
235
- />
236
- </ClientOnly>
237
- </template>
238
- <template v-else>
239
- <img
240
- class="shadow max-w-full h-auto object-contain max-h-[85vh]"
241
- :src="modelValueSrc"
242
- v-if="modelValueSrc && imageComponent == 'img'"
243
- />
244
- <component
245
- v-else-if="modelValueSrc && imageComponent"
246
- :is="imageComponent"
247
- :image="modelValueSrc.image"
248
- :variant="modelValueSrc.variant"
249
- :alt="modelValueSrc.alt"
250
- class="shadow max-w-full h-auto object-contain max-h-[85vh]"
251
- />
252
- </template>
253
- </div>
290
+ <transition :name="transitionName" mode="out-in">
291
+ <component
292
+ :is="currentMediaComponent"
293
+ :key="modelValue"
294
+ :src="modelValueSrc"
295
+ :imageComponent="imageComponent"
296
+ :videoComponent="videoComponent"
297
+ class="flex-1 w-full max-w-full flex items-center justify-center"
298
+ />
299
+ </transition>
300
+
254
301
  <div
255
302
  class="flex-0 py-2 flex items-center justify-center max-w-full w-full"
256
303
  >
@@ -416,3 +463,60 @@ onUnmounted(() => {
416
463
  </button>
417
464
  </div>
418
465
  </template>
466
+ <style scoped>
467
+ /* Transition styles for next (right) navigation */
468
+ .slide-next-enter-active,
469
+ .slide-next-leave-active {
470
+ transition:
471
+ opacity 0.5s,
472
+ transform 0.5s;
473
+ }
474
+
475
+ .slide-next-enter-from {
476
+ opacity: 0;
477
+ transform: translateX(100%);
478
+ }
479
+
480
+ .slide-next-enter-to {
481
+ opacity: 1;
482
+ transform: translateX(0);
483
+ }
484
+
485
+ .slide-next-leave-from {
486
+ opacity: 1;
487
+ transform: translateX(0);
488
+ }
489
+
490
+ .slide-next-leave-to {
491
+ opacity: 0;
492
+ transform: translateX(-100%);
493
+ }
494
+
495
+ /* Transition styles for prev (left) navigation */
496
+ .slide-prev-enter-active,
497
+ .slide-prev-leave-active {
498
+ transition:
499
+ opacity 0.5s,
500
+ transform 0.5s;
501
+ }
502
+
503
+ .slide-prev-enter-from {
504
+ opacity: 0;
505
+ transform: translateX(-100%);
506
+ }
507
+
508
+ .slide-prev-enter-to {
509
+ opacity: 1;
510
+ transform: translateX(0);
511
+ }
512
+
513
+ .slide-prev-leave-from {
514
+ opacity: 1;
515
+ transform: translateX(0);
516
+ }
517
+
518
+ .slide-prev-leave-to {
519
+ opacity: 0;
520
+ transform: translateX(100%);
521
+ }
522
+ </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fy-/fws-vue",
3
- "version": "2.0.84",
3
+ "version": "2.0.86",
4
4
  "author": "Florian 'Fy' Gasquez <m@fy.to>",
5
5
  "license": "MIT",
6
6
  "repository": {