@ulu/frontend-vue 0.2.0-beta.8 → 0.3.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.
- package/dist/components/collapsible/UluAccordionGroup.vue.d.ts +2 -2
- package/dist/components/collapsible/UluAccordionGroup.vue.d.ts.map +1 -1
- package/dist/components/collapsible/UluAccordionGroup.vue.js +22 -19
- package/dist/components/collapsible/UluDropdown.vue.d.ts +1 -1
- package/dist/components/collapsible/UluDropdown.vue.d.ts.map +1 -1
- package/dist/components/collapsible/UluDropdown.vue.js +22 -15
- package/dist/components/collapsible/UluModal.vue.d.ts +43 -248
- package/dist/components/collapsible/UluModal.vue.d.ts.map +1 -1
- package/dist/components/collapsible/UluModal.vue.js +139 -191
- package/dist/components/collapsible/UluTabGroup.vue.d.ts +2 -0
- package/dist/components/collapsible/UluTabGroup.vue.d.ts.map +1 -1
- package/dist/components/collapsible/UluTabGroup.vue.js +23 -14
- package/dist/components/elements/UluAlert.vue.d.ts +29 -144
- package/dist/components/elements/UluAlert.vue.d.ts.map +1 -1
- package/dist/components/elements/UluAlert.vue.js +39 -50
- package/dist/components/elements/UluBadge.vue.d.ts +6 -6
- package/dist/components/elements/UluBadgeStack.vue.d.ts +1 -1
- package/dist/components/elements/UluBadgeStack.vue.d.ts.map +1 -1
- package/dist/components/elements/UluBadgeStack.vue.js +12 -9
- package/dist/components/elements/UluButton.vue.d.ts +47 -177
- package/dist/components/elements/UluButton.vue.d.ts.map +1 -1
- package/dist/components/elements/UluButton.vue.js +59 -72
- package/dist/components/elements/UluButtonVerbose.vue.d.ts +38 -123
- package/dist/components/elements/UluButtonVerbose.vue.d.ts.map +1 -1
- package/dist/components/elements/UluButtonVerbose.vue.js +52 -65
- package/dist/components/elements/UluCallout.vue.d.ts +20 -25
- package/dist/components/elements/UluCallout.vue.d.ts.map +1 -1
- package/dist/components/elements/UluCallout.vue.js +11 -16
- package/dist/components/elements/UluCaptionedFigure.vue.d.ts +25 -0
- package/dist/components/elements/UluCaptionedFigure.vue.d.ts.map +1 -0
- package/dist/components/elements/UluCaptionedFigure.vue.js +48 -0
- package/dist/components/elements/UluCard.vue.d.ts +2 -2
- package/dist/components/elements/UluDefinitionList.vue.d.ts +4 -2
- package/dist/components/elements/UluDefinitionList.vue.d.ts.map +1 -1
- package/dist/components/elements/UluDefinitionList.vue.js +32 -28
- package/dist/components/elements/UluExternalLink.vue.d.ts +2 -2
- package/dist/components/elements/UluImage.vue.d.ts +14 -0
- package/dist/components/elements/UluImage.vue.d.ts.map +1 -0
- package/dist/components/elements/UluImage.vue.js +53 -0
- package/dist/components/elements/UluList.vue.d.ts.map +1 -1
- package/dist/components/elements/UluList.vue.js +14 -13
- package/dist/components/elements/UluOverflowScroller.vue.d.ts +49 -0
- package/dist/components/elements/UluOverflowScroller.vue.d.ts.map +1 -0
- package/dist/components/elements/UluOverflowScroller.vue.js +138 -0
- package/dist/components/elements/UluScrollSlider.vue.d.ts +38 -0
- package/dist/components/elements/UluScrollSlider.vue.d.ts.map +1 -0
- package/dist/components/elements/UluScrollSlider.vue.js +146 -0
- package/dist/components/elements/UluSlider.vue.d.ts +57 -0
- package/dist/components/elements/UluSlider.vue.d.ts.map +1 -0
- package/dist/components/elements/UluSlider.vue.js +277 -0
- package/dist/components/forms/UluFormFile.vue.d.ts +2 -2
- package/dist/components/forms/UluFormRadio.vue.d.ts +4 -4
- package/dist/components/index.d.ts +6 -0
- package/dist/components/layout/UluTitleRail.vue.d.ts +29 -87
- package/dist/components/layout/UluTitleRail.vue.d.ts.map +1 -1
- package/dist/components/layout/UluTitleRail.vue.js +51 -46
- package/dist/components/navigation/UluBreadcrumb.vue.d.ts +27 -68
- package/dist/components/navigation/UluBreadcrumb.vue.d.ts.map +1 -1
- package/dist/components/navigation/UluBreadcrumb.vue.js +51 -54
- package/dist/components/navigation/UluMenu.vue.d.ts +30 -138
- package/dist/components/navigation/UluMenu.vue.d.ts.map +1 -1
- package/dist/components/navigation/UluMenu.vue.js +85 -84
- package/dist/components/navigation/UluMenuStack.vue.d.ts +12 -2
- package/dist/components/navigation/UluMenuStack.vue.d.ts.map +1 -1
- package/dist/components/navigation/UluMenuStack.vue.js +26 -18
- package/dist/components/navigation/UluNavStrip.vue.d.ts +22 -134
- package/dist/components/navigation/UluNavStrip.vue.d.ts.map +1 -1
- package/dist/components/navigation/UluNavStrip.vue.js +43 -31
- package/dist/components/systems/facets/UluFacetsSidebarLayout.vue.js +10 -10
- package/dist/components/systems/facets/useFacets.d.ts +3 -0
- package/dist/components/systems/facets/useFacets.d.ts.map +1 -1
- package/dist/components/systems/facets/useFacets.js +124 -112
- package/dist/components/systems/index.d.ts +0 -3
- package/dist/components/systems/scroll-anchors/UluScrollAnchors.vue.d.ts +2 -2
- package/dist/components/systems/table-sticky/UluTableSticky.vue.d.ts +504 -432
- package/dist/components/systems/table-sticky/UluTableSticky.vue.d.ts.map +1 -1
- package/dist/components/systems/table-sticky/UluTableSticky.vue.js +313 -456
- package/dist/components/systems/table-sticky/UluTableStickyRows.vue.d.ts +40 -31
- package/dist/components/systems/table-sticky/UluTableStickyRows.vue.d.ts.map +1 -1
- package/dist/components/systems/table-sticky/UluTableStickyRows.vue.js +43 -45
- package/dist/components/systems/table-sticky/UluTableStickyTable.vue.d.ts +60 -146
- package/dist/components/systems/table-sticky/UluTableStickyTable.vue.d.ts.map +1 -1
- package/dist/components/systems/table-sticky/UluTableStickyTable.vue.js +156 -175
- package/dist/components/utils/UluAction.vue.d.ts +36 -0
- package/dist/components/utils/UluAction.vue.d.ts.map +1 -0
- package/dist/components/utils/UluAction.vue.js +59 -0
- package/dist/components/utils/UluConditionalText.vue.d.ts +7 -26
- package/dist/components/utils/UluConditionalText.vue.d.ts.map +1 -1
- package/dist/components/utils/UluConditionalText.vue.js +12 -14
- package/dist/components/utils/UluConditionalWrapper.vue.d.ts.map +1 -1
- package/dist/components/utils/UluConditionalWrapper.vue.js +11 -9
- package/dist/components/utils/UluPlaceholderImage.vue.d.ts +12 -57
- package/dist/components/utils/UluPlaceholderImage.vue.d.ts.map +1 -1
- package/dist/components/utils/UluPlaceholderImage.vue.js +18 -26
- package/dist/components/utils/UluPlaceholderText.vue.d.ts +6 -20
- package/dist/components/utils/UluPlaceholderText.vue.js +12 -14
- package/dist/components/utils/UluRouteAnnouncer.vue.d.ts +9 -58
- package/dist/components/utils/UluRouteAnnouncer.vue.d.ts.map +1 -1
- package/dist/components/utils/UluRouteAnnouncer.vue.js +28 -28
- package/dist/components/visualizations/UluAnimateNumber.vue.d.ts +20 -14
- package/dist/components/visualizations/UluAnimateNumber.vue.d.ts.map +1 -1
- package/dist/components/visualizations/UluAnimateNumber.vue.js +18 -26
- package/dist/components/visualizations/UluProgressCircle.vue.d.ts +2 -2
- package/dist/composables/useModifiers.d.ts +20 -25
- package/dist/composables/useModifiers.d.ts.map +1 -1
- package/dist/index.js +206 -200
- package/dist/plugins/modals/UluModalsDisplay.vue.d.ts +3 -12
- package/dist/plugins/modals/UluModalsDisplay.vue.js +24 -45
- package/dist/plugins/modals/index.js +6 -6
- package/dist/plugins/toast/UluToast.vue.d.ts +24 -49
- package/dist/plugins/toast/UluToast.vue.d.ts.map +1 -1
- package/dist/plugins/toast/UluToast.vue.js +68 -77
- package/dist/plugins/toast/UluToastDisplay.vue.d.ts +1 -9
- package/dist/plugins/toast/UluToastDisplay.vue.js +27 -35
- package/dist/plugins/toast/defaults.d.ts +40 -35
- package/dist/plugins/toast/defaults.js +2 -2
- package/dist/plugins/toast/index.js +4 -4
- package/dist/plugins/toast/store.d.ts +40 -35
- package/dist/plugins/toast/store.d.ts.map +1 -1
- package/dist/utils/props.d.ts +7 -0
- package/dist/utils/props.d.ts.map +1 -0
- package/dist/utils/props.js +6 -0
- package/lib/components/collapsible/UluAccordionGroup.vue +4 -1
- package/lib/components/collapsible/UluDropdown.vue +5 -1
- package/lib/components/collapsible/UluModal.vue +278 -298
- package/lib/components/collapsible/UluTabGroup.vue +21 -6
- package/lib/components/elements/UluAlert.vue +38 -51
- package/lib/components/elements/UluBadgeStack.vue +4 -1
- package/lib/components/elements/UluButton.vue +105 -129
- package/lib/components/elements/UluButtonVerbose.vue +67 -89
- package/lib/components/elements/UluCallout.vue +15 -19
- package/lib/components/elements/UluCaptionedFigure.vue +40 -0
- package/lib/components/elements/UluDefinitionList.vue +27 -6
- package/lib/components/elements/UluImage.vue +56 -0
- package/lib/components/elements/UluList.vue +1 -0
- package/lib/components/elements/UluOverflowScroller.vue +140 -0
- package/lib/components/elements/UluScrollSlider.vue +150 -0
- package/lib/components/elements/UluSlider.vue +488 -0
- package/lib/components/index.js +10 -0
- package/lib/components/layout/UluTitleRail.vue +55 -48
- package/lib/components/navigation/UluBreadcrumb.vue +29 -34
- package/lib/components/navigation/UluMenu.vue +60 -71
- package/lib/components/navigation/UluMenuStack.vue +6 -1
- package/lib/components/navigation/UluNavStrip.vue +43 -31
- package/lib/components/systems/facets/useFacets.js +33 -17
- package/lib/components/systems/index.js +0 -4
- package/lib/components/systems/table-sticky/UluTableSticky.vue +602 -576
- package/lib/components/systems/table-sticky/UluTableStickyRows.vue +16 -27
- package/lib/components/systems/table-sticky/UluTableStickyTable.vue +95 -96
- package/lib/components/utils/UluAction.vue +81 -0
- package/lib/components/utils/UluConditionalText.vue +13 -16
- package/lib/components/utils/UluConditionalWrapper.vue +5 -1
- package/lib/components/utils/UluPlaceholderImage.vue +44 -46
- package/lib/components/utils/UluPlaceholderText.vue +10 -13
- package/lib/components/utils/UluRouteAnnouncer.vue +59 -47
- package/lib/components/visualizations/UluAnimateNumber.vue +23 -30
- package/lib/composables/useModifiers.js +21 -26
- package/lib/plugins/modals/UluModalsDisplay.vue +44 -45
- package/lib/plugins/toast/UluToast.vue +28 -34
- package/lib/plugins/toast/UluToastDisplay.vue +9 -15
- package/lib/utils/props.js +8 -0
- package/package.json +9 -5
- package/dist/components/systems/slider/UluImageSlideShow.vue.d.ts +0 -130
- package/dist/components/systems/slider/UluImageSlideShow.vue.d.ts.map +0 -1
- package/dist/components/systems/slider/UluImageSlideShow.vue.js +0 -73
- package/dist/components/systems/slider/UluSlideShow.vue.d.ts +0 -205
- package/dist/components/systems/slider/UluSlideShow.vue.d.ts.map +0 -1
- package/dist/components/systems/slider/UluSlideShow.vue.js +0 -292
- package/dist/components/systems/slider/UluSlideShowSlide.vue.d.ts +0 -17
- package/dist/components/systems/slider/UluSlideShowSlide.vue.d.ts.map +0 -1
- package/dist/components/systems/slider/UluSlideShowSlide.vue.js +0 -26
- package/lib/components/systems/slider/UluImageSlideShow.vue +0 -75
- package/lib/components/systems/slider/UluSlideShow.vue +0 -336
- package/lib/components/systems/slider/UluSlideShowSlide.vue +0 -25
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="slider"
|
|
4
|
+
:class="[resolvedModifiers, { 'slider--transitioning': isTransitioning }]"
|
|
5
|
+
@mouseenter="pauseAutoplay"
|
|
6
|
+
@mouseleave="resumeAutoplay"
|
|
7
|
+
@focusin="pauseAutoplay"
|
|
8
|
+
@focusout="resumeAutoplay"
|
|
9
|
+
>
|
|
10
|
+
<div class="slider__control-context">
|
|
11
|
+
<div class="slider__track-crop" style="overflow: hidden;">
|
|
12
|
+
<ul
|
|
13
|
+
class="slider__track"
|
|
14
|
+
:id="trackId"
|
|
15
|
+
ref="trackRef"
|
|
16
|
+
style="display: flex; list-style: none; position: relative; margin: 0; padding: 0;"
|
|
17
|
+
:inert="isTransitioning"
|
|
18
|
+
:aria-live="autoplayTimer ? 'off' : 'polite'"
|
|
19
|
+
>
|
|
20
|
+
<li
|
|
21
|
+
v-for="(item, index) in items"
|
|
22
|
+
:key="index"
|
|
23
|
+
:id="getSlideId(index)"
|
|
24
|
+
class="slider__slide"
|
|
25
|
+
:class="{ 'is-active': index === activeIndex }"
|
|
26
|
+
style="flex: 0 0 100%; width: 100%; visibility: hidden;"
|
|
27
|
+
tabindex="-1"
|
|
28
|
+
role="group"
|
|
29
|
+
aria-roledescription="slide"
|
|
30
|
+
:aria-label="`Slide ${index + 1} of ${items.length}`"
|
|
31
|
+
:inert="!isTransitioning && index !== activeIndex"
|
|
32
|
+
:aria-hidden="index !== activeIndex"
|
|
33
|
+
:ref="el => setSlideRef(el, index)"
|
|
34
|
+
>
|
|
35
|
+
<slot
|
|
36
|
+
name="slide"
|
|
37
|
+
:item="item"
|
|
38
|
+
:index="index"
|
|
39
|
+
:active="index === activeIndex"
|
|
40
|
+
:upcoming="isUpcoming(index)"
|
|
41
|
+
></slot>
|
|
42
|
+
</li>
|
|
43
|
+
</ul>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<ul v-if="controls" class="Slider__controls">
|
|
47
|
+
<li class="Slider__controls-item Slider__controls-item--previous">
|
|
48
|
+
<button
|
|
49
|
+
class="Slider__control-button Slider__control-button--previous button button--icon"
|
|
50
|
+
aria-label="Previous Slide"
|
|
51
|
+
:aria-controls="trackId"
|
|
52
|
+
@click="previous('previous')"
|
|
53
|
+
:disabled="!loop && activeIndex === 0"
|
|
54
|
+
>
|
|
55
|
+
<slot name="previous">
|
|
56
|
+
<UluIcon icon="type:previous" class="Slider__control-icon" />
|
|
57
|
+
</slot>
|
|
58
|
+
</button>
|
|
59
|
+
</li>
|
|
60
|
+
<li class="Slider__controls-item Slider__controls-item--next">
|
|
61
|
+
<button
|
|
62
|
+
class="Slider__control-button Slider__control-button--next button button--icon"
|
|
63
|
+
aria-label="Next Slide"
|
|
64
|
+
:aria-controls="trackId"
|
|
65
|
+
@click="next('next')"
|
|
66
|
+
:disabled="!loop && activeIndex === props.items.length - 1"
|
|
67
|
+
>
|
|
68
|
+
<slot name="next">
|
|
69
|
+
<UluIcon icon="type:next" class="Slider__control-icon" />
|
|
70
|
+
</slot>
|
|
71
|
+
</button>
|
|
72
|
+
</li>
|
|
73
|
+
</ul>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<ul v-if="nav" class="Slider__nav">
|
|
77
|
+
<li
|
|
78
|
+
v-for="(item, index) in items"
|
|
79
|
+
:key="index"
|
|
80
|
+
class="Slider__nav-item"
|
|
81
|
+
>
|
|
82
|
+
<button
|
|
83
|
+
class="Slider__nav-button"
|
|
84
|
+
:class="{ 'Slider__nav-button--active': index === activeIndex }"
|
|
85
|
+
@click="goto(index, 'nav')"
|
|
86
|
+
:aria-controls="getSlideId(index)"
|
|
87
|
+
:aria-label="`Go to slide ${index + 1}`"
|
|
88
|
+
:aria-current="index === activeIndex ? 'true' : null"
|
|
89
|
+
>
|
|
90
|
+
<slot name="nav" :item="item" :index="index" :active="index === activeIndex">
|
|
91
|
+
<span class="hidden-visually">Item {{ index + 1 }}</span>
|
|
92
|
+
</slot>
|
|
93
|
+
</button>
|
|
94
|
+
</li>
|
|
95
|
+
</ul>
|
|
96
|
+
</div>
|
|
97
|
+
</template>
|
|
98
|
+
|
|
99
|
+
<script setup>
|
|
100
|
+
import { ref, computed, onMounted, onBeforeUnmount, nextTick, watch } from 'vue';
|
|
101
|
+
import { useModifiers } from '../../composables/useModifiers.js';
|
|
102
|
+
import { newId } from '../../utils/dom.js';
|
|
103
|
+
import UluIcon from '../elements/UluIcon.vue';
|
|
104
|
+
|
|
105
|
+
const props = defineProps({
|
|
106
|
+
/**
|
|
107
|
+
* Array of slide items.
|
|
108
|
+
*/
|
|
109
|
+
items: {
|
|
110
|
+
type: Array,
|
|
111
|
+
required: true,
|
|
112
|
+
},
|
|
113
|
+
/**
|
|
114
|
+
* Transition type: 'slide', 'fade', or 'none'.
|
|
115
|
+
*/
|
|
116
|
+
transition: {
|
|
117
|
+
type: String,
|
|
118
|
+
default: 'slide',
|
|
119
|
+
validator: (val) => ['slide', 'fade', 'none'].includes(val)
|
|
120
|
+
},
|
|
121
|
+
/**
|
|
122
|
+
* Transition duration in milliseconds.
|
|
123
|
+
*/
|
|
124
|
+
duration: {
|
|
125
|
+
type: Number,
|
|
126
|
+
default: 700,
|
|
127
|
+
},
|
|
128
|
+
/**
|
|
129
|
+
* Easing function for the transition.
|
|
130
|
+
*/
|
|
131
|
+
timingFunction: {
|
|
132
|
+
type: String,
|
|
133
|
+
default: 'ease-in-out',
|
|
134
|
+
},
|
|
135
|
+
/**
|
|
136
|
+
* Enable infinite looping.
|
|
137
|
+
*/
|
|
138
|
+
loop: {
|
|
139
|
+
type: Boolean,
|
|
140
|
+
default: true,
|
|
141
|
+
},
|
|
142
|
+
/**
|
|
143
|
+
* Show dot navigation.
|
|
144
|
+
*/
|
|
145
|
+
nav: {
|
|
146
|
+
type: Boolean,
|
|
147
|
+
default: true,
|
|
148
|
+
},
|
|
149
|
+
/**
|
|
150
|
+
* Show previous/next controls.
|
|
151
|
+
*/
|
|
152
|
+
controls: {
|
|
153
|
+
type: Boolean,
|
|
154
|
+
default: true,
|
|
155
|
+
},
|
|
156
|
+
/**
|
|
157
|
+
* Autoplay duration in ms. If > 0, autoplay is enabled.
|
|
158
|
+
*/
|
|
159
|
+
autoplay: {
|
|
160
|
+
type: Number,
|
|
161
|
+
default: 0,
|
|
162
|
+
},
|
|
163
|
+
/**
|
|
164
|
+
* Automatically switch to 'fade' transition if OS prefers reduced motion.
|
|
165
|
+
*/
|
|
166
|
+
reduceMotionFallback: {
|
|
167
|
+
type: Boolean,
|
|
168
|
+
default: true,
|
|
169
|
+
},
|
|
170
|
+
/**
|
|
171
|
+
* Setting for element.focus() when navigating to a specific slide.
|
|
172
|
+
*/
|
|
173
|
+
focusOptions: {
|
|
174
|
+
type: Object,
|
|
175
|
+
default: () => ({
|
|
176
|
+
preventScroll: true,
|
|
177
|
+
focusVisible: false
|
|
178
|
+
})
|
|
179
|
+
},
|
|
180
|
+
/**
|
|
181
|
+
* Base class modifiers.
|
|
182
|
+
*/
|
|
183
|
+
modifiers: [String, Array],
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const emit = defineEmits(['change']);
|
|
187
|
+
|
|
188
|
+
const trackId = newId('ulu-slider-track');
|
|
189
|
+
const getSlideId = (index) => `${trackId}-slide-${index}`;
|
|
190
|
+
|
|
191
|
+
const trackRef = ref(null);
|
|
192
|
+
const slideRefs = ref([]);
|
|
193
|
+
const activeIndex = ref(0);
|
|
194
|
+
const isTransitioning = ref(false);
|
|
195
|
+
const prefersReducedMotion = ref(false);
|
|
196
|
+
|
|
197
|
+
const { resolvedModifiers } = useModifiers({ props, baseClass: 'slider' });
|
|
198
|
+
|
|
199
|
+
const actualTransition = computed(() => {
|
|
200
|
+
if (props.transition === 'none') return 'none';
|
|
201
|
+
if (props.reduceMotionFallback && prefersReducedMotion.value) return 'fade';
|
|
202
|
+
return props.transition;
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const setSlideRef = (el, index) => {
|
|
206
|
+
if (el) {
|
|
207
|
+
slideRefs.value[index] = el;
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const isUpcoming = (index) => {
|
|
212
|
+
if (!props.items || props.items.length === 0) return false;
|
|
213
|
+
const nextIdx = activeIndex.value === props.items.length - 1 ? 0 : activeIndex.value + 1;
|
|
214
|
+
const prevIdx = activeIndex.value === 0 ? props.items.length - 1 : activeIndex.value - 1;
|
|
215
|
+
return index === nextIdx || index === prevIdx;
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
// --- Autoplay Logic ---
|
|
219
|
+
const autoplayTimer = ref(null);
|
|
220
|
+
const startAutoplay = () => {
|
|
221
|
+
if (props.autoplay > 0) {
|
|
222
|
+
stopAutoplay();
|
|
223
|
+
autoplayTimer.value = setInterval(() => {
|
|
224
|
+
next('autoplay');
|
|
225
|
+
}, props.autoplay);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
const stopAutoplay = () => {
|
|
229
|
+
if (autoplayTimer.value) {
|
|
230
|
+
clearInterval(autoplayTimer.value);
|
|
231
|
+
autoplayTimer.value = null;
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
const pauseAutoplay = () => stopAutoplay();
|
|
235
|
+
const resumeAutoplay = () => startAutoplay();
|
|
236
|
+
|
|
237
|
+
watch(() => props.autoplay, () => {
|
|
238
|
+
startAutoplay();
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// --- Imperative DOM Helpers ---
|
|
242
|
+
const trackTransformX = ref(0);
|
|
243
|
+
const currentDuration = ref(0);
|
|
244
|
+
const orderedIndex = ref(null);
|
|
245
|
+
const fadedIndexes = ref([]);
|
|
246
|
+
const showAllSlides = ref(false);
|
|
247
|
+
|
|
248
|
+
const setVisibility = (activeIdx, showAll) => {
|
|
249
|
+
slideRefs.value.forEach((el, index) => {
|
|
250
|
+
if (!el) return;
|
|
251
|
+
if (showAll) {
|
|
252
|
+
el.style.visibility = 'visible';
|
|
253
|
+
} else {
|
|
254
|
+
el.style.visibility = index === activeIdx ? 'visible' : 'hidden';
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const setTrackTransform = (x, duration) => {
|
|
260
|
+
const track = trackRef.value;
|
|
261
|
+
if (!track) return;
|
|
262
|
+
track.style.transitionProperty = 'transform';
|
|
263
|
+
track.style.transitionDuration = `${duration}ms`;
|
|
264
|
+
track.style.transitionTimingFunction = props.timingFunction;
|
|
265
|
+
track.style.transform = `translateX(-${x}px)`;
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const setSlideOpacity = (index, opacity, duration) => {
|
|
269
|
+
const el = slideRefs.value[index];
|
|
270
|
+
if (!el) return;
|
|
271
|
+
el.style.transitionProperty = 'opacity';
|
|
272
|
+
el.style.transitionDuration = `${duration}ms`;
|
|
273
|
+
el.style.transitionTimingFunction = props.timingFunction;
|
|
274
|
+
el.style.opacity = opacity;
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const setSlideOrder = (index, order) => {
|
|
278
|
+
const el = slideRefs.value[index];
|
|
279
|
+
if (!el) return;
|
|
280
|
+
el.style.order = order;
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const getSlideOffset = (index) => {
|
|
284
|
+
return slideRefs.value[index]?.offsetLeft || 0;
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
const ensureTransitionEnds = (element, duration) => {
|
|
288
|
+
return new Promise(resolve => {
|
|
289
|
+
if (duration <= 0 || !element) {
|
|
290
|
+
resolve();
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
let timeoutIdStart = null;
|
|
295
|
+
let timeoutIdEnd = null;
|
|
296
|
+
|
|
297
|
+
const onComplete = () => {
|
|
298
|
+
clearTimeout(timeoutIdStart);
|
|
299
|
+
clearTimeout(timeoutIdEnd);
|
|
300
|
+
element.removeEventListener('transitionrun', onStart);
|
|
301
|
+
element.removeEventListener('transitionend', onComplete);
|
|
302
|
+
element.removeEventListener('transitioncancel', onComplete);
|
|
303
|
+
resolve();
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
const onStart = () => {
|
|
307
|
+
clearTimeout(timeoutIdStart);
|
|
308
|
+
timeoutIdEnd = setTimeout(onComplete, duration + 500);
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
element.addEventListener('transitionrun', onStart, { once: true });
|
|
312
|
+
element.addEventListener('transitionend', onComplete, { once: true });
|
|
313
|
+
element.addEventListener('transitioncancel', onComplete, { once: true });
|
|
314
|
+
|
|
315
|
+
// Fallback if event doesn't fire
|
|
316
|
+
timeoutIdStart = setTimeout(onComplete, duration + 500);
|
|
317
|
+
});
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
// --- Navigation Logic ---
|
|
321
|
+
|
|
322
|
+
const goto = async (index, triggerType = 'nav') => {
|
|
323
|
+
if (index === activeIndex.value || isTransitioning.value || !props.items || props.items.length === 0) return;
|
|
324
|
+
|
|
325
|
+
isTransitioning.value = true;
|
|
326
|
+
stopAutoplay();
|
|
327
|
+
|
|
328
|
+
const oldIndex = activeIndex.value;
|
|
329
|
+
const count = props.items.length;
|
|
330
|
+
const reverse = triggerType === 'previous';
|
|
331
|
+
const lastIndex = count - 1;
|
|
332
|
+
const lastToFirst = index === 0 && oldIndex === lastIndex;
|
|
333
|
+
const firstToLast = index === lastIndex && oldIndex === 0;
|
|
334
|
+
|
|
335
|
+
const transitionType = actualTransition.value;
|
|
336
|
+
|
|
337
|
+
if (transitionType === 'slide') {
|
|
338
|
+
let switchIndex = null;
|
|
339
|
+
let durationMultiplier = 1;
|
|
340
|
+
|
|
341
|
+
if (oldIndex !== null && !lastToFirst && !firstToLast) {
|
|
342
|
+
durationMultiplier = Math.abs(oldIndex - index);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (count < 3) {
|
|
346
|
+
if (lastToFirst && !reverse) {
|
|
347
|
+
switchIndex = oldIndex;
|
|
348
|
+
} else if (firstToLast) {
|
|
349
|
+
switchIndex = reverse ? index : oldIndex;
|
|
350
|
+
}
|
|
351
|
+
} else {
|
|
352
|
+
if (lastToFirst) {
|
|
353
|
+
switchIndex = oldIndex;
|
|
354
|
+
} else if (firstToLast) {
|
|
355
|
+
switchIndex = index;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
setVisibility(null, true);
|
|
360
|
+
|
|
361
|
+
if (switchIndex !== null) {
|
|
362
|
+
setSlideOrder(switchIndex, '-1');
|
|
363
|
+
setTrackTransform(lastToFirst ? 0 : getSlideOffset(oldIndex), 0);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const duration = props.duration * Math.min(durationMultiplier, count);
|
|
367
|
+
|
|
368
|
+
trackRef.value?.getBoundingClientRect();
|
|
369
|
+
|
|
370
|
+
setTrackTransform(getSlideOffset(index), duration);
|
|
371
|
+
|
|
372
|
+
await ensureTransitionEnds(trackRef.value, duration);
|
|
373
|
+
|
|
374
|
+
if (switchIndex !== null) {
|
|
375
|
+
setSlideOrder(switchIndex, '0');
|
|
376
|
+
setTrackTransform(getSlideOffset(index), 0);
|
|
377
|
+
|
|
378
|
+
await nextTick();
|
|
379
|
+
trackRef.value?.getBoundingClientRect();
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
setVisibility(index, false);
|
|
383
|
+
|
|
384
|
+
} else if (transitionType === 'fade') {
|
|
385
|
+
const duration = props.duration;
|
|
386
|
+
|
|
387
|
+
setVisibility(null, true);
|
|
388
|
+
|
|
389
|
+
if (oldIndex !== null) {
|
|
390
|
+
setSlideOpacity(oldIndex, '0', duration);
|
|
391
|
+
// Wait for the old slide to fade out completely
|
|
392
|
+
await ensureTransitionEnds(slideRefs.value[oldIndex], duration);
|
|
393
|
+
setSlideOrder(oldIndex, '0');
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Reset track if we previously came from a 'slide' transition
|
|
397
|
+
setTrackTransform(0, 0);
|
|
398
|
+
|
|
399
|
+
setSlideOrder(index, '-1');
|
|
400
|
+
slideRefs.value[index]?.getBoundingClientRect(); // Force reflow so it knows it's at opacity 0
|
|
401
|
+
|
|
402
|
+
setSlideOpacity(index, '1', duration);
|
|
403
|
+
await ensureTransitionEnds(slideRefs.value[index], duration);
|
|
404
|
+
|
|
405
|
+
setVisibility(index, false);
|
|
406
|
+
|
|
407
|
+
} else {
|
|
408
|
+
// None
|
|
409
|
+
setVisibility(null, true);
|
|
410
|
+
if (oldIndex !== null) {
|
|
411
|
+
setSlideOrder(oldIndex, '0');
|
|
412
|
+
setSlideOpacity(oldIndex, '1', 0);
|
|
413
|
+
}
|
|
414
|
+
setTrackTransform(0, 0);
|
|
415
|
+
setSlideOrder(index, '-1');
|
|
416
|
+
setSlideOpacity(index, '1', 0);
|
|
417
|
+
setVisibility(index, false);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
activeIndex.value = index;
|
|
421
|
+
isTransitioning.value = false;
|
|
422
|
+
|
|
423
|
+
if (triggerType !== 'init') {
|
|
424
|
+
const targetSlide = slideRefs.value[index];
|
|
425
|
+
if (targetSlide && targetSlide.focus) {
|
|
426
|
+
targetSlide.focus(props.focusOptions);
|
|
427
|
+
}
|
|
428
|
+
emit('change', { index, item: props.items[index] });
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
startAutoplay();
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
const move = (direction) => {
|
|
435
|
+
if (!props.items) return;
|
|
436
|
+
const count = props.items.length;
|
|
437
|
+
if (count === 0) return;
|
|
438
|
+
|
|
439
|
+
const isNext = direction === 'next';
|
|
440
|
+
const targetIndex = isNext ? activeIndex.value + 1 : activeIndex.value - 1;
|
|
441
|
+
|
|
442
|
+
if (targetIndex > count - 1) {
|
|
443
|
+
if (props.loop) goto(0, direction);
|
|
444
|
+
} else if (targetIndex < 0) {
|
|
445
|
+
if (props.loop) goto(count - 1, direction);
|
|
446
|
+
} else {
|
|
447
|
+
goto(targetIndex, direction);
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
const next = () => move('next');
|
|
452
|
+
const previous = () => move('previous');
|
|
453
|
+
|
|
454
|
+
onMounted(() => {
|
|
455
|
+
if (typeof window !== 'undefined') {
|
|
456
|
+
prefersReducedMotion.value = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Initialize styles based on transition type
|
|
460
|
+
nextTick(() => {
|
|
461
|
+
const isSlide = actualTransition.value === 'slide';
|
|
462
|
+
|
|
463
|
+
slideRefs.value.forEach((el, index) => {
|
|
464
|
+
if (!el) return;
|
|
465
|
+
el.style.visibility = index === activeIndex.value ? 'visible' : 'hidden';
|
|
466
|
+
if (!isSlide) {
|
|
467
|
+
el.style.opacity = index === activeIndex.value ? '1' : '0';
|
|
468
|
+
el.style.order = index === activeIndex.value ? '-1' : '0';
|
|
469
|
+
} else {
|
|
470
|
+
el.style.opacity = '1';
|
|
471
|
+
el.style.order = '0';
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
if (isSlide) {
|
|
476
|
+
setTrackTransform(getSlideOffset(activeIndex.value), 0);
|
|
477
|
+
} else {
|
|
478
|
+
setTrackTransform(0, 0);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
startAutoplay();
|
|
482
|
+
});
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
onBeforeUnmount(() => {
|
|
486
|
+
stopAutoplay();
|
|
487
|
+
});
|
|
488
|
+
</script>
|
package/lib/components/index.js
CHANGED
|
@@ -20,13 +20,22 @@ export { default as UluBadgeStack } from './elements/UluBadgeStack.vue';
|
|
|
20
20
|
export { default as UluButton } from './elements/UluButton.vue';
|
|
21
21
|
export { default as UluButtonVerbose } from './elements/UluButtonVerbose.vue';
|
|
22
22
|
export { default as UluCallout } from './elements/UluCallout.vue';
|
|
23
|
+
export { default as UluCaptionedFigure } from './elements/UluCaptionedFigure.vue';
|
|
23
24
|
export { default as UluCard } from './elements/UluCard.vue';
|
|
24
25
|
export { default as UluDefinitionList } from './elements/UluDefinitionList.vue';
|
|
25
26
|
export { default as UluExternalLink } from './elements/UluExternalLink.vue';
|
|
26
27
|
export { default as UluIcon } from './elements/UluIcon.vue';
|
|
28
|
+
export { default as UluImage } from './elements/UluImage.vue';
|
|
29
|
+
|
|
30
|
+
// TODO (Still needs stylesheet for main library [and any scripting over there so it's contained in ulu])
|
|
31
|
+
// export { default as UluImageSlider } from './elements/UluImageSlider.vue';
|
|
32
|
+
|
|
27
33
|
export { default as UluList } from './elements/UluList.vue';
|
|
28
34
|
export { default as UluMain } from './elements/UluMain.vue';
|
|
35
|
+
export { default as UluOverflowScroller } from './elements/UluOverflowScroller.vue';
|
|
29
36
|
export { default as UluRule } from './elements/UluRule.vue';
|
|
37
|
+
export { default as UluScrollSlider } from './elements/UluScrollSlider.vue';
|
|
38
|
+
export { default as UluSlider } from './elements/UluSlider.vue';
|
|
30
39
|
export { default as UluSpokeSpinner } from './elements/UluSpokeSpinner.vue';
|
|
31
40
|
export { default as UluTag } from './elements/UluTag.vue';
|
|
32
41
|
export { default as UluSelectableMenu } from './forms/UluSelectableMenu.vue';
|
|
@@ -55,6 +64,7 @@ export { default as UluMenuStack } from './navigation/UluMenuStack.vue';
|
|
|
55
64
|
export { default as UluNavStrip } from './navigation/UluNavStrip.vue';
|
|
56
65
|
export { default as UluPager } from './navigation/UluPager.vue';
|
|
57
66
|
export { default as UluSkipLink } from './navigation/UluSkipLink.vue';
|
|
67
|
+
export { default as UluAction } from './utils/UluAction.vue';
|
|
58
68
|
export { default as UluConditionalText } from './utils/UluConditionalText.vue';
|
|
59
69
|
export { default as UluConditionalWrapper } from './utils/UluConditionalWrapper.vue';
|
|
60
70
|
export { default as UluEmpty } from './utils/UluEmpty.vue';
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
|
-
class="rail
|
|
4
|
-
:class="
|
|
5
|
-
'rail--rule' : rule
|
|
6
|
-
}"
|
|
3
|
+
class="rail"
|
|
4
|
+
:class="resolvedModifiers"
|
|
7
5
|
>
|
|
8
6
|
<div class="rail__item rail__item--title" :class="classes.itemTitle">
|
|
9
7
|
<component
|
|
@@ -28,51 +26,60 @@
|
|
|
28
26
|
</div>
|
|
29
27
|
</template>
|
|
30
28
|
|
|
31
|
-
<script>
|
|
29
|
+
<script setup>
|
|
30
|
+
import { computed } from "vue";
|
|
32
31
|
import UluIcon from "../elements/UluIcon.vue";
|
|
32
|
+
import { useModifiers } from "../../composables/useModifiers.js";
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
const props = defineProps({
|
|
35
|
+
/**
|
|
36
|
+
* Icon to display next to the title.
|
|
37
|
+
*/
|
|
38
|
+
icon: String,
|
|
39
|
+
/**
|
|
40
|
+
* The alignment of the icon with the title.
|
|
41
|
+
*/
|
|
42
|
+
iconAlign: {
|
|
43
|
+
type: String,
|
|
44
|
+
default: "baseline"
|
|
38
45
|
},
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
rule:
|
|
76
|
-
}
|
|
77
|
-
}
|
|
46
|
+
/**
|
|
47
|
+
* Classes for the different elements in the component.
|
|
48
|
+
*/
|
|
49
|
+
classes: {
|
|
50
|
+
type: Object,
|
|
51
|
+
default: () => ({
|
|
52
|
+
title: "h2",
|
|
53
|
+
icon: "margin-right-small"
|
|
54
|
+
})
|
|
55
|
+
},
|
|
56
|
+
/**
|
|
57
|
+
* The title to display.
|
|
58
|
+
*/
|
|
59
|
+
title: String,
|
|
60
|
+
/**
|
|
61
|
+
* The HTML element to use for the title.
|
|
62
|
+
*/
|
|
63
|
+
titleElement: {
|
|
64
|
+
type: String,
|
|
65
|
+
default: "h2"
|
|
66
|
+
},
|
|
67
|
+
/**
|
|
68
|
+
* If true, a rule will be displayed under the title.
|
|
69
|
+
*/
|
|
70
|
+
rule: Boolean,
|
|
71
|
+
/**
|
|
72
|
+
* Modifiers (to add any modifier classes based on base class)
|
|
73
|
+
*/
|
|
74
|
+
modifiers: [String, Array]
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const { resolvedModifiers } = useModifiers({
|
|
78
|
+
props,
|
|
79
|
+
baseClass: "rail",
|
|
80
|
+
internal: computed(() => ({
|
|
81
|
+
"title-rail": true,
|
|
82
|
+
"rule": props.rule,
|
|
83
|
+
}))
|
|
84
|
+
});
|
|
78
85
|
</script>
|