srcdev-nuxt-components 4.0.0 → 4.0.2

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.
@@ -9,6 +9,7 @@ html {
9
9
 
10
10
  font-size: 62.5%;
11
11
  scrollbar-gutter: stable;
12
+ overflow-x: hidden;
12
13
  }
13
14
  body {
14
15
  color: var(--grayscale-text-body);
@@ -18,6 +19,8 @@ body {
18
19
  }
19
20
 
20
21
  body {
22
+ overflow-x: hidden;
23
+
21
24
  &.lock {
22
25
  overflow: hidden;
23
26
  }
@@ -0,0 +1,263 @@
1
+ <template>
2
+ <section class="carousel-basic" :class="[elementClasses]" ref="carouselWrapperRef" role="region" aria-label="Image carousel">
3
+ <!-- Screen reader announcement for current item -->
4
+ <div aria-live="polite" aria-atomic="true" class="sr-only">Item {{ currentIndex + 1 }} of {{ itemCount }}</div>
5
+
6
+ <LayoutRow tag="div" variant="full-width" :style-class-passthrough="['mbe-20']">
7
+ <div tabindex="0" class="item-container" :class="{ 'allow-overflow': allowCarouselOverflow }" ref="carouselContainerRef" role="group" aria-label="Carousel items">
8
+ <div v-for="(item, index) in carouselDataIds" :key="index" class="item" ref="carouselItems" :aria-current="currentIndex === index ? 'true' : 'false'">
9
+ <slot :name="item"></slot>
10
+ </div>
11
+ </div>
12
+ </LayoutRow>
13
+
14
+ <LayoutRow tag="div" variant="full-width" :style-class-passthrough="['mbe-20']">
15
+ <div class="timeline-container">
16
+ <div v-for="index in itemCount" :key="index" class="timeline-item">
17
+ <div class="count">Step {{ index }}</div>
18
+ </div>
19
+ </div>
20
+ </LayoutRow>
21
+
22
+ <LayoutRow tag="div" variant="full-width" :style-class-passthrough="['mbe-20']">
23
+ <div tabindex="0" class="controls-container" ref="controlsContainerRef">
24
+ <div class="markers-container">
25
+ <ul class="markers-list">
26
+ <li v-for="index in itemCount" :key="index" class="markers-item">
27
+ <button @click.prevent="jumpToFrame(index - 1)" class="btn-marker" :class="[{ active: currentIndex === index - 1 }]" :aria-label="`Jump to item ${Math.floor(index + 1)}`"></button>
28
+ </li>
29
+ </ul>
30
+ </div>
31
+ <div class="buttons-container">
32
+ <button type="button" @click.prevent="actionPrevious()" class="btn-action" aria-label="Go to previous item">Prev</button>
33
+ <button type="button" @click.prevent="actionNext()" class="btn-action" aria-label="Go to next item">Next</button>
34
+ </div>
35
+ </div>
36
+ </LayoutRow>
37
+ </section>
38
+ </template>
39
+
40
+ <script setup lang="ts">
41
+ import { useEventListener, useResizeObserver, useSwipe } from '@vueuse/core';
42
+
43
+ const props = defineProps({
44
+ carouselDataIds: {
45
+ type: Array as PropType<string[]>,
46
+ default: () => [],
47
+ },
48
+ styleClassPassthrough: {
49
+ type: Array as PropType<string[]>,
50
+ default: () => [],
51
+ },
52
+ transitionSpeed: {
53
+ type: Number,
54
+ default: 200,
55
+ },
56
+ allowCarouselOverflow: {
57
+ type: Boolean,
58
+ default: false,
59
+ },
60
+ });
61
+
62
+ const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
63
+
64
+ const carouselWrapperRef = ref<HTMLDivElement | null>(null);
65
+ const carouselContainerRef = ref<HTMLDivElement | null>(null);
66
+ const carouselItemsRef = useTemplateRef<HTMLDivElement[]>('carouselItems');
67
+ const controlsContainerRef = ref<HTMLDivElement | null>(null);
68
+ const carouselInitComplete = ref(false);
69
+
70
+ const currentIndex = ref(0);
71
+ const itemCount = ref(props.carouselDataIds.length);
72
+ const offset = ref(0);
73
+ const transitionSpeedStr = props.transitionSpeed + 'ms';
74
+ const itemTransform = computed(() => {
75
+ return `translateX(calc(${offset.value} * (${itemWidth.value} + var(--_item-gap))))`;
76
+ });
77
+
78
+ const itemWidth = ref('0px');
79
+
80
+ const actionPrevious = () => {
81
+ if (offset.value >= 0) {
82
+ return;
83
+ }
84
+
85
+ offset.value = Math.min(offset.value + 1);
86
+ doAction();
87
+ };
88
+
89
+ const actionNext = () => {
90
+ if (offset.value <= -1 * (itemCount.value - 1)) {
91
+ return;
92
+ }
93
+
94
+ offset.value = Math.min(offset.value - 1);
95
+ doAction();
96
+ };
97
+
98
+ const doAction = () => {
99
+ currentIndex.value = Math.abs(offset.value);
100
+ };
101
+
102
+ const jumpToFrame = (index: number) => {
103
+ if (index >= 0 && index < itemCount.value) {
104
+ offset.value = -index;
105
+ doAction();
106
+ }
107
+ };
108
+
109
+ const initialSetup = () => {
110
+ if (carouselItemsRef?.value && carouselItemsRef.value.length > 0 && carouselItemsRef.value[0]) {
111
+ itemWidth.value = carouselItemsRef.value[0].offsetWidth + 'px';
112
+ }
113
+
114
+ carouselInitComplete.value = true;
115
+ };
116
+
117
+ const { direction } = useSwipe(carouselContainerRef, {
118
+ passive: false,
119
+ onSwipeEnd() {
120
+ if (direction.value === 'left') {
121
+ actionNext();
122
+ } else if (direction.value === 'right') {
123
+ actionPrevious();
124
+ }
125
+ },
126
+ });
127
+
128
+ useEventListener(carouselContainerRef, 'keydown', (event: KeyboardEvent) => {
129
+ if (event.key === 'ArrowLeft') {
130
+ actionPrevious();
131
+ } else if (event.key === 'ArrowRight') {
132
+ actionNext();
133
+ }
134
+ });
135
+
136
+ useEventListener(controlsContainerRef, 'keydown', (event: KeyboardEvent) => {
137
+ if (event.key === 'ArrowLeft') {
138
+ actionPrevious();
139
+ } else if (event.key === 'ArrowRight') {
140
+ actionNext();
141
+ }
142
+ });
143
+
144
+ useResizeObserver(carouselWrapperRef, async () => {
145
+ initialSetup();
146
+ });
147
+
148
+ onMounted(() => {
149
+ initialSetup();
150
+ });
151
+ </script>
152
+
153
+ <style lang="css">
154
+ .carousel-basic {
155
+ --_item-gap: 10px;
156
+
157
+ display: grid;
158
+ grid-template-columns: 1fr;
159
+ gap: 10px;
160
+
161
+ .sr-only {
162
+ position: absolute;
163
+ width: 1px;
164
+ height: 1px;
165
+ padding: 0;
166
+ margin: -1px;
167
+ overflow: hidden;
168
+ clip: rect(0, 0, 0, 0);
169
+ white-space: nowrap;
170
+ border: 0;
171
+ }
172
+
173
+ .timeline-container {
174
+ display: flex;
175
+ gap: var(--_item-gap);
176
+ overflow-x: hidden;
177
+
178
+ .timeline-item {
179
+ display: flex;
180
+ flex: 0 0 100%;
181
+ max-inline-size: 800px;
182
+ align-items: center;
183
+ transform: v-bind(itemTransform);
184
+ position: relative;
185
+
186
+ &::before {
187
+ content: '';
188
+ position: absolute;
189
+ height: 2px;
190
+ background-color: #fff;
191
+ left: 70px;
192
+ right: 0;
193
+ }
194
+
195
+ .count {
196
+ width: fit-content;
197
+ }
198
+ }
199
+ }
200
+
201
+ .item-container {
202
+ display: flex;
203
+ gap: var(--_item-gap);
204
+ overflow-x: hidden;
205
+ position: relative;
206
+
207
+ &.allow-overflow {
208
+ overflow-x: initial;
209
+ }
210
+
211
+ .item {
212
+ display: flex;
213
+ flex: 0 0 100%;
214
+ max-inline-size: 800px;
215
+ transition: transform v-bind(transitionSpeedStr) ease;
216
+ transform: v-bind(itemTransform);
217
+ }
218
+ }
219
+
220
+ .controls-container {
221
+ display: flex;
222
+ align-items: center;
223
+ justify-content: flex-end;
224
+
225
+ .markers-container {
226
+ .markers-list {
227
+ display: flex;
228
+ flex-direction: row;
229
+ gap: 10px;
230
+ list-style-type: none;
231
+ margin: unset;
232
+ padding: unset;
233
+
234
+ .markers-item {
235
+ .btn-marker {
236
+ border: none;
237
+ outline: none;
238
+ box-shadow: none;
239
+ cursor: pointer;
240
+ transition: background-color v-bind(transitionSpeedStr) linear;
241
+
242
+ &.active {
243
+ background-color: red;
244
+ }
245
+ }
246
+ }
247
+ }
248
+ }
249
+
250
+ .buttons-container {
251
+ display: flex;
252
+ align-items: center;
253
+ justify-content: end;
254
+ gap: 20px;
255
+
256
+ .btn-action {
257
+ cursor: pointer;
258
+ height: fit-content;
259
+ }
260
+ }
261
+ }
262
+ }
263
+ </style>
@@ -0,0 +1,378 @@
1
+ <template>
2
+ <section class="carousel-flip" :class="[elementClasses, { 'controls-inside': controlsInside }]">
3
+
4
+ <div class="item-container" ref="carouselContent">
5
+ <div v-for="(item, index) in data?.items" :key="index" class="item" ref="carouselItems">
6
+ <h3>{{ index }}</h3>
7
+ <p>{{ item.alt }}</p>
8
+ </div>
9
+
10
+ </div>
11
+
12
+ <div class="controls-container">
13
+ <div class="buttons-container">
14
+ <button type="submit" @click.prevent="actionPrevious()" class="btn-action"
15
+ :disabled="transitionRunning">Prev</button>
16
+ <button type="submit" @click.prevent="actionNext()" class="btn-action"
17
+ :disabled="transitionRunning">Next</button>
18
+ </div>
19
+ <div class="thumbnail-container">
20
+ <ul class="thumbnail-list">
21
+ <li v-for="item, index in data?.items" class="thumbnail-item" ref="thumbnailItems">
22
+ <div class="thumbnail-item_inner">{{ index }}</div>
23
+ </li>
24
+ </ul>
25
+ </div>
26
+ </div>
27
+ </section>
28
+ </template>
29
+
30
+ <script setup lang="ts">
31
+ import type { ICarouselBasic } from "@/types/types.carousel-basic";
32
+ import { useElementSize, useEventListener, useResizeObserver } from "@vueuse/core";
33
+ const props = defineProps({
34
+ propsData: {
35
+ type: Object as PropType<ICarouselBasic>,
36
+ default: <ICarouselBasic>{
37
+ items: [],
38
+ total: 0,
39
+ skip: 0,
40
+ limit: 10
41
+ }
42
+ },
43
+ data: {
44
+ type: Object,
45
+ default: <ICarouselBasic>{}
46
+ },
47
+ styleClassPassthrough: {
48
+ type: Array as PropType<string[]>,
49
+ default: () => [],
50
+ },
51
+ transitionSpeed: {
52
+ type: Number,
53
+ default: 1000
54
+ },
55
+ controlsInside: {
56
+ type: Boolean,
57
+ default: false
58
+ }
59
+ });
60
+
61
+ const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
62
+
63
+ const carouselContentRef = useTemplateRef<HTMLDivElement>('carouselContent');
64
+ const carouselItems = useTemplateRef<HTMLDivElement[]>('carouselItems');
65
+ const thumbnailItems = useTemplateRef<HTMLLIElement[]>('thumbnailItems');
66
+ const carouselInitComplete = ref(false);
67
+
68
+ const currentIndex = ref(1);
69
+ const itemCount = ref(props.data.items.length);
70
+ const offset = ref(1);
71
+ const previousOffset = ref(1);
72
+ const transitionSpeedStr = props.transitionSpeed + 'ms';
73
+ const transitionRunning = ref(false);
74
+
75
+ const actionPrevious = () => {
76
+ // if (transitionRunning.value) return;
77
+
78
+ offset.value = -1;
79
+ onTransitionEnd();
80
+ }
81
+
82
+ const actionNext = () => {
83
+ // if (transitionRunning.value) return;
84
+
85
+ offset.value = 1;
86
+ onTransitionEnd();
87
+ }
88
+
89
+ const updateOrder = (index: number, order: number) => {
90
+ if (carouselItems.value !== null && thumbnailItems.value !== null) {
91
+ carouselItems.value[index - 1].style.order = order.toString();
92
+ thumbnailItems.value[index - 1].style.order = order.toString();
93
+ }
94
+ };
95
+
96
+ const initialSetup = () => {
97
+ const items = carouselItems.value;
98
+ const thumbs = thumbnailItems.value;
99
+
100
+ items?.forEach((item, index) => {
101
+ item.style.zIndex = index === 0 || index === itemCount.value - 1 ? '1' : '2';
102
+ item.style.order = String(index + 1);
103
+ // item.setAttribute('data-order', String(index + 1));
104
+ });
105
+ thumbs?.forEach((thumb, index) => {
106
+ thumb.style.zIndex = index === 0 || index === itemCount.value - 1 ? '1' : '2';
107
+ thumb.style.order = String(index + 1);
108
+ // thumb.setAttribute('data-order', String(index + 1));
109
+ });
110
+ carouselInitComplete.value = true;
111
+ }
112
+
113
+
114
+ const onTransitionEnd = () => {
115
+
116
+ // transitionRunning.value = true;
117
+ const items = carouselItems.value;
118
+ const thumbs = thumbnailItems.value;
119
+
120
+ if (!items || !Array.isArray(items)) return;
121
+ if (!thumbs || !Array.isArray(thumbs)) return;
122
+
123
+ // 1. Capture initial positions for both main items and thumbnails
124
+ const firstRects = items.map(el => el.getBoundingClientRect());
125
+ const firstThumbRects = thumbs.map(el => el.getBoundingClientRect());
126
+
127
+ // 2. Update orders
128
+ let firstVisualElementIndex = currentIndex.value; // Track which element should be visually first
129
+
130
+ if (carouselInitComplete.value) {
131
+ if (offset.value === 1) {
132
+ const localOffset = offset.value === previousOffset.value ? offset.value : 2; // Ensure we have a valid offset
133
+ currentIndex.value = currentIndex.value === itemCount.value ? 1 : currentIndex.value + localOffset;
134
+ firstVisualElementIndex = currentIndex.value;
135
+ let order = 1;
136
+
137
+ for (let i = currentIndex.value; i <= itemCount.value; i++) updateOrder(i, order++);
138
+ for (let i = 1; i < currentIndex.value; i++) updateOrder(i, order++);
139
+
140
+ } else {
141
+ const localOffset = offset.value === previousOffset.value ? offset.value : -2; // Ensure we have a valid offset
142
+ currentIndex.value = currentIndex.value === 1 ? itemCount.value : currentIndex.value + localOffset;
143
+ firstVisualElementIndex = currentIndex.value;
144
+ let order = itemCount.value;
145
+
146
+ for (let i = currentIndex.value; i >= 1; i--) updateOrder(i, order--);
147
+ for (let i = itemCount.value; i > currentIndex.value; i--) updateOrder(i, order--);
148
+ }
149
+ previousOffset.value = offset.value; // Store the previous offset for next transition
150
+
151
+ // setTimeout(() => {
152
+ // transitionRunning.value = false;
153
+ // }, props.transitionSpeed);
154
+ }
155
+
156
+ // 3. Next tick: capture new positions & animate both main items and thumbnails
157
+ requestAnimationFrame(() => {
158
+ const lastRects = items.map(el => el.getBoundingClientRect());
159
+ const lastThumbRects = thumbs.map(el => el.getBoundingClientRect());
160
+
161
+ // Animate main carousel items
162
+ items.forEach((el, i) => {
163
+ const dx = firstRects[i].left - lastRects[i].left;
164
+ const dy = firstRects[i].top - lastRects[i].top;
165
+
166
+ el.style.transition = 'none';
167
+ el.style.transform = `translate(${dx}px, ${dy}px)`;
168
+
169
+ requestAnimationFrame(() => {
170
+ el.style.transition = `transform ${transitionSpeedStr} ease`;
171
+ el.style.transform = '';
172
+
173
+ // Set z-index after the transition actually completes
174
+ const elementIndex = i + 1; // Convert to 1-based index to match your logic
175
+ const isFirstVisual = elementIndex === firstVisualElementIndex;
176
+
177
+ // Listen for transition end to update z-index
178
+ const handleTransitionEnd = (event: TransitionEvent) => {
179
+ if (event.propertyName === 'transform') {
180
+ el.style.zIndex = isFirstVisual ? '1' : '2';
181
+ el.removeEventListener('transitionend', handleTransitionEnd);
182
+ }
183
+ };
184
+
185
+ el.addEventListener('transitionend', handleTransitionEnd);
186
+ });
187
+ });
188
+
189
+ // Animate thumbnail items
190
+ thumbs.forEach((thumb, i) => {
191
+ const dx = firstThumbRects[i].left - lastThumbRects[i].left;
192
+ const dy = firstThumbRects[i].top - lastThumbRects[i].top;
193
+
194
+ thumb.style.transition = 'none';
195
+ thumb.style.transform = `translate(${dx}px, ${dy}px)`;
196
+
197
+ requestAnimationFrame(() => {
198
+ thumb.style.transition = `transform ${transitionSpeedStr} ease`;
199
+ thumb.style.transform = '';
200
+
201
+ // Set z-index after the transition actually completes
202
+ const thumbIndex = i + 1; // Convert to 1-based index
203
+ const isActiveThumbnail = thumbIndex === firstVisualElementIndex;
204
+
205
+ // Listen for transition end to update z-index
206
+ const handleThumbTransitionEnd = (event: TransitionEvent) => {
207
+ if (event.propertyName === 'transform') {
208
+ thumb.style.zIndex = isActiveThumbnail ? '1' : '2';
209
+ thumb.removeEventListener('transitionend', handleThumbTransitionEnd);
210
+ }
211
+ };
212
+
213
+ thumb.addEventListener('transitionend', handleThumbTransitionEnd);
214
+ });
215
+ });
216
+ });
217
+
218
+ carouselInitComplete.value = true;
219
+ };
220
+
221
+ onMounted(() => {
222
+ initialSetup();
223
+ });
224
+
225
+ </script>
226
+
227
+ <style lang="css">
228
+ .carousel-flip {
229
+
230
+ display: grid;
231
+ grid-template-columns: 1fr;
232
+ gap: 10px;
233
+
234
+ &.controls-inside {
235
+ grid-template-areas: "carousel-content";
236
+ isolation: isolate;
237
+
238
+ .item-container {
239
+ grid-area: carousel-content;
240
+ z-index: 1;
241
+ }
242
+
243
+ .controls-container {
244
+ grid-area: carousel-content;
245
+ z-index: 2;
246
+ height: fit-content;
247
+ align-self: flex-end;
248
+ }
249
+ }
250
+
251
+ .item-container {
252
+ display: flex;
253
+ gap: 10px;
254
+ overflow-x: auto;
255
+ padding-block: 10px;
256
+ padding-inline: 10px;
257
+ outline: 1px solid light-dark(#00000090, #f00ff090);
258
+
259
+ /* scroll-snap-type: x mandatory; */
260
+
261
+ /* isolation: isolate; */
262
+ position: relative;
263
+
264
+ .item {
265
+ display: flex;
266
+ flex-direction: column;
267
+ align-items: center;
268
+ justify-content: center;
269
+
270
+ /* transition: transform v-bind(transitionSpeedStr) ease; */
271
+ /* For FLIP smoothness */
272
+
273
+ aspect-ratio: 4 / 3;
274
+
275
+ min-inline-size: 600px;
276
+ color: light-dar(#aaa, #333);
277
+ padding-block: 10px;
278
+ padding-inline: 10px;
279
+ border-radius: 4px;
280
+ outline: 1px solid light-dark(#00000090, #f00ff090);
281
+
282
+ background-color: light-dark(#f00, #00f);
283
+
284
+ /* scroll-snap-align: none center; */
285
+
286
+ &:nth-child(odd) {
287
+ background-color: light-dark(#00f, #f00);
288
+ }
289
+ }
290
+ }
291
+
292
+
293
+ .controls-container {
294
+
295
+ display: flex;
296
+ gap: 20px;
297
+
298
+
299
+
300
+ .buttons-container {
301
+ display: flex;
302
+ flex-grow: 1;
303
+ align-items: center;
304
+ justify-content: end;
305
+ gap: 20px;
306
+
307
+
308
+ .btn-action {
309
+ padding: 10px 20px;
310
+ border-radius: 4px;
311
+ background-color: light-dark(#000, #fff);
312
+ color: light-dark(#fff, #000);
313
+ border: none;
314
+ cursor: pointer;
315
+ height: fit-content;
316
+
317
+ transition: background-color 0.3s ease, color 0.3s ease;
318
+
319
+ &:hover {
320
+ background-color: light-dark(#0009, #fff9);
321
+ }
322
+
323
+ &:active {
324
+ background-color: light-dark(#0009, #fff9);
325
+ }
326
+
327
+ &:disabled {
328
+ background-color: light-dark(#0003, #fff3);
329
+ cursor: not-allowed;
330
+ }
331
+ }
332
+ }
333
+
334
+ .thumbnail-container {
335
+ padding-block: 10px;
336
+ padding-inline: 10px;
337
+ outline: 1px solid light-dark(#00000090, #f00ff090);
338
+ max-inline-size: 40%;
339
+
340
+ .thumbnail-list {
341
+ display: flex;
342
+ gap: 10px;
343
+ list-style-type: none;
344
+ padding-block: 8px;
345
+ padding-inline: 8px;
346
+ margin-block: 0;
347
+ margin-inline: 0;
348
+
349
+ outline: 1px solid light-dark(#00000090, #f00ff090);
350
+ overflow-x: auto;
351
+
352
+ .thumbnail-item {
353
+
354
+ display: flex;
355
+ align-items: center;
356
+ justify-content: center;
357
+
358
+ aspect-ratio: 3 / 4;
359
+ min-inline-size: 120px;
360
+ outline: 1px solid light-dark(#f00, #00f);
361
+ border-radius: 4px;
362
+
363
+ background-color: light-dark(#f00, #00f);
364
+
365
+ &:nth-child(odd) {
366
+ background-color: light-dark(#00f, #f00);
367
+ }
368
+
369
+
370
+ .thumbnail-item_inner {}
371
+
372
+ }
373
+ }
374
+ }
375
+ }
376
+
377
+ }
378
+ </style>
package/nuxt.config.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  export default defineNuxtConfig({
3
3
  devtools: { enabled: true },
4
4
  css: ['modern-normalize', './assets/styles/main.css'],
5
- modules: ['@nuxt/icon', '@nuxt/image', '@nuxt/eslint'],
5
+ modules: ['@nuxt/icon', '@nuxt/image'],
6
6
  app: {
7
7
  head: {
8
8
  htmlAttrs: {
@@ -34,7 +34,4 @@ export default defineNuxtConfig({
34
34
  typescript: {
35
35
  includeWorkspace: true,
36
36
  },
37
- eslint: {
38
- // Add any custom options here
39
- },
40
37
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "srcdev-nuxt-components",
3
3
  "type": "module",
4
- "version": "4.0.0",
4
+ "version": "4.0.2",
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": "1.3.0",
31
- "@nuxt/eslint-config": "1.3.0",
32
- "@nuxt/icon": "1.12.0",
30
+ "@nuxt/eslint-config": "1.5.2",
31
+ "@nuxt/icon": "1.15.0",
33
32
  "@nuxt/image": "1.10.0",
33
+ "@oddbird/css-anchor-positioning": "0.6.1",
34
+ "@vueuse/core": "13.5.0",
35
+ "eslint": "9.31.0",
34
36
  "happy-dom": "16.8.1",
35
- "nuxt": "3.17.5",
37
+ "nuxt": "3.17.6",
36
38
  "release-it": "18.1.2",
37
39
  "typescript": "5.8.3"
38
40
  },
39
41
  "dependencies": {
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
  },
@@ -0,0 +1,19 @@
1
+ export interface CarouselBasicItem {
2
+ id: number | string;
3
+ url: string;
4
+ alt: string;
5
+ }
6
+
7
+ export interface CarouselModifiedItem {
8
+ id: number | string;
9
+ url: string;
10
+ alt: string;
11
+ order: number;
12
+ }
13
+
14
+ export interface ICarouselBasic {
15
+ items: CarouselBasicItem[] | CarouselModifiedItem[];
16
+ total: number;
17
+ skip: number;
18
+ limit: number;
19
+ }