srcdev-nuxt-components 2.3.1 → 2.4.0
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.
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component :is="tag" class="display-banner" :class="[elementClasses]">
|
|
3
|
+
<div v-if="$slots.canvas" class="canvas">
|
|
4
|
+
<slot name="canvas"></slot>
|
|
5
|
+
</div>
|
|
6
|
+
<div v-if="$slots.content" class="content">
|
|
7
|
+
<slot name="content"></slot>
|
|
8
|
+
</div>
|
|
9
|
+
</component>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<script lang="ts">
|
|
13
|
+
const TAGS_ALLOWED = <string[]>['div', 'p', 'span', 'section', 'article', 'aside', 'header', 'footer', 'main', 'nav', 'ul', 'ol'];
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<script setup lang="ts">
|
|
17
|
+
|
|
18
|
+
const props = defineProps({
|
|
19
|
+
tag: {
|
|
20
|
+
type: String,
|
|
21
|
+
default: 'div',
|
|
22
|
+
validator(value: string) {
|
|
23
|
+
return TAGS_ALLOWED.includes(value);
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
styleClassPassthrough: {
|
|
27
|
+
type: Array as PropType<string[]>,
|
|
28
|
+
default: () => [],
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
|
|
33
|
+
|
|
34
|
+
watch(
|
|
35
|
+
() => props.styleClassPassthrough,
|
|
36
|
+
() => {
|
|
37
|
+
resetElementClasses(props.styleClassPassthrough);
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<style lang="css">
|
|
43
|
+
.display-banner {
|
|
44
|
+
display: grid;
|
|
45
|
+
grid-template-areas: 'banner';
|
|
46
|
+
container-type: inline-size;
|
|
47
|
+
|
|
48
|
+
.canvas {
|
|
49
|
+
grid-area: banner;
|
|
50
|
+
|
|
51
|
+
.image {
|
|
52
|
+
object-fit: cover;
|
|
53
|
+
width: 100%;
|
|
54
|
+
height: 100%;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.content {
|
|
59
|
+
grid-area: banner;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
</style>
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="slider-gallery" :class="[elementClasses]" ref="sliderGalleryWrapper">
|
|
3
|
-
<div class="loading-state" :class="[{
|
|
3
|
+
<div class="loading-state" :class="[{ galleryLoaded: !galleryLoaded }]">
|
|
4
4
|
<div class="loading-spinner"></div>
|
|
5
5
|
<p>Loading gallery...</p>
|
|
6
6
|
</div>
|
|
7
7
|
|
|
8
|
-
<div v-if="showGallery" class="gallery-content" :class="[{
|
|
8
|
+
<div v-if="showGallery" class="gallery-content" :class="[{ galleryLoaded: !galleryLoaded }]">
|
|
9
9
|
<div class="list" ref="sliderGalleryImagesList">
|
|
10
10
|
<div v-for="(item, index) in galleryData" :key="index" class="item">
|
|
11
|
-
<NuxtImg :src="item.src" :alt="item.alt" @load="handleImageLoad(index)" @error="handleImageError(index)"
|
|
11
|
+
<NuxtImg :src="item.src" :alt="item.alt" @load="handleImageLoad(index)" @error="handleImageError(index)"
|
|
12
|
+
loading="lazy" />
|
|
12
13
|
<div class="content">
|
|
13
14
|
<div class="author">{{ item.stylist }}</div>
|
|
14
15
|
<div class="title">{{ item.title }}</div>
|
|
@@ -27,15 +28,20 @@
|
|
|
27
28
|
<img :src="item.src" :alt="item.alt" loading="lazy" />
|
|
28
29
|
<div class="content">
|
|
29
30
|
<div class="title" v-show="item.thumbnail?.title !== ''">{{ item.thumbnail?.title }}</div>
|
|
30
|
-
<div class="description" v-show="item.thumbnail?.description !== ''">{{ item.thumbnail?.description }}
|
|
31
|
+
<div class="description" v-show="item.thumbnail?.description !== ''">{{ item.thumbnail?.description }}
|
|
32
|
+
</div>
|
|
31
33
|
</div>
|
|
32
34
|
</div>
|
|
33
35
|
</div>
|
|
34
36
|
</div>
|
|
35
37
|
|
|
36
38
|
<div class="arrows">
|
|
37
|
-
<button id="prev" ref="prevDom" @click.prevent="doPrevious()"
|
|
38
|
-
|
|
39
|
+
<button id="prev" ref="prevDom" @click.prevent="doPrevious()">
|
|
40
|
+
<Icon name="ic:outline-keyboard-arrow-left" class="arrows-icon" />
|
|
41
|
+
</button>
|
|
42
|
+
<button id="next" ref="nextDom" @click.prevent="doNext()">
|
|
43
|
+
<Icon name="ic:outline-keyboard-arrow-right" class="arrows-icon" />
|
|
44
|
+
</button>
|
|
39
45
|
</div>
|
|
40
46
|
|
|
41
47
|
<div class="time"></div>
|
|
@@ -83,7 +89,8 @@ const sliderGalleryWrapper = useTemplateRef('sliderGalleryWrapper');
|
|
|
83
89
|
const sliderGalleryImagesList = useTemplateRef('sliderGalleryImagesList');
|
|
84
90
|
const sliderGalleryThumbnailsList = useTemplateRef('sliderGalleryThumbnailsList');
|
|
85
91
|
|
|
86
|
-
const
|
|
92
|
+
const transitionRunning = ref(false);
|
|
93
|
+
const galleryLoaded = ref(true);
|
|
87
94
|
const showGallery = ref(false);
|
|
88
95
|
const loadedImages = ref<Set<number>>(new Set());
|
|
89
96
|
const preloadedImages = ref<Array<HTMLImageElement>>([]);
|
|
@@ -93,7 +100,7 @@ onMounted(async () => {
|
|
|
93
100
|
|
|
94
101
|
// If no images or galleryData is empty, stop loading
|
|
95
102
|
if (!galleryData.value || galleryData.value.length === 0) {
|
|
96
|
-
|
|
103
|
+
galleryLoaded.value = false;
|
|
97
104
|
return;
|
|
98
105
|
}
|
|
99
106
|
|
|
@@ -127,27 +134,28 @@ onMounted(async () => {
|
|
|
127
134
|
await Promise.race(imageLoadPromises);
|
|
128
135
|
|
|
129
136
|
setTimeout(() => {
|
|
130
|
-
|
|
137
|
+
galleryLoaded.value = false;
|
|
131
138
|
}, 500);
|
|
139
|
+
|
|
140
|
+
showGallery.value = true;
|
|
141
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
132
142
|
});
|
|
133
143
|
|
|
134
144
|
const handleImageLoad = (index: number) => {
|
|
135
|
-
console.log('Image loaded:', index);
|
|
136
145
|
loadedImages.value.add(index);
|
|
137
146
|
};
|
|
138
147
|
|
|
139
148
|
const handleImageError = (index: number) => {
|
|
140
|
-
console.error(`Failed to load image at index ${index}`);
|
|
141
149
|
loadedImages.value.add(index);
|
|
142
150
|
};
|
|
143
151
|
|
|
144
152
|
const doNext = () => {
|
|
145
|
-
if (
|
|
153
|
+
if (transitionRunning.value) return;
|
|
146
154
|
showSlider('next');
|
|
147
155
|
};
|
|
148
156
|
|
|
149
157
|
const doPrevious = () => {
|
|
150
|
-
if (
|
|
158
|
+
if (transitionRunning.value) return;
|
|
151
159
|
showSlider('prev');
|
|
152
160
|
};
|
|
153
161
|
|
|
@@ -155,6 +163,8 @@ let runTimeOut: any;
|
|
|
155
163
|
let runNextAuto: any = null;
|
|
156
164
|
|
|
157
165
|
function showSlider(type: string) {
|
|
166
|
+
transitionRunning.value = true;
|
|
167
|
+
|
|
158
168
|
const currentSliderItems = Array.from(sliderGalleryImagesList.value?.children || []);
|
|
159
169
|
const currentThumbnailItems = Array.from(sliderGalleryThumbnailsList.value?.children || []);
|
|
160
170
|
|
|
@@ -189,31 +199,45 @@ function showSlider(type: string) {
|
|
|
189
199
|
|
|
190
200
|
clearTimeout(runTimeOut);
|
|
191
201
|
runTimeOut = setTimeout(() => {
|
|
192
|
-
|
|
202
|
+
|
|
193
203
|
if (sliderGalleryWrapper.value) {
|
|
194
204
|
sliderGalleryWrapper.value.classList.remove('next');
|
|
195
205
|
sliderGalleryWrapper.value.classList.remove('prev');
|
|
196
206
|
|
|
197
|
-
// Remove helper classes
|
|
198
207
|
const items = sliderGalleryImagesList.value?.querySelectorAll('.prepend-item');
|
|
199
208
|
items?.forEach((item) => item.classList.remove('prepend-item'));
|
|
200
209
|
|
|
201
210
|
const thumbs = sliderGalleryThumbnailsList.value?.querySelectorAll('.prepend-item');
|
|
202
211
|
thumbs?.forEach((thumb) => thumb.classList.remove('prepend-item'));
|
|
203
212
|
}
|
|
213
|
+
transitionRunning.value = false;
|
|
204
214
|
}, props.animationDuration);
|
|
205
215
|
|
|
206
216
|
// Reset auto-run timer
|
|
207
217
|
clearTimeout(runNextAuto);
|
|
208
218
|
runNextAuto = setTimeout(() => {
|
|
209
|
-
if (!props.autoRun ||
|
|
219
|
+
if (!props.autoRun || galleryLoaded.value) return;
|
|
210
220
|
doNext();
|
|
211
221
|
}, props.autoRunInterval);
|
|
212
222
|
}
|
|
213
223
|
|
|
224
|
+
// Add keyboard navigation event handlers
|
|
225
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
226
|
+
// Don't process key events if transition is running or gallery isn't loaded
|
|
227
|
+
if (transitionRunning.value || galleryLoaded.value) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (event.key === 'ArrowLeft') {
|
|
232
|
+
doPrevious();
|
|
233
|
+
} else if (event.key === 'ArrowRight') {
|
|
234
|
+
doNext();
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
214
238
|
// Initialize auto-run only after loading completes
|
|
215
|
-
watch(
|
|
216
|
-
if (!
|
|
239
|
+
watch(galleryLoaded, (previousValue, currentValue) => {
|
|
240
|
+
if (!currentValue && props.autoRun) {
|
|
217
241
|
clearTimeout(runNextAuto);
|
|
218
242
|
runNextAuto = setTimeout(() => {
|
|
219
243
|
doNext();
|
|
@@ -231,10 +255,7 @@ watch(
|
|
|
231
255
|
onBeforeUnmount(() => {
|
|
232
256
|
clearTimeout(runTimeOut);
|
|
233
257
|
clearTimeout(runNextAuto);
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
onMounted(() => {
|
|
237
|
-
showGallery.value = true;
|
|
258
|
+
window.removeEventListener('keydown', handleKeyDown);
|
|
238
259
|
});
|
|
239
260
|
</script>
|
|
240
261
|
|
|
@@ -258,6 +279,8 @@ onMounted(() => {
|
|
|
258
279
|
position: absolute;
|
|
259
280
|
inset: 0 0 0 0;
|
|
260
281
|
|
|
282
|
+
z-index: 9999;
|
|
283
|
+
|
|
261
284
|
.loading-state {
|
|
262
285
|
position: absolute;
|
|
263
286
|
inset: 0 0 0 0;
|
|
@@ -272,7 +295,7 @@ onMounted(() => {
|
|
|
272
295
|
transition: display 0.5s, opacity 0.5s;
|
|
273
296
|
transition-behavior: allow-discrete;
|
|
274
297
|
|
|
275
|
-
&.
|
|
298
|
+
&.galleryLoaded {
|
|
276
299
|
display: none;
|
|
277
300
|
opacity: 0;
|
|
278
301
|
}
|
|
@@ -300,7 +323,7 @@ onMounted(() => {
|
|
|
300
323
|
/* opacity: 0; */
|
|
301
324
|
/* transition: opacity 0.5s ease-in-out; */
|
|
302
325
|
|
|
303
|
-
&.
|
|
326
|
+
&.galleryLoaded {
|
|
304
327
|
/* opacity: 1; */
|
|
305
328
|
}
|
|
306
329
|
}
|
|
@@ -457,24 +480,46 @@ onMounted(() => {
|
|
|
457
480
|
width: 300px;
|
|
458
481
|
max-width: 30%;
|
|
459
482
|
display: flex;
|
|
460
|
-
gap:
|
|
483
|
+
gap: 20px;
|
|
461
484
|
align-items: center;
|
|
462
485
|
|
|
463
486
|
button {
|
|
487
|
+
display: grid;
|
|
488
|
+
justify-content: center;
|
|
489
|
+
align-items: center;
|
|
464
490
|
width: 40px;
|
|
465
491
|
height: 40px;
|
|
466
492
|
border-radius: 50%;
|
|
467
493
|
background-color: #eee4;
|
|
468
|
-
border: none;
|
|
469
494
|
color: #fff;
|
|
470
495
|
font-family: monospace;
|
|
471
496
|
font-weight: bold;
|
|
472
497
|
transition: 0.5s;
|
|
473
498
|
|
|
499
|
+
border-width: 2px;
|
|
500
|
+
border-style: solid;
|
|
501
|
+
border-color: white;
|
|
502
|
+
|
|
503
|
+
&#prev {
|
|
504
|
+
--_translateX: -2px;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
&#next {
|
|
508
|
+
--_translateX: 2px;
|
|
509
|
+
}
|
|
510
|
+
|
|
474
511
|
&:hover {
|
|
475
512
|
background-color: #fff;
|
|
476
513
|
color: #000;
|
|
477
514
|
}
|
|
515
|
+
|
|
516
|
+
.arrows-icon {
|
|
517
|
+
color: currentColor;
|
|
518
|
+
font-weight: 900;
|
|
519
|
+
height: 40px;
|
|
520
|
+
width: 40px;
|
|
521
|
+
translate: var(--_translateX) -3px;
|
|
522
|
+
}
|
|
478
523
|
}
|
|
479
524
|
}
|
|
480
525
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "srcdev-nuxt-components",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.4.0",
|
|
5
5
|
"main": "nuxt.config.ts",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"clean": "rm -rf .nuxt && rm -rf .output && rm -rf .playground/.nuxt && rm -rf .playground/.output",
|
|
@@ -27,18 +27,18 @@
|
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@iconify-json/akar-icons": "1.2.2",
|
|
29
29
|
"@iconify-json/bitcoin-icons": "1.2.2",
|
|
30
|
-
"@nuxt/eslint": "
|
|
31
|
-
"@nuxt/eslint-config": "1.
|
|
32
|
-
"@nuxt/icon": "1.
|
|
30
|
+
"@nuxt/eslint": "1.3.0",
|
|
31
|
+
"@nuxt/eslint-config": "1.3.0",
|
|
32
|
+
"@nuxt/icon": "1.12.0",
|
|
33
33
|
"@nuxt/image": "1.10.0",
|
|
34
34
|
"happy-dom": "16.8.1",
|
|
35
|
-
"nuxt": "3.
|
|
35
|
+
"nuxt": "3.17.2",
|
|
36
36
|
"release-it": "18.1.2",
|
|
37
|
-
"typescript": "5.8.
|
|
37
|
+
"typescript": "5.8.3"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@oddbird/css-anchor-positioning": "0.
|
|
41
|
-
"@vueuse/core": "13.
|
|
40
|
+
"@oddbird/css-anchor-positioning": "0.6.0",
|
|
41
|
+
"@vueuse/core": "13.1.0",
|
|
42
42
|
"focus-trap-vue": "4.0.3",
|
|
43
43
|
"modern-normalize": "3.0.1"
|
|
44
44
|
},
|