srcdev-nuxt-components 6.2.0 → 6.2.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.
@@ -0,0 +1,153 @@
1
+ <template>
2
+ <div class="alert-mask-core" :class="[elementClasses]">
3
+ <svg class="alert-mask-decorator" :style="{ '--alertHeight': svgHeight + 'px' }" xmlns="http://www.w3.org/2000/svg">
4
+ <defs>
5
+ <mask id="borderMask" maskUnits="userSpaceOnUse">
6
+ <path :d="outerPath" fill="white" />
7
+ <path :d="innerPath" fill="black" />
8
+ </mask>
9
+ </defs>
10
+
11
+ <path :d="outerPath" :fill="cfg.color" mask="url(#borderMask)" vector-effect="non-scaling-stroke" />
12
+ <path :d="innerPath" fill="rgba(0,0,0,0.5)" />
13
+ </svg>
14
+
15
+ <div
16
+ class="alert-mask-content"
17
+ :style="{
18
+ '--insetInlineStart': (props.config?.borderLeft ?? 0) + 'px',
19
+ '--insetInlineEnd': (props.config?.borderRight ?? 0) + 'px',
20
+ '--insetBlockStart': (props.config?.borderTop ?? 0) + 'px',
21
+ '--insetBlockEnd': (props.config?.borderBottom ?? 0) + 'px',
22
+ }"
23
+ >
24
+ <div class="alert-mask-content-slot" ref="alertContentRef">
25
+ <slot name="default"></slot>
26
+ </div>
27
+ </div>
28
+ </div>
29
+ </template>
30
+
31
+ <script lang="ts">
32
+ export interface BorderConfig {
33
+ color?: string
34
+ radiusLeft?: number
35
+ radiusRight?: number
36
+ borderLeft?: number
37
+ borderTop?: number
38
+ borderRight?: number
39
+ borderBottom?: number
40
+ }
41
+ </script>
42
+
43
+ <script setup lang="ts">
44
+ const props = defineProps({
45
+ config: Object as PropType<BorderConfig>,
46
+ styleClassPassthrough: {
47
+ type: [String, Array] as PropType<string | string[]>,
48
+ default: () => [],
49
+ },
50
+ })
51
+
52
+ const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough)
53
+
54
+ const alertContentRef = useTemplateRef<HTMLElement | null>("alertContentRef")
55
+ const svgWidth = ref(0)
56
+ const svgHeight = ref(0)
57
+
58
+ // Update dimensions based on content
59
+ onMounted(() => {
60
+ const updateDimensions = () => {
61
+ const contentEl = alertContentRef.value
62
+ if (!contentEl) return
63
+
64
+ const rect = contentEl.getBoundingClientRect()
65
+ const { borderLeft, borderTop, borderRight, borderBottom } = cfg.value
66
+
67
+ // Calculate total dimensions including borders
68
+ svgWidth.value = rect.width + borderLeft + borderRight
69
+ svgHeight.value = rect.height + borderTop + borderBottom
70
+ }
71
+
72
+ // Initial measurement
73
+ nextTick(updateDimensions)
74
+
75
+ // Observe content changes
76
+ const contentEl = alertContentRef.value
77
+ if (contentEl) {
78
+ const resizeObserver = new ResizeObserver(updateDimensions)
79
+ resizeObserver.observe(contentEl)
80
+ }
81
+ })
82
+
83
+ const cfg = computed(() => ({
84
+ color: props.config?.color ?? "var(--orange-8)",
85
+ radiusLeft: props.config?.radiusLeft ?? 12,
86
+ radiusRight: props.config?.radiusRight ?? 12,
87
+ borderLeft: props.config?.borderLeft ?? 8,
88
+ borderTop: props.config?.borderTop ?? 8,
89
+ borderRight: props.config?.borderRight ?? 8,
90
+ borderBottom: props.config?.borderBottom ?? 8,
91
+ }))
92
+
93
+ // Outer border path (fixed radii)
94
+ const outerPath = computed(() => {
95
+ const width = svgWidth.value
96
+ const height = svgHeight.value
97
+ const { radiusLeft, radiusRight } = cfg.value
98
+
99
+ if (!width || !height) return ""
100
+
101
+ return `
102
+ M ${radiusLeft} 0
103
+ L ${width - radiusRight} 0
104
+ Q ${width} 0 ${width} ${radiusRight}
105
+ L ${width} ${height - radiusRight}
106
+ Q ${width} ${height} ${width - radiusRight} ${height}
107
+ L ${radiusLeft} ${height}
108
+ Q 0 ${height} 0 ${height - radiusLeft}
109
+ L 0 ${radiusLeft}
110
+ Q 0 0 ${radiusLeft} 0 Z
111
+ `
112
+ })
113
+
114
+ // Inner cutout (based on fixed pixel border thickness)
115
+ const innerPath = computed(() => {
116
+ const width = svgWidth.value
117
+ const height = svgHeight.value
118
+ const { radiusLeft, radiusRight, borderLeft, borderTop, borderRight, borderBottom: borderBottom } = cfg.value
119
+
120
+ if (!width || !height) return ""
121
+
122
+ return `
123
+ M ${radiusLeft + borderLeft} ${borderTop}
124
+ L ${width - radiusRight - borderRight} ${borderTop}
125
+ Q ${width - borderRight} ${borderTop} ${width - borderRight} ${radiusRight + borderTop}
126
+ L ${width - borderRight} ${height - radiusRight - borderBottom}
127
+ Q ${width - borderRight} ${height - borderBottom} ${width - radiusRight - borderRight} ${height - borderBottom}
128
+ L ${radiusLeft + borderLeft} ${height - borderBottom}
129
+ Q ${borderLeft} ${height - borderBottom} ${borderLeft} ${height - radiusLeft - borderBottom}
130
+ L ${borderLeft} ${radiusLeft + borderTop}
131
+ Q ${borderLeft} ${borderTop} ${radiusLeft + borderLeft} ${borderTop} Z
132
+ `
133
+ })
134
+ </script>
135
+
136
+ <style lang="css">
137
+ .alert-mask-core {
138
+ display: grid;
139
+ grid-template-areas: "mask";
140
+
141
+ .alert-mask-decorator {
142
+ grid-area: mask;
143
+ width: 100%;
144
+ height: var(--alertHeight);
145
+ }
146
+
147
+ .alert-mask-content {
148
+ grid-area: mask;
149
+ margin-block: var(--insetBlockStart) var(--insetBlockEnd);
150
+ margin-inline: var(--insetInlineStart) var(--insetInlineEnd);
151
+ }
152
+ }
153
+ </style>
@@ -41,7 +41,7 @@
41
41
  <button
42
42
  @click.prevent="jumpToFrame(index - 1)"
43
43
  class="btn-marker"
44
- :class="[{ active: currentActiveIndex === getOffsetIndex(index - 1, circularOffsetBase, itemCount) }]"
44
+ :class="[{ active: displayActiveIndex === index - 1 }]"
45
45
  :aria-label="`Jump to item ${Math.floor(index + 1)}`"
46
46
  ></button>
47
47
  </li>
@@ -82,7 +82,11 @@ const props = defineProps({
82
82
  },
83
83
  useFlipAnimation: {
84
84
  type: Boolean,
85
- default: true,
85
+ default: false,
86
+ },
87
+ useSpringEffect: {
88
+ type: Boolean,
89
+ default: false,
86
90
  },
87
91
  })
88
92
 
@@ -98,13 +102,6 @@ const userHasInteracted = ref(false)
98
102
  const initialItemOffset = computed(() => {
99
103
  return props.useFlipAnimation ? 1 : 2
100
104
  })
101
- const circularOffsetBase = computed(() => {
102
- return props.useFlipAnimation ? 1 : Math.floor(2 * initialItemOffset.value)
103
- })
104
-
105
- function getOffsetIndex(index: number, offset: number, itemCount: number): number {
106
- return (index + offset) % itemCount
107
- }
108
105
 
109
106
  const currentIndex = ref(0)
110
107
  const itemCount = ref(props.carouselDataIds.length)
@@ -112,25 +109,32 @@ const transitionSpeedStr = props.transitionSpeed + "ms"
112
109
 
113
110
  const itemWidth = ref(0)
114
111
  const itemWidthOffsetStr = computed(() => {
115
- if (props.allowCarouselOverflow) {
116
- if (props.useFlipAnimation) {
117
- return `calc(-${initialItemOffset.value} * ${itemWidth.value}px - var(--_carousel-item-track-gap))` // Good
118
- } else {
119
- return `calc(-${initialItemOffset.value} * ${itemWidth.value}px - (2 * var(--_carousel-item-track-gap)))` // Good
120
- }
112
+ // if (props.allowCarouselOverflow) {
113
+ if (props.useFlipAnimation) {
114
+ return `calc(-${initialItemOffset.value} * ${itemWidth.value}px - var(--_carousel-item-track-gap))` // Good
121
115
  } else {
122
- if (props.useFlipAnimation) {
123
- return `calc(-${initialItemOffset.value} * ${itemWidth.value}px - var(--_carousel-item-track-gap))` // Goof
124
- } else {
125
- return `calc(-${initialItemOffset.value} * ${itemWidth.value}px - (2 * var(--_carousel-item-track-gap)))` // Good
126
- }
116
+ return `calc(-${initialItemOffset.value} * ${itemWidth.value}px - (2 * var(--_carousel-item-track-gap)))` // Good
127
117
  }
118
+ // } else {
119
+ // if (props.useFlipAnimation) {
120
+ // return `calc(-${initialItemOffset.value} * ${itemWidth.value}px - var(--_carousel-item-track-gap))` // Goof
121
+ // } else {
122
+ // return `calc(-${initialItemOffset.value} * ${itemWidth.value}px - (2 * var(--_carousel-item-track-gap)))` // Good
123
+ // }
124
+ // }
128
125
  })
129
126
  const currentActiveIndex = ref(0)
130
127
 
128
+ // Computed property to get the display index (what the user sees as active)
129
+ const displayActiveIndex = computed(() => {
130
+ return currentActiveIndex.value
131
+ })
132
+
131
133
  const updateItemOrder = (index: number, order: number, zIndex: number = 2) => {
132
134
  if (carouselItemsRef?.value && carouselItemsRef.value[index]) {
133
- carouselItemsRef.value[index].style.order = order.toString()
135
+ if (order !== -1) {
136
+ carouselItemsRef.value[index].style.order = order.toString()
137
+ }
134
138
  carouselItemsRef.value[index].style.zIndex = zIndex.toString()
135
139
  }
136
140
  }
@@ -144,8 +148,17 @@ function analyzeOffsets(offsets: number[]) {
144
148
 
145
149
  const sorted = [...counts.entries()].sort((a, b) => b[1] - a[1])
146
150
 
147
- const majorityValue = sorted[0][0]
148
- const minorityValue = sorted[sorted.length - 1][0]
151
+ // Handle empty sorted array
152
+ if (sorted.length === 0) {
153
+ return {
154
+ majorityValue: 0,
155
+ minorityValue: 0,
156
+ minorityIndex: -1,
157
+ }
158
+ }
159
+
160
+ const majorityValue = sorted[0]![0]
161
+ const minorityValue = sorted[sorted.length - 1]![0]
149
162
  const minorityIndex = offsets.findIndex((val) => val === minorityValue)
150
163
 
151
164
  return {
@@ -161,32 +174,58 @@ const reorderItems = (direction: "next" | "previous" | "jump" = "jump", skipAnim
161
174
  // Capture positions before reordering (only if we're going to animate)
162
175
  const beforeRects = skipAnimation ? [] : carouselItemsRef.value.map((item) => item.getBoundingClientRect())
163
176
 
164
- // Apply new order and z-index based on direction
177
+ // Store current order positions before reordering
178
+ const currentOrderMap = new Map<number, number>()
179
+ if (!skipAnimation) {
180
+ carouselItemsRef.value.forEach((item, index) => {
181
+ const currentOrder = parseInt(item.style.order || "1")
182
+ currentOrderMap.set(index, currentOrder)
183
+ })
184
+ }
185
+
186
+ // Apply new order and calculate z-index based on order transition
165
187
  let order = 1
166
188
 
167
- // For items from currentActiveIndex to end
168
- for (let i = currentActiveIndex.value; i < itemCount.value; i++) {
169
- let zIndex = 2 // default normal z-index
170
-
171
- if (i === currentActiveIndex.value) {
172
- // The item becoming visible
173
- if (direction === "previous") {
174
- // When going previous, the item moving to first position should go behind
175
- zIndex = 1
176
- } else {
177
- // Normal case - visible item gets highest z-index
178
- zIndex = 3
179
- }
180
- }
189
+ // Helper function to determine if an item should go behind during transition
190
+ const shouldGoBehind = (currentOrder: number, newOrder: number) => {
191
+ // Normal case: moving to higher order (left to right)
192
+ if (currentOrder < newOrder) return true
181
193
 
182
- updateItemOrder(i, order++, zIndex)
194
+ // Wrap case: moving from end to beginning (high order to low order with big gap)
195
+ // This happens when an item at the end wraps to the beginning
196
+ const orderDifference = Math.abs(currentOrder - newOrder)
197
+ const isWrapping = orderDifference > itemCount.value / 2
198
+ if (isWrapping && currentOrder > newOrder) return true
199
+
200
+ return false
183
201
  }
184
202
 
185
- // For items from 0 to currentActiveIndex
186
- for (let i = 0; i < currentActiveIndex.value; i++) {
187
- // Items that wrap around get lower z-index to slide behind
188
- const zIndex = 1
189
- updateItemOrder(i, order++, zIndex)
203
+ // First, place the previous item (for visual continuity)
204
+ const prevIndex = currentActiveIndex.value === 0 ? itemCount.value - 1 : currentActiveIndex.value - 1
205
+ const prevCurrentOrder = currentOrderMap.get(prevIndex) || 1
206
+ const prevNewOrder = order++
207
+ const prevZIndex = shouldGoBehind(prevCurrentOrder, prevNewOrder) ? 1 : 2
208
+ updateItemOrder(prevIndex, prevNewOrder, prevZIndex)
209
+
210
+ // Then place the current active item
211
+ const currentOrder = currentOrderMap.get(currentActiveIndex.value) || 1
212
+ const newCurrentOrder = order++
213
+ const currentZIndex = shouldGoBehind(currentOrder, newCurrentOrder) ? 1 : 2
214
+ updateItemOrder(currentActiveIndex.value, newCurrentOrder, currentZIndex)
215
+
216
+ // Then place all remaining items in sequence
217
+ let nextIndex = currentActiveIndex.value + 1
218
+ while (nextIndex !== prevIndex) {
219
+ if (nextIndex >= itemCount.value) {
220
+ nextIndex = 0 // Wrap around
221
+ }
222
+ if (nextIndex === prevIndex) break // Don't place the previous item again
223
+
224
+ const itemCurrentOrder = currentOrderMap.get(nextIndex) || 1
225
+ const itemNewOrder = order++
226
+ const itemZIndex = shouldGoBehind(itemCurrentOrder, itemNewOrder) ? 1 : 2
227
+ updateItemOrder(nextIndex, itemNewOrder, itemZIndex)
228
+ nextIndex++
190
229
  }
191
230
 
192
231
  // Skip animation if requested (for initial setup)
@@ -201,15 +240,20 @@ const reorderItems = (direction: "next" | "previous" | "jump" = "jump", skipAnim
201
240
  // Calculate offset values
202
241
  const offsetValues = beforeRects.map((beforeRect, index) => {
203
242
  const afterRect = afterRects[index]
204
- return beforeRect.left - afterRect.left
243
+ return beforeRect.left - (afterRect?.left ?? beforeRect.left)
205
244
  })
206
245
 
207
246
  const leftValues = analyzeOffsets(offsetValues)
208
247
 
209
248
  carouselItemsRef.value!.forEach((item, index) => {
210
- const deltaX = beforeRects[index].left - afterRects[index].left
249
+ const beforeRect = beforeRects[index]
250
+ const afterRect = afterRects[index]
251
+ const deltaX = (beforeRect?.left ?? 0) - (afterRect?.left ?? 0)
252
+ const timingFunction = props.useSpringEffect ? "var(--spring-easing)" : "ease"
211
253
 
212
254
  if (deltaX !== 0) {
255
+ // Optimize for upcoming transform animation
256
+ item.style.willChange = "transform"
213
257
  item.style.transition = "none"
214
258
  item.style.transform = `translateX(${deltaX}px)`
215
259
 
@@ -220,18 +264,18 @@ const reorderItems = (direction: "next" | "previous" | "jump" = "jump", skipAnim
220
264
  if (shouldTransition) {
221
265
  if (props.allowCarouselOverflow) {
222
266
  if (props.useFlipAnimation) {
223
- transitionProperties = `transform ${transitionSpeedStr} ease`
267
+ transitionProperties = `transform ${transitionSpeedStr} ${timingFunction}`
224
268
  } else {
225
269
  if (leftValues.minorityIndex !== index) {
226
- transitionProperties = `transform ${transitionSpeedStr} ease`
270
+ transitionProperties = `transform ${transitionSpeedStr} ${timingFunction}`
227
271
  }
228
272
  }
229
273
  } else {
230
274
  if (props.useFlipAnimation) {
231
- transitionProperties = `transform ${transitionSpeedStr} ease`
275
+ transitionProperties = `transform ${transitionSpeedStr} ${timingFunction}`
232
276
  } else {
233
277
  if (leftValues.minorityIndex !== index) {
234
- transitionProperties = `transform ${transitionSpeedStr} ease`
278
+ transitionProperties = `transform ${transitionSpeedStr} ${timingFunction}`
235
279
  }
236
280
  }
237
281
  }
@@ -240,12 +284,10 @@ const reorderItems = (direction: "next" | "previous" | "jump" = "jump", skipAnim
240
284
  item.style.transition = transitionProperties
241
285
  item.style.transform = "translateX(0)"
242
286
 
243
- // After animation completes, normalize z-index values
287
+ // After animation completes, clean up will-change (keep z-index as set)
244
288
  const handleTransitionEnd = (event: TransitionEvent) => {
245
289
  if (event.propertyName === "transform") {
246
- // Set final z-index: current item gets highest, others get normal
247
- const isCurrentlyVisible = index === currentActiveIndex.value
248
- item.style.zIndex = isCurrentlyVisible ? "3" : "2"
290
+ item.style.willChange = "auto"
249
291
  item.removeEventListener("transitionend", handleTransitionEnd)
250
292
  }
251
293
  }
@@ -253,9 +295,8 @@ const reorderItems = (direction: "next" | "previous" | "jump" = "jump", skipAnim
253
295
  if (shouldTransition) {
254
296
  item.addEventListener("transitionend", handleTransitionEnd)
255
297
  } else {
256
- // If no transition, immediately normalize z-index
257
- const isCurrentlyVisible = index === currentActiveIndex.value
258
- item.style.zIndex = isCurrentlyVisible ? "3" : "2"
298
+ // If no transition, just clean up will-change (keep z-index as set)
299
+ item.style.willChange = "auto"
259
300
  }
260
301
  })
261
302
  }
@@ -300,20 +341,18 @@ const jumpToFrame = (index: number) => {
300
341
  userHasInteracted.value = true
301
342
  }
302
343
 
303
- currentActiveIndex.value = getOffsetIndex(index, circularOffsetBase.value, itemCount.value)
344
+ currentActiveIndex.value = index
304
345
 
305
- // currentActiveIndex.value = index;
306
346
  reorderItems("jump")
307
347
  currentIndex.value = currentActiveIndex.value
308
348
  }
309
349
  }
310
350
 
311
351
  const checkAndMoveLastItem = () => {
312
- if (props.allowCarouselOverflow || !props.useFlipAnimation) {
313
- currentActiveIndex.value = itemCount.value - initialItemOffset.value
314
- reorderItems("jump", true) // Skip animation during initial setup
315
- currentIndex.value = currentActiveIndex.value
316
- }
352
+ // We need to reorder items for the initial layout regardless of the settings
353
+ // Keep currentActiveIndex at 0 (first item) but reorder visually
354
+ reorderItems("jump", true) // Skip animation during initial setup
355
+ currentIndex.value = currentActiveIndex.value
317
356
  }
318
357
 
319
358
  const initialSetup = () => {
@@ -331,6 +370,13 @@ const initialSetup = () => {
331
370
 
332
371
  carouselInitComplete.value = true
333
372
  checkAndMoveLastItem()
373
+
374
+ // Add mounted class to trigger opacity transition after setup is complete
375
+ nextTick(() => {
376
+ if (carouselWrapperRef.value) {
377
+ carouselWrapperRef.value.classList.add("mounted")
378
+ }
379
+ })
334
380
  }
335
381
 
336
382
  const { direction } = useSwipe(carouselContainerRef, {
@@ -376,6 +422,11 @@ onMounted(() => {
376
422
  display: grid;
377
423
  grid-template-columns: 1fr;
378
424
  gap: 10px;
425
+ opacity: 0;
426
+
427
+ &.mounted {
428
+ opacity: 1;
429
+ }
379
430
 
380
431
  .sr-only {
381
432
  position: absolute;
@@ -394,6 +445,7 @@ onMounted(() => {
394
445
  gap: var(--_carousel-item-track-gap);
395
446
  overflow-x: hidden;
396
447
  position: relative;
448
+ isolation: isolate;
397
449
 
398
450
  max-inline-size: var(--_carousel-display-max-width);
399
451
  margin-inline: auto;
@@ -2,7 +2,7 @@
2
2
  <div class="expanding-panel" :class="[elementClasses]">
3
3
  <details class="expanding-panel-details" :name :open>
4
4
  <summary
5
- @click.prevent="handleToggle"
5
+ @click.prevent.stop="handleToggle"
6
6
  @keydown.enter.prevent="handleToggle"
7
7
  @keydown.space.prevent="handleToggle"
8
8
  class="expanding-panel-summary"
@@ -56,7 +56,7 @@ const props = defineProps({
56
56
  },
57
57
  })
58
58
 
59
- const name = computed(() => props.name || useId())
59
+ const name = shallowRef(props.name || useId())
60
60
  const isPanelOpen = defineModel({ default: false }) as Ref<boolean>
61
61
  const animationDurationStr = computed(() => `${props.animationDuration}ms`)
62
62
  const open = computed(() => {
@@ -76,6 +76,9 @@ const handleToggle = (event: Event) => {
76
76
  <style lang="css">
77
77
  .expanding-panel {
78
78
  .expanding-panel-details {
79
+ &:hover {
80
+ cursor: pointer;
81
+ }
79
82
  .expanding-panel-summary {
80
83
  display: flex;
81
84
  align-items: center;
@@ -109,6 +112,7 @@ const handleToggle = (event: Event) => {
109
112
  transform: scaleY(1);
110
113
  transition: transform v-bind(animationDurationStr) ease-in-out;
111
114
  font-size: 1.2rem;
115
+ will-change: transform;
112
116
  }
113
117
  }
114
118
  }
@@ -134,6 +138,7 @@ const handleToggle = (event: Event) => {
134
138
  display: grid;
135
139
  grid-template-rows: 0fr;
136
140
  transition: all v-bind(animationDurationStr) ease-in-out;
141
+ will-change: grid-template-rows;
137
142
 
138
143
  .inner {
139
144
  overflow: hidden;
@@ -15,6 +15,7 @@
15
15
  'animation-timeline': key === itemCount - 1 ? 'none' : `--section-${timelineId}-${key}`,
16
16
  'z-index': itemCount - key,
17
17
  }"
18
+ ref="stickyItemsRef"
18
19
  >
19
20
  <slot :name="`stickyItem-${key}`"></slot>
20
21
  </div>
@@ -27,6 +28,7 @@
27
28
  :style="{
28
29
  'view-timeline-name': `--section-${timelineId}-${key}`,
29
30
  }"
31
+ ref="scrollingItemsRef"
30
32
  >
31
33
  <slot :name="`scrollingItem-${key}`"></slot>
32
34
  </section>
@@ -54,7 +56,9 @@ const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.s
54
56
 
55
57
  const timelineId = useId()
56
58
  const stickyItemsContainerRef = useTemplateRef<HTMLElement | null>("stickyItemsContainerRef")
59
+ const stickyItemsRef = useTemplateRef<HTMLElement[] | null>("stickyItemsRef")
57
60
  const scrollContainerRef = useTemplateRef<HTMLElement | null>("scrollContainerRef")
61
+ const scrollingItemsRef = useTemplateRef<HTMLElement[] | null>("scrollingItemsRef")
58
62
  const timelineInset = ref("35% 35%")
59
63
  const topPercent = ref("0")
60
64
  const bottomPercent = ref("0")
@@ -94,13 +98,36 @@ watch(
94
98
  }
95
99
  )
96
100
 
101
+ const fallbackScrollHandler = () => {
102
+ const sections = scrollingItemsRef.value || []
103
+ const layers = stickyItemsRef.value || []
104
+
105
+ const mid = window.innerHeight / 2
106
+
107
+ sections.forEach((section, i) => {
108
+ const rect = section.getBoundingClientRect()
109
+ const active = rect.top <= mid && rect.bottom >= mid
110
+ if (layers[i]) layers[i].style.opacity = active ? "1" : "0"
111
+ })
112
+ }
113
+
114
+ const supportsScrollTimeline = import.meta.client ? CSS.supports("animation-timeline: view()") : false
115
+
97
116
  onMounted(() => {
98
117
  calculateInset()
99
- window.addEventListener("scroll", onScrollDebounce)
118
+ if (supportsScrollTimeline) {
119
+ window.addEventListener("scroll", onScrollDebounce)
120
+ } else {
121
+ window.addEventListener("scroll", fallbackScrollHandler)
122
+ }
100
123
  })
101
124
 
102
125
  onUnmounted(() => {
103
- window.removeEventListener("scroll", onScrollDebounce)
126
+ if (supportsScrollTimeline) {
127
+ window.removeEventListener("scroll", onScrollDebounce)
128
+ } else {
129
+ window.removeEventListener("scroll", fallbackScrollHandler)
130
+ }
104
131
  })
105
132
  </script>
106
133
 
@@ -141,4 +168,11 @@ onUnmounted(() => {
141
168
  animation-range: entry 0% entry 100%;
142
169
  }
143
170
  }
171
+
172
+ @supports not (animation-timeline: view()) {
173
+ .sticky-item {
174
+ opacity: 0;
175
+ transition: opacity 0.4s ease-in-out;
176
+ }
177
+ }
144
178
  </style>
@@ -3,3 +3,4 @@ export * from "../components/display-chip/DisplayChip.vue"
3
3
  export * from "../components/carousel-basic/CarouselBasic.vue"
4
4
  export * from "../components/image-galleries/SliderGallery.vue"
5
5
  export * from "../components/display-toast/DisplayToast.vue"
6
+ export * from "../components/alert-mask/AlertMaskCore.vue"
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "srcdev-nuxt-components",
3
3
  "type": "module",
4
- "version": "6.2.0",
4
+ "version": "6.2.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",