@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.
- package/components/ui/DefaultGallery.vue +137 -33
- package/package.json +1 -1
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { Dialog, DialogPanel, TransitionRoot } from "@headlessui/vue";
|
|
3
|
-
import {
|
|
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)
|
|
73
|
-
|
|
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
|
-
<
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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>
|