@fy-/fws-vue 2.0.85 → 2.0.87

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 {
@@ -12,10 +20,11 @@ import {
12
20
  } from "@heroicons/vue/24/solid";
13
21
  import DefaultPaging from "./DefaultPaging.vue";
14
22
  import type { Component } from "vue";
15
- import CollapseTransition from "./transitions/CollapseTransition.vue";
16
23
  const isGalleryOpen = ref<boolean>(false);
17
24
  const eventBus = useEventBus();
18
25
  const sidePanel = ref<boolean>(true);
26
+ const direction = ref("next"); // Possible values: 'next' or 'prev'
27
+
19
28
  const props = withDefaults(
20
29
  defineProps<{
21
30
  id: string;
@@ -70,20 +79,25 @@ const setModal = (value: boolean) => {
70
79
  isGalleryOpen.value = value;
71
80
  };
72
81
  const openGalleryImage = (index: number | undefined) => {
73
- if (index === undefined) modelValue.value = 0;
74
- else {
82
+ if (index === undefined) {
83
+ modelValue.value = 0;
84
+ } else {
85
+ direction.value = index > modelValue.value ? "next" : "prev";
75
86
  modelValue.value = parseInt(index.toString());
76
87
  }
77
88
  setModal(true);
78
89
  };
79
90
  const goNextImage = () => {
91
+ direction.value = "next";
80
92
  if (modelValue.value < props.images.length - 1) {
81
93
  modelValue.value++;
82
94
  } else {
83
95
  modelValue.value = 0;
84
96
  }
85
97
  };
98
+
86
99
  const goPrevImage = () => {
100
+ direction.value = "prev";
87
101
  if (modelValue.value > 0) {
88
102
  modelValue.value--;
89
103
  } else {
@@ -91,6 +105,7 @@ const goPrevImage = () => {
91
105
  props.images.length - 1 > 0 ? props.images.length - 1 : 0;
92
106
  }
93
107
  };
108
+
94
109
  const modelValueSrc = computed(() => {
95
110
  if (props.images.length == 0) return false;
96
111
  if (props.images[modelValue.value] == undefined) return false;
@@ -115,8 +130,10 @@ const touchEnd = (event: TouchEvent) => {
115
130
  // Add a threshold to prevent accidental swipes
116
131
  if (Math.abs(diffX) > Math.abs(diffY) && Math.abs(diffX) > 50) {
117
132
  if (diffX > 0) {
133
+ direction.value = "next";
118
134
  goNextImage();
119
135
  } else {
136
+ direction.value = "prev";
120
137
  goPrevImage();
121
138
  }
122
139
  }
@@ -134,16 +151,19 @@ const handleKeyboardInput = (event: KeyboardEvent) => {
134
151
  switch (event.key) {
135
152
  case "ArrowRight":
136
153
  isKeyPressed.value = true;
154
+ direction.value = "next";
137
155
  goNextImage();
138
156
  break;
139
157
  case "ArrowLeft":
140
158
  isKeyPressed.value = true;
159
+ direction.value = "prev";
141
160
  goPrevImage();
142
161
  break;
143
162
  default:
144
163
  break;
145
164
  }
146
165
  };
166
+
147
167
  const handleKeyboardRelease = (event: KeyboardEvent) => {
148
168
  if (event.key === "ArrowRight" || event.key === "ArrowLeft") {
149
169
  isKeyPressed.value = false;
@@ -170,6 +190,51 @@ onUnmounted(() => {
170
190
  window.removeEventListener("keyup", handleKeyboardRelease);
171
191
  }
172
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
+ class="shadow max-w-full h-auto object-contain max-h-[85vh]"
203
+ :src="modelValueSrc"
204
+ v-if="modelValueSrc && imageComponent == 'img'"
205
+ />
206
+ <component
207
+ v-else-if="modelValueSrc && imageComponent"
208
+ :is="imageComponent"
209
+ :image="modelValueSrc.image"
210
+ :variant="modelValueSrc.variant"
211
+ :alt="modelValueSrc.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="isVideo(images[modelValue])"
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
+ });
173
238
  </script>
174
239
  <template>
175
240
  <div>
@@ -222,38 +287,17 @@ onUnmounted(() => {
222
287
  @touchstart="touchStart"
223
288
  @touchend="touchEnd"
224
289
  >
225
- <div
226
- class="flex-1 w-full max-w-full flex items-center justify-center"
227
- >
228
- <CollapseTransition>
229
- <template
230
- v-if="videoComponent && isVideo(images[modelValue])"
231
- >
232
- <ClientOnly>
233
- <component
234
- :is="videoComponent"
235
- :src="isVideo(images[modelValue])"
236
- class="shadow max-w-full h-auto object-contain max-h-[85vh]"
237
- />
238
- </ClientOnly>
239
- </template>
240
- <template v-else>
241
- <img
242
- class="shadow max-w-full h-auto object-contain max-h-[85vh]"
243
- :src="modelValueSrc"
244
- v-if="modelValueSrc && imageComponent == 'img'"
245
- />
246
- <component
247
- v-else-if="modelValueSrc && imageComponent"
248
- :is="imageComponent"
249
- :image="modelValueSrc.image"
250
- :variant="modelValueSrc.variant"
251
- :alt="modelValueSrc.alt"
252
- class="shadow max-w-full h-auto object-contain max-h-[85vh]"
253
- />
254
- </template>
255
- </CollapseTransition>
256
- </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
+
257
301
  <div
258
302
  class="flex-0 py-2 flex items-center justify-center max-w-full w-full"
259
303
  >
@@ -419,3 +463,60 @@ onUnmounted(() => {
419
463
  </button>
420
464
  </div>
421
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.85",
3
+ "version": "2.0.87",
4
4
  "author": "Florian 'Fy' Gasquez <m@fy.to>",
5
5
  "license": "MIT",
6
6
  "repository": {