@wyxos/vibe 1.6.8 → 1.6.10
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/lib/index.js +442 -434
- package/package.json +1 -1
- package/src/components/MasonryItem.vue +34 -18
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { ref, onMounted, onUnmounted, watch, computed, withDefaults } from 'vue';
|
|
2
|
+
import { ref, onMounted, onUnmounted, watch, computed, withDefaults, nextTick } from 'vue';
|
|
3
3
|
|
|
4
4
|
const props = withDefaults(defineProps<{
|
|
5
5
|
item: any;
|
|
@@ -20,6 +20,7 @@ const videoError = ref(false);
|
|
|
20
20
|
const videoSrc = ref<string | null>(null);
|
|
21
21
|
const isInView = ref(false);
|
|
22
22
|
const isLoading = ref(false);
|
|
23
|
+
const showMedia = ref(false); // Controls fade-in animation
|
|
23
24
|
const containerRef = ref<HTMLElement | null>(null);
|
|
24
25
|
let intersectionObserver: IntersectionObserver | null = null;
|
|
25
26
|
|
|
@@ -43,10 +44,14 @@ function preloadImage(src: string): Promise<void> {
|
|
|
43
44
|
const remaining = Math.max(0, minLoadTime - elapsed);
|
|
44
45
|
|
|
45
46
|
// Ensure spinner shows for at least minLoadTime
|
|
46
|
-
setTimeout(() => {
|
|
47
|
+
setTimeout(async () => {
|
|
47
48
|
imageLoaded.value = true;
|
|
48
49
|
imageError.value = false;
|
|
49
50
|
isLoading.value = false;
|
|
51
|
+
// Wait for Vue to update DOM, then trigger fade-in
|
|
52
|
+
await nextTick();
|
|
53
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
54
|
+
showMedia.value = true;
|
|
50
55
|
resolve();
|
|
51
56
|
}, remaining);
|
|
52
57
|
};
|
|
@@ -78,10 +83,14 @@ function preloadVideo(src: string): Promise<void> {
|
|
|
78
83
|
const elapsed = Date.now() - startTime;
|
|
79
84
|
const remaining = Math.max(0, minLoadTime - elapsed);
|
|
80
85
|
|
|
81
|
-
setTimeout(() => {
|
|
86
|
+
setTimeout(async () => {
|
|
82
87
|
videoLoaded.value = true;
|
|
83
88
|
videoError.value = false;
|
|
84
89
|
isLoading.value = false;
|
|
90
|
+
// Wait for Vue to update DOM, then trigger fade-in
|
|
91
|
+
await nextTick();
|
|
92
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
93
|
+
showMedia.value = true;
|
|
85
94
|
resolve();
|
|
86
95
|
}, remaining);
|
|
87
96
|
};
|
|
@@ -117,6 +126,7 @@ async function startPreloading() {
|
|
|
117
126
|
if (!src) return;
|
|
118
127
|
|
|
119
128
|
isLoading.value = true;
|
|
129
|
+
showMedia.value = false; // Reset fade-in state
|
|
120
130
|
|
|
121
131
|
if (mediaType.value === 'video') {
|
|
122
132
|
videoSrc.value = src;
|
|
@@ -239,21 +249,29 @@ watch(
|
|
|
239
249
|
|
|
240
250
|
<!-- Media content (image or video) -->
|
|
241
251
|
<div v-else class="relative w-full h-full">
|
|
242
|
-
<!-- Image (
|
|
252
|
+
<!-- Image (always rendered when src exists, fades in when loaded) -->
|
|
243
253
|
<img
|
|
244
|
-
v-if="mediaType === 'image' &&
|
|
254
|
+
v-if="mediaType === 'image' && imageSrc"
|
|
245
255
|
:src="imageSrc"
|
|
246
|
-
class="
|
|
256
|
+
:class="[
|
|
257
|
+
'w-full h-full object-cover transition-opacity duration-700 ease-in-out group-hover:scale-105',
|
|
258
|
+
imageLoaded && showMedia ? 'opacity-100' : 'opacity-0'
|
|
259
|
+
]"
|
|
260
|
+
style="position: absolute; top: 0; left: 0;"
|
|
247
261
|
loading="lazy"
|
|
248
262
|
decoding="async"
|
|
249
263
|
alt=""
|
|
250
264
|
/>
|
|
251
265
|
|
|
252
|
-
<!-- Video (
|
|
266
|
+
<!-- Video (always rendered when src exists, fades in when loaded) -->
|
|
253
267
|
<video
|
|
254
|
-
v-if="mediaType === 'video' &&
|
|
268
|
+
v-if="mediaType === 'video' && videoSrc"
|
|
255
269
|
:src="videoSrc"
|
|
256
|
-
class="
|
|
270
|
+
:class="[
|
|
271
|
+
'w-full h-full object-cover transition-opacity duration-700 ease-in-out group-hover:scale-105',
|
|
272
|
+
videoLoaded && showMedia ? 'opacity-100' : 'opacity-0'
|
|
273
|
+
]"
|
|
274
|
+
style="position: absolute; top: 0; left: 0;"
|
|
257
275
|
muted
|
|
258
276
|
loop
|
|
259
277
|
playsinline
|
|
@@ -262,19 +280,17 @@ watch(
|
|
|
262
280
|
@error="videoError = true"
|
|
263
281
|
/>
|
|
264
282
|
|
|
265
|
-
<!-- Placeholder background while loading or if not loaded yet -->
|
|
283
|
+
<!-- Placeholder background while loading or if not loaded yet (fades out when media appears) -->
|
|
266
284
|
<div
|
|
267
285
|
v-if="!imageLoaded && !videoLoaded && !imageError && !videoError"
|
|
268
|
-
class="
|
|
286
|
+
:class="[
|
|
287
|
+
'absolute inset-0 bg-slate-100 flex items-center justify-center transition-opacity duration-500',
|
|
288
|
+
showMedia ? 'opacity-0 pointer-events-none' : 'opacity-100'
|
|
289
|
+
]"
|
|
269
290
|
>
|
|
270
291
|
<!-- Media type indicator - shown BEFORE preloading starts -->
|
|
271
|
-
<div
|
|
272
|
-
class="
|
|
273
|
-
>
|
|
274
|
-
<div class="w-12 h-12 rounded-full bg-white/80 backdrop-blur-sm flex items-center justify-center shadow-sm">
|
|
275
|
-
<i :class="mediaType === 'video' ? 'fas fa-video text-xl' : 'fas fa-image text-xl'"></i>
|
|
276
|
-
</div>
|
|
277
|
-
<span class="text-xs font-medium uppercase">{{ mediaType }}</span>
|
|
292
|
+
<div class="w-12 h-12 rounded-full bg-white/80 backdrop-blur-sm flex items-center justify-center shadow-sm">
|
|
293
|
+
<i :class="mediaType === 'video' ? 'fas fa-video text-xl text-slate-400' : 'fas fa-image text-xl text-slate-400'"></i>
|
|
278
294
|
</div>
|
|
279
295
|
</div>
|
|
280
296
|
|