@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.
- package/components/ui/DefaultGallery.vue +137 -36
- 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 {
|
|
@@ -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)
|
|
74
|
-
|
|
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
|
-
<
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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>
|