nuxt-hero 0.1.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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +212 -0
  3. package/dist/module.d.mts +19 -0
  4. package/dist/module.json +12 -0
  5. package/dist/module.mjs +322 -0
  6. package/dist/runtime/assets/hero.css +1 -0
  7. package/dist/runtime/components/navigation/HeroNavigation.d.vue.ts +18 -0
  8. package/dist/runtime/components/navigation/HeroNavigation.vue +68 -0
  9. package/dist/runtime/components/navigation/HeroNavigation.vue.d.ts +18 -0
  10. package/dist/runtime/components/navigation/HeroPagination.d.vue.ts +22 -0
  11. package/dist/runtime/components/navigation/HeroPagination.vue +50 -0
  12. package/dist/runtime/components/navigation/HeroPagination.vue.d.ts +22 -0
  13. package/dist/runtime/components/slider/HeroSlide.d.vue.ts +66 -0
  14. package/dist/runtime/components/slider/HeroSlide.vue +124 -0
  15. package/dist/runtime/components/slider/HeroSlide.vue.d.ts +66 -0
  16. package/dist/runtime/components/slider/index.d.vue.ts +69 -0
  17. package/dist/runtime/components/slider/index.vue +200 -0
  18. package/dist/runtime/components/slider/index.vue.d.ts +69 -0
  19. package/dist/runtime/components/video/HeroSlideVideo.d.vue.ts +39 -0
  20. package/dist/runtime/components/video/HeroSlideVideo.vue +116 -0
  21. package/dist/runtime/components/video/HeroSlideVideo.vue.d.ts +39 -0
  22. package/dist/runtime/components/video/HeroVideoControls.d.vue.ts +12 -0
  23. package/dist/runtime/components/video/HeroVideoControls.vue +87 -0
  24. package/dist/runtime/components/video/HeroVideoControls.vue.d.ts +12 -0
  25. package/dist/runtime/components/video/HeroVideoScrubber.d.vue.ts +64 -0
  26. package/dist/runtime/components/video/HeroVideoScrubber.vue +50 -0
  27. package/dist/runtime/components/video/HeroVideoScrubber.vue.d.ts +64 -0
  28. package/dist/runtime/composables/_autoplay.d.ts +25 -0
  29. package/dist/runtime/composables/_autoplay.js +72 -0
  30. package/dist/runtime/composables/_gsap.d.ts +60 -0
  31. package/dist/runtime/composables/_gsap.js +135 -0
  32. package/dist/runtime/composables/_hls.d.ts +35 -0
  33. package/dist/runtime/composables/_hls.js +88 -0
  34. package/dist/runtime/composables/_slides.d.ts +26 -0
  35. package/dist/runtime/composables/_slides.js +40 -0
  36. package/dist/runtime/composables/_swiper.d.ts +23 -0
  37. package/dist/runtime/composables/_swiper.js +52 -0
  38. package/dist/runtime/composables/_video.d.ts +30 -0
  39. package/dist/runtime/composables/_video.js +89 -0
  40. package/dist/runtime/composables/useHeroSlider.d.ts +24 -0
  41. package/dist/runtime/composables/useHeroSlider.js +131 -0
  42. package/dist/runtime/hero-swiper-modules.d.ts +4 -0
  43. package/dist/runtime/types.d.ts +221 -0
  44. package/dist/runtime/types.js +0 -0
  45. package/dist/runtime/utils.d.ts +22 -0
  46. package/dist/runtime/utils.js +50 -0
  47. package/dist/types.d.mts +9 -0
  48. package/package.json +94 -0
@@ -0,0 +1,18 @@
1
+ import type { HeroSlide } from '#hero/types';
2
+ interface NavigationProps {
3
+ slides: HeroSlide[];
4
+ activeIndex: number;
5
+ /** When true, prev is on top and next is on bottom */
6
+ vertical?: boolean;
7
+ }
8
+ declare const __VLS_export: import("vue").DefineComponent<NavigationProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
9
+ next: () => any;
10
+ prev: () => any;
11
+ }, string, import("vue").PublicProps, Readonly<NavigationProps> & Readonly<{
12
+ onNext?: (() => any) | undefined;
13
+ onPrev?: (() => any) | undefined;
14
+ }>, {
15
+ vertical: boolean;
16
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
17
+ declare const _default: typeof __VLS_export;
18
+ export default _default;
@@ -0,0 +1,22 @@
1
+ import type { HeroSlide } from '#hero/types';
2
+ interface PaginationProps {
3
+ slides: HeroSlide[];
4
+ activeIndex: number;
5
+ /** Snap-based active index (accounts for slidesPerView) */
6
+ snapIndex: number;
7
+ /** Total number of snap points (pagination dots to show) */
8
+ totalSnaps: number;
9
+ /** Autoplay progress 0-1 */
10
+ progress: number;
11
+ /** When true, renders vertically on the side (RTL/LTR aware) */
12
+ vertical?: boolean;
13
+ }
14
+ declare const __VLS_export: import("vue").DefineComponent<PaginationProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
15
+ slideTo: (index: number) => any;
16
+ }, string, import("vue").PublicProps, Readonly<PaginationProps> & Readonly<{
17
+ onSlideTo?: ((index: number) => any) | undefined;
18
+ }>, {
19
+ vertical: boolean;
20
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
21
+ declare const _default: typeof __VLS_export;
22
+ export default _default;
@@ -0,0 +1,50 @@
1
+ <script setup>
2
+ const props = defineProps({
3
+ slides: { type: Array, required: true },
4
+ activeIndex: { type: Number, required: true },
5
+ snapIndex: { type: Number, required: true },
6
+ totalSnaps: { type: Number, required: true },
7
+ progress: { type: Number, required: true },
8
+ vertical: { type: Boolean, required: false, default: false }
9
+ });
10
+ const emit = defineEmits(["slideTo"]);
11
+ </script>
12
+
13
+ <template>
14
+ <!--
15
+ Horizontal (default): centered at bottom
16
+ Vertical: on the side — ltr:right, rtl:left — centered vertically
17
+ -->
18
+ <nav role="navigation" aria-label="Slide pagination"
19
+ class="hero-pagination swiper-pagination pointer-events-auto absolute z-10 flex items-center gap-1 rounded-full p-1 px-1.5 ring-2 ring-white bg-black/35 backdrop-blur-sm"
20
+ :class="vertical ? 'flex-col top-1/2 -translate-y-1/2 ltr:right-6 rtl:left-6' : 'bottom-6 left-1/2 -translate-x-1/2'">
21
+ <button v-for="i in totalSnaps" :key="i - 1" type="button"
22
+ class="group relative flex size-4 shrink-0 items-center justify-center rounded-full"
23
+ :class="{ active: snapIndex === i - 1 }" :aria-label="`Go to slide ${i}`"
24
+ :aria-current="snapIndex === i - 1 ? 'step' : void 0" @click="emit('slideTo', i - 1)">
25
+ <!-- Inactive dot -->
26
+ <span v-if="snapIndex !== i - 1"
27
+ class="hero-dot bg-white size-2.5 cursor-pointer rounded-full transition-colors" />
28
+
29
+ <!-- Active: progress circle -->
30
+ <template v-else>
31
+ <div class="hero-radial-progress text-white "
32
+ :style="{ '--hero-progress-value': Math.round(progress * 100), '--hero-progress-size': '0.75rem', '--hero-progress-thickness': '0.15rem' }"
33
+ :aria-valuenow="Math.round(progress * 100)" role="progressbar" />
34
+ <div class="hero-radial-progress absolute text-white opacity-25"
35
+ :style="{ '--hero-progress-value': 100, '--hero-progress-size': '0.75rem', '--hero-progress-thickness': '0.15rem' }"
36
+ aria-valuenow="100" role="progressbar" />
37
+ </template>
38
+
39
+ <!-- Thumbnail tooltip on hover (show first slide of the snap group) -->
40
+ <div v-if="slides[i - 1]?.thumbSrc" class="tooltip-content border-white">
41
+ <div class="tooltip-text border-inherit">
42
+ <div class="tooltip-inner overflow-hidden rounded-sm bg-black">
43
+ <img :src="slides[i - 1]?.thumbSrc" :alt="slides[i - 1]?.title ?? `Slide ${i}`"
44
+ class="size-full object-cover opacity-65" loading="lazy" />
45
+ </div>
46
+ </div>
47
+ </div>
48
+ </button>
49
+ </nav>
50
+ </template>
@@ -0,0 +1,22 @@
1
+ import type { HeroSlide } from '#hero/types';
2
+ interface PaginationProps {
3
+ slides: HeroSlide[];
4
+ activeIndex: number;
5
+ /** Snap-based active index (accounts for slidesPerView) */
6
+ snapIndex: number;
7
+ /** Total number of snap points (pagination dots to show) */
8
+ totalSnaps: number;
9
+ /** Autoplay progress 0-1 */
10
+ progress: number;
11
+ /** When true, renders vertically on the side (RTL/LTR aware) */
12
+ vertical?: boolean;
13
+ }
14
+ declare const __VLS_export: import("vue").DefineComponent<PaginationProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
15
+ slideTo: (index: number) => any;
16
+ }, string, import("vue").PublicProps, Readonly<PaginationProps> & Readonly<{
17
+ onSlideTo?: ((index: number) => any) | undefined;
18
+ }>, {
19
+ vertical: boolean;
20
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
21
+ declare const _default: typeof __VLS_export;
22
+ export default _default;
@@ -0,0 +1,66 @@
1
+ import type { MediaControlsOptions, VideoMediaControls } from '#hero/types';
2
+ interface SlideProps {
3
+ bgSrc: string;
4
+ bgDarkSrc?: string;
5
+ poster?: string;
6
+ imagePreset?: string;
7
+ eager?: boolean;
8
+ isActive?: boolean;
9
+ animationClass?: string;
10
+ slideIndex?: number;
11
+ onVideoReady?: (index: number, controls: VideoMediaControls) => void;
12
+ onVideoRemoved?: (index: number) => void;
13
+ mediaControlsOptions?: MediaControlsOptions;
14
+ showVideoControls?: boolean;
15
+ videoLoop?: boolean;
16
+ /** Whether video should auto-play when slide becomes active */
17
+ autoPlay?: boolean;
18
+ containerClass?: string;
19
+ bgClass?: string;
20
+ }
21
+ declare var __VLS_13: {}, __VLS_15: {}, __VLS_17: {
22
+ playing: import("vue").ShallowRef<boolean>;
23
+ togglePlay: () => void;
24
+ currentTime: import("vue").ShallowRef<number>;
25
+ duration: import("vue").ShallowRef<number>;
26
+ buffered: import("vue").Ref<[number, number][], [number, number][]>;
27
+ volume: import("vue").ShallowRef<number>;
28
+ muted: import("vue").ShallowRef<boolean>;
29
+ waiting: import("vue").ShallowRef<boolean>;
30
+ hls: {
31
+ loading: import("vue").Ref<boolean, boolean>;
32
+ error: import("vue").Ref<string | null, string | null>;
33
+ qualities: import("vue").Ref<number[], number[]>;
34
+ setQuality: (height: number) => void;
35
+ } | null;
36
+ };
37
+ type __VLS_Slots = {} & {
38
+ overlay?: (props: typeof __VLS_13) => any;
39
+ } & {
40
+ default?: (props: typeof __VLS_15) => any;
41
+ } & {
42
+ 'video-controls'?: (props: typeof __VLS_17) => any;
43
+ };
44
+ declare const __VLS_base: import("vue").DefineComponent<SlideProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<SlideProps> & Readonly<{}>, {
45
+ showVideoControls: boolean;
46
+ isActive: boolean;
47
+ slideIndex: number;
48
+ onVideoReady: (index: number, controls: VideoMediaControls) => void;
49
+ onVideoRemoved: (index: number) => void;
50
+ mediaControlsOptions: MediaControlsOptions;
51
+ videoLoop: boolean;
52
+ autoPlay: boolean;
53
+ imagePreset: string;
54
+ eager: boolean;
55
+ animationClass: string;
56
+ containerClass: string;
57
+ bgClass: string;
58
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
59
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
60
+ declare const _default: typeof __VLS_export;
61
+ export default _default;
62
+ type __VLS_WithSlots<T, S> = T & {
63
+ new (): {
64
+ $slots: S;
65
+ };
66
+ };
@@ -0,0 +1,124 @@
1
+ <script setup>
2
+ import { computed, ref, useTemplateRef, watch } from "vue";
3
+ import { useMounted } from "@vueuse/core";
4
+ import { useColorMode, useRuntimeConfig } from "#imports";
5
+ import { isVideoUrl, getHeroConfig } from "#hero/utils";
6
+ import HeroSlideVideo from "../video/HeroSlideVideo.vue";
7
+ const props = defineProps({
8
+ bgSrc: { type: String, required: true },
9
+ bgDarkSrc: { type: String, required: false },
10
+ poster: { type: String, required: false },
11
+ imagePreset: { type: String, required: false, default: "" },
12
+ eager: { type: Boolean, required: false, default: false },
13
+ isActive: { type: Boolean, required: false, default: false },
14
+ animationClass: { type: String, required: false, default: "" },
15
+ slideIndex: { type: Number, required: false, default: 0 },
16
+ onVideoReady: { type: Function, required: false, default: void 0 },
17
+ onVideoRemoved: { type: Function, required: false, default: void 0 },
18
+ mediaControlsOptions: { type: null, required: false, default: void 0 },
19
+ showVideoControls: { type: Boolean, required: false, default: false },
20
+ videoLoop: { type: Boolean, required: false, default: false },
21
+ autoPlay: { type: Boolean, required: false, default: true },
22
+ containerClass: { type: String, required: false, default: "" },
23
+ bgClass: { type: String, required: false, default: "" }
24
+ });
25
+ const colorMode = useColorMode();
26
+ const isDark = computed(() => colorMode.value === "dark");
27
+ const isMounted = useMounted();
28
+ const heroConfig = getHeroConfig(useRuntimeConfig());
29
+ const hasNuxtImage = !!heroConfig.hasNuxtImage;
30
+ const videoEnabled = !!heroConfig.features?.video;
31
+ const activeBgSrc = computed(
32
+ () => isMounted.value && isDark.value && props.bgDarkSrc ? props.bgDarkSrc : props.bgSrc
33
+ );
34
+ const isBgVideo = computed(() => videoEnabled && isVideoUrl(activeBgSrc.value));
35
+ const videoComponentRef = useTemplateRef("videoComponentRef");
36
+ const contentVisible = ref(props.isActive || !props.animationClass);
37
+ const contentAnimClass = ref(props.animationClass);
38
+ watch(
39
+ () => props.isActive,
40
+ (active) => {
41
+ if (active) {
42
+ contentVisible.value = true;
43
+ contentAnimClass.value = props.animationClass;
44
+ }
45
+ }
46
+ );
47
+ watch(
48
+ () => props.animationClass,
49
+ (cls) => {
50
+ contentAnimClass.value = cls;
51
+ if (!props.isActive && cls) contentVisible.value = true;
52
+ else if (!props.isActive && !cls) contentVisible.value = false;
53
+ }
54
+ );
55
+ function onAnimationEnd() {
56
+ if (!props.isActive) contentVisible.value = false;
57
+ }
58
+ const shouldShowVideoControls = computed(
59
+ () => isBgVideo.value && props.showVideoControls && !!videoComponentRef.value?.mediaControls
60
+ );
61
+ const hlsSlotData = computed(() => {
62
+ const hls = videoComponentRef.value?.hlsState;
63
+ if (!hls) return null;
64
+ return {
65
+ loading: hls.loading,
66
+ error: hls.error,
67
+ qualities: hls.qualities,
68
+ setQuality: hls.setQuality
69
+ };
70
+ });
71
+ </script>
72
+
73
+ <template>
74
+ <div class="hero-slide relative flex size-full" :class="containerClass">
75
+ <!-- Background layer (GSAP targets .hero-slide-bg) -->
76
+ <div class="hero-slide-bg absolute inset-0 z-0 flex items-center justify-center overflow-hidden" :class="bgClass">
77
+ <!-- Video background (lazy loaded) -->
78
+ <HeroSlideVideo v-if="isBgVideo" ref="videoComponentRef" :src="activeBgSrc" :poster="poster" :is-active="isActive"
79
+ :slide-index="slideIndex" :on-video-ready="onVideoReady" :on-video-removed="onVideoRemoved"
80
+ :media-controls-options="mediaControlsOptions" :video-loop="videoLoop" :auto-play="autoPlay" />
81
+ <!-- Image background -->
82
+ <NuxtImg v-else-if="hasNuxtImage" :src="activeBgSrc" :preset="imagePreset || void 0"
83
+ :loading="eager ? 'eager' : 'lazy'" alt="" class="size-full object-cover will-change-transform" />
84
+ <img v-else :src="activeBgSrc" :loading="eager ? 'eager' : 'lazy'" alt=""
85
+ class="size-full object-cover will-change-transform" />
86
+ </div>
87
+
88
+ <!-- Inset shadow overlay -->
89
+ <span class="pointer-events-none absolute inset-0 z-1 [box-shadow:0rem_-8rem_8rem_-6rem_#000000_inset]" />
90
+
91
+ <!-- Overlay patterns (delegated from parent via slot) -->
92
+ <slot name="overlay" />
93
+
94
+ <!-- Content container (GSAP targets .hero-slide-content) -->
95
+ <div class="hero-slide-content relative z-5 flex size-full flex-col">
96
+ <div v-show="contentVisible" :class="contentAnimClass" class="size-full" @animationend="onAnimationEnd">
97
+ <slot />
98
+ </div>
99
+ </div>
100
+
101
+ <!-- Video controls: rendered outside bg/content layers so parallax doesn't affect them -->
102
+ <template v-if="shouldShowVideoControls">
103
+ <slot name="video-controls" v-bind="{
104
+ playing: videoComponentRef.mediaControls.playing,
105
+ togglePlay: () => {
106
+ videoComponentRef.mediaControls.playing.value = !videoComponentRef.mediaControls.playing.value;
107
+ },
108
+ currentTime: videoComponentRef.mediaControls.currentTime,
109
+ duration: videoComponentRef.mediaControls.duration,
110
+ buffered: videoComponentRef.mediaControls.buffered,
111
+ volume: videoComponentRef.mediaControls.volume,
112
+ muted: videoComponentRef.mediaControls.muted,
113
+ waiting: videoComponentRef.mediaControls.waiting,
114
+ hls: hlsSlotData
115
+ }">
116
+ <HeroVideoControls :playing="videoComponentRef.mediaControls.playing"
117
+ :waiting="videoComponentRef.mediaControls.waiting"
118
+ :current-time="videoComponentRef.mediaControls.currentTime"
119
+ :duration="videoComponentRef.mediaControls.duration" :volume="videoComponentRef.mediaControls.volume"
120
+ :muted="videoComponentRef.mediaControls.muted" />
121
+ </slot>
122
+ </template>
123
+ </div>
124
+ </template>
@@ -0,0 +1,66 @@
1
+ import type { MediaControlsOptions, VideoMediaControls } from '#hero/types';
2
+ interface SlideProps {
3
+ bgSrc: string;
4
+ bgDarkSrc?: string;
5
+ poster?: string;
6
+ imagePreset?: string;
7
+ eager?: boolean;
8
+ isActive?: boolean;
9
+ animationClass?: string;
10
+ slideIndex?: number;
11
+ onVideoReady?: (index: number, controls: VideoMediaControls) => void;
12
+ onVideoRemoved?: (index: number) => void;
13
+ mediaControlsOptions?: MediaControlsOptions;
14
+ showVideoControls?: boolean;
15
+ videoLoop?: boolean;
16
+ /** Whether video should auto-play when slide becomes active */
17
+ autoPlay?: boolean;
18
+ containerClass?: string;
19
+ bgClass?: string;
20
+ }
21
+ declare var __VLS_13: {}, __VLS_15: {}, __VLS_17: {
22
+ playing: import("vue").ShallowRef<boolean>;
23
+ togglePlay: () => void;
24
+ currentTime: import("vue").ShallowRef<number>;
25
+ duration: import("vue").ShallowRef<number>;
26
+ buffered: import("vue").Ref<[number, number][], [number, number][]>;
27
+ volume: import("vue").ShallowRef<number>;
28
+ muted: import("vue").ShallowRef<boolean>;
29
+ waiting: import("vue").ShallowRef<boolean>;
30
+ hls: {
31
+ loading: import("vue").Ref<boolean, boolean>;
32
+ error: import("vue").Ref<string | null, string | null>;
33
+ qualities: import("vue").Ref<number[], number[]>;
34
+ setQuality: (height: number) => void;
35
+ } | null;
36
+ };
37
+ type __VLS_Slots = {} & {
38
+ overlay?: (props: typeof __VLS_13) => any;
39
+ } & {
40
+ default?: (props: typeof __VLS_15) => any;
41
+ } & {
42
+ 'video-controls'?: (props: typeof __VLS_17) => any;
43
+ };
44
+ declare const __VLS_base: import("vue").DefineComponent<SlideProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<SlideProps> & Readonly<{}>, {
45
+ showVideoControls: boolean;
46
+ isActive: boolean;
47
+ slideIndex: number;
48
+ onVideoReady: (index: number, controls: VideoMediaControls) => void;
49
+ onVideoRemoved: (index: number) => void;
50
+ mediaControlsOptions: MediaControlsOptions;
51
+ videoLoop: boolean;
52
+ autoPlay: boolean;
53
+ imagePreset: string;
54
+ eager: boolean;
55
+ animationClass: string;
56
+ containerClass: string;
57
+ bgClass: string;
58
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
59
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
60
+ declare const _default: typeof __VLS_export;
61
+ export default _default;
62
+ type __VLS_WithSlots<T, S> = T & {
63
+ new (): {
64
+ $slots: S;
65
+ };
66
+ };
@@ -0,0 +1,69 @@
1
+ import type { HeroSliderProps } from '#hero/types';
2
+ import { patternCSS, patternSize } from '#hero/utils';
3
+ declare var __VLS_30: {
4
+ slide: import("#hero/types").HeroSlide;
5
+ index: number;
6
+ isActive: boolean;
7
+ animationClass: string;
8
+ isVideo: boolean;
9
+ videoPlaying: boolean;
10
+ videoDuration: number;
11
+ videoCurrentTime: number;
12
+ videoWaiting: boolean;
13
+ videoEnded: boolean;
14
+ videoMuted: boolean;
15
+ videoVolume: number;
16
+ videoToggle: () => void;
17
+ videoSeek: (time: number) => void;
18
+ videoSetVolume: (v: number) => void;
19
+ videoToggleMute: () => void;
20
+ }, __VLS_33: any, __VLS_36: {
21
+ patterns: import("#hero/types").OverlayPattern[];
22
+ index: number;
23
+ isActive: boolean;
24
+ patternCSS: typeof patternCSS;
25
+ patternSize: typeof patternSize;
26
+ }, __VLS_38: {
27
+ activeIndex: number;
28
+ snapIndex: number;
29
+ totalSnaps: number;
30
+ total: number;
31
+ progress: number;
32
+ goTo: (index: number) => void;
33
+ vertical: boolean;
34
+ autoplayEnabled: boolean;
35
+ }, __VLS_47: {
36
+ prev: () => void;
37
+ next: () => void;
38
+ activeIndex: number;
39
+ slides: import("#hero/types").HeroSlide[];
40
+ vertical: boolean;
41
+ };
42
+ type __VLS_Slots = {} & {
43
+ slide?: (props: typeof __VLS_30) => any;
44
+ } & {
45
+ 'video-controls'?: (props: typeof __VLS_33) => any;
46
+ } & {
47
+ overlay?: (props: typeof __VLS_36) => any;
48
+ } & {
49
+ pagination?: (props: typeof __VLS_38) => any;
50
+ } & {
51
+ navigation?: (props: typeof __VLS_47) => any;
52
+ };
53
+ declare const __VLS_base: import("vue").DefineComponent<HeroSliderProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<HeroSliderProps> & Readonly<{}>, {
54
+ enterAnimation: string;
55
+ leaveAnimation: string;
56
+ imagePreset: string;
57
+ overlayPatterns: import("#hero/types").OverlayPattern[];
58
+ parallax: import("vue").MaybeRefOrGetter<boolean | import("#hero/types").ParallaxConfig>;
59
+ as: string;
60
+ ui: import("#hero/types").HeroSliderUI;
61
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
62
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
63
+ declare const _default: typeof __VLS_export;
64
+ export default _default;
65
+ type __VLS_WithSlots<T, S> = T & {
66
+ new (): {
67
+ $slots: S;
68
+ };
69
+ };
@@ -0,0 +1,200 @@
1
+ <script setup>
2
+ import { computed, toValue, useTemplateRef, watchEffect } from "vue";
3
+ import { Swiper, SwiperSlide } from "swiper/vue";
4
+ import { useElementBounding, useWindowSize } from "@vueuse/core";
5
+ import { useRuntimeConfig } from "#imports";
6
+ import { swiperModules } from "#hero/swiper-modules";
7
+ import { resolveParallaxConfig, formatTime, isVideoUrl, patternCSS, patternSize, getHeroConfig } from "#hero/utils";
8
+ import { useGSAP } from "#hero/composables/_gsap";
9
+ const props = defineProps({
10
+ slides: { type: Array, required: true },
11
+ slider: { type: Object, required: true },
12
+ enterAnimation: { type: String, required: false, default: "" },
13
+ leaveAnimation: { type: String, required: false, default: "" },
14
+ overlayPatterns: { type: Array, required: false, default: () => [{ type: "lines", opacity: 0.1 }] },
15
+ parallax: { type: null, required: false, default: true },
16
+ imagePreset: { type: String, required: false, default: "" },
17
+ as: { type: String, required: false, default: "div" },
18
+ ui: { type: Object, required: false, default: () => ({}) }
19
+ });
20
+ const heroConfig = getHeroConfig(useRuntimeConfig());
21
+ const features = heroConfig.features ?? {};
22
+ const containerRef = useTemplateRef("containerRef");
23
+ const {
24
+ activeIndex,
25
+ snapIndex,
26
+ totalSnaps,
27
+ isActiveSlideVideo,
28
+ next,
29
+ prev,
30
+ goTo,
31
+ onSwiper,
32
+ onSlideChange,
33
+ animationClass,
34
+ registerSlideVideo,
35
+ unregisterSlideVideo,
36
+ activeSlideConfig,
37
+ autoplayEnabled,
38
+ autoplayProgress,
39
+ videoPlaying,
40
+ videoCurrentTime,
41
+ videoDuration,
42
+ videoBuffered,
43
+ videoVolume,
44
+ videoMuted,
45
+ videoWaiting,
46
+ videoEnded,
47
+ videoToggle,
48
+ videoSeek,
49
+ videoScrubStart,
50
+ videoScrubEnd,
51
+ videoSetVolume,
52
+ videoToggleMute,
53
+ mergedSwiperOptions,
54
+ isMultiSlide
55
+ } = props.slider;
56
+ const isVertical = computed(() => mergedSwiperOptions.value.direction === "vertical");
57
+ function isVideo(index) {
58
+ const slide = props.slides[index];
59
+ if (!slide) return false;
60
+ return isVideoUrl(slide.bgSrc) || !!slide.bgDarkSrc && isVideoUrl(slide.bgDarkSrc);
61
+ }
62
+ const shouldShowPagination = computed(
63
+ () => features.pagination && totalSnaps.value > 1 && activeSlideConfig.value.showPagination
64
+ );
65
+ const shouldShowNavigation = computed(
66
+ () => features.navigation && props.slides.length > 1 && activeSlideConfig.value.showNavigation
67
+ );
68
+ const shouldShowVideoScrubber = computed(
69
+ () => features.video && activeSlideConfig.value.showProgress && isActiveSlideVideo.value && videoDuration.value > 0 && !isMultiSlide.value
70
+ );
71
+ const shouldShowAutoplayProgress = computed(
72
+ () => !shouldShowVideoScrubber.value && autoplayEnabled && activeSlideConfig.value.showProgress
73
+ );
74
+ const parallaxConfig = computed(() => resolveParallaxConfig(toValue(props.parallax)));
75
+ if (features.parallax) {
76
+ const { top: elTop, height: elHeight } = useElementBounding(containerRef);
77
+ const { height: winHeight } = useWindowSize();
78
+ watchEffect(() => {
79
+ const cfg = parallaxConfig.value;
80
+ if (!cfg.bg) return;
81
+ const el = containerRef.value;
82
+ if (!el) return;
83
+ const wh = winHeight.value;
84
+ const eh = elHeight.value;
85
+ if (!wh || !eh) return;
86
+ const bgs = el.querySelectorAll(".hero-slide-bg");
87
+ const totalScroll = wh + eh;
88
+ const progress = Math.min(Math.max((wh - elTop.value) / totalScroll, 0), 1);
89
+ const maxTravel = eh * cfg.speed * 0.4;
90
+ const offset = (progress - 0.5) * 2 * maxTravel;
91
+ for (const bg of bgs) {
92
+ bg.style.height = `calc(100% + ${maxTravel * 2}px)`;
93
+ bg.style.top = `${-maxTravel}px`;
94
+ bg.style.transform = `translate3d(0, ${offset}px, 0)`;
95
+ }
96
+ });
97
+ useGSAP(({ gsap }) => {
98
+ const cfg = parallaxConfig.value;
99
+ if (!cfg.content) return;
100
+ const el = containerRef.value;
101
+ if (!el) return;
102
+ gsap.to(el.querySelectorAll(".hero-slide-content"), {
103
+ yPercent: 35 * cfg.speed,
104
+ opacity: cfg.minOpacity,
105
+ ease: "none",
106
+ scrollTrigger: {
107
+ trigger: el,
108
+ start: "top top",
109
+ end: "bottom top",
110
+ scrub: true
111
+ }
112
+ });
113
+ }, { scope: containerRef });
114
+ }
115
+ </script>
116
+
117
+ <template>
118
+ <component :is="as" ref="containerRef" class="hero-slider group/slider relative size-full" :class="ui.root">
119
+ <Swiper v-bind="mergedSwiperOptions" :parallax="true" :modules="swiperModules" class="size-full" :class="ui.swiper"
120
+ @swiper="onSwiper" @slide-change="onSlideChange">
121
+ <SwiperSlide v-for="(slide, index) in slides" :key="index" class="size-full overflow-hidden" :class="ui.slide">
122
+ <HeroSlide :bg-src="slide.bgSrc" :bg-dark-src="slide.bgDarkSrc" :poster="slide.poster"
123
+ :image-preset="imagePreset" :eager="index === 0" :is-active="index === activeIndex || isMultiSlide"
124
+ :animation-class="animationClass(index)" :slide-index="index" :on-video-ready="registerSlideVideo"
125
+ :on-video-removed="unregisterSlideVideo" :media-controls-options="slide.config?.mediaControlsOptions"
126
+ :show-video-controls="index === activeIndex || isMultiSlide ? slide.config?.showVideoControls ?? activeSlideConfig.showVideoControls : false"
127
+ :video-loop="slide.config?.videoLoop ?? false" :auto-play="!isMultiSlide" :container-class="ui.container"
128
+ :bg-class="ui.bg">
129
+ <slot name="slide" v-bind="{
130
+ slide,
131
+ index,
132
+ isActive: index === activeIndex,
133
+ animationClass: animationClass(index),
134
+ isVideo: isVideo(index),
135
+ videoPlaying,
136
+ videoDuration,
137
+ videoCurrentTime,
138
+ videoWaiting,
139
+ videoEnded,
140
+ videoMuted,
141
+ videoVolume,
142
+ videoToggle,
143
+ videoSeek,
144
+ videoSetVolume,
145
+ videoToggleMute
146
+ }" />
147
+ <template #video-controls="videoProps">
148
+ <slot name="video-controls" v-bind="videoProps" />
149
+ </template>
150
+ <template #overlay>
151
+ <slot name="overlay"
152
+ v-bind="{ patterns: overlayPatterns, index, isActive: index === activeIndex, patternCSS, patternSize }">
153
+ <!-- Default overlay rendering -->
154
+ <div v-for="(pattern, i) in overlayPatterns" :key="i" class="pointer-events-none absolute inset-0 z-2"
155
+ :style="{
156
+ backgroundImage: patternCSS(pattern),
157
+ backgroundSize: patternSize(pattern),
158
+ opacity: pattern.opacity ?? 0.15
159
+ }" />
160
+ </slot>
161
+ </template>
162
+ </HeroSlide>
163
+ </SwiperSlide>
164
+ </Swiper>
165
+
166
+ <!-- UI controls layer -->
167
+ <div class="pointer-events-none absolute inset-0 z-50 overflow-hidden" :class="ui.controls">
168
+ <slot v-if="shouldShowPagination" name="pagination"
169
+ v-bind="{ activeIndex, snapIndex, totalSnaps, total: slides.length, progress: autoplayProgress, goTo, vertical: isVertical, autoplayEnabled }">
170
+ <HeroPagination :slides="slides" :active-index="activeIndex" :snap-index="snapIndex" :total-snaps="totalSnaps"
171
+ :progress="autoplayEnabled ? autoplayProgress : 1" :vertical="isVertical" @slide-to="goTo" />
172
+ </slot>
173
+
174
+ <slot v-if="shouldShowNavigation" name="navigation"
175
+ v-bind="{ prev, next, activeIndex, slides, vertical: isVertical }">
176
+ <HeroNavigation :slides="slides" :active-index="activeIndex" :vertical="isVertical" @prev="prev" @next="next" />
177
+ </slot>
178
+
179
+ <!-- Video scrubber (replaces progress bar when active slide is video) -->
180
+ <HeroVideoScrubber v-if="shouldShowVideoScrubber" :model-value="videoCurrentTime" :max="videoDuration"
181
+ class="pointer-events-auto absolute z-11" :class="isVertical ? 'top-0 ltr:right-0 rtl:left-0 h-full w-1' : 'bottom-0 left-0 h-1 hover:h-2 w-full'" @update:model-value="(v) => videoSeek(v)"
182
+ @scrubber-mousedown="videoScrubStart" @scrubber-mouseup="videoScrubEnd">
183
+ <template #default="{ position, pendingValue }">
184
+ <div
185
+ class="text-white mb-2 pointer-events-none px-2 py-1 rounded-sm bg-black/85 backdrop-blur-sm transform bottom-0 absolute z-99 -translate-x-1/2"
186
+ :style="{ left: position }">
187
+ <div
188
+ class="size-2 rotate-45 left-1/2 top-3.5 absolute overflow-hidden after:bg-white after:size-2 after:content-[''] -translate-x-1/2 after:rotate-45 after:left-1/2 after:top-1/2 after:absolute" />
189
+ {{ formatTime(pendingValue) }}
190
+ </div>
191
+ </template>
192
+ </HeroVideoScrubber>
193
+
194
+ <!-- Autoplay progress bar: horizontal at bottom, vertical on the side -->
195
+ <div v-if="shouldShowAutoplayProgress" class="pointer-events-none absolute z-4 bg-white/50" :class="[isVertical ? 'top-0 ltr:right-0 rtl:left-0 h-full w-1' : 'bottom-0 left-0 w-full h-1', ui.progress]">
196
+ <div class="bg-white transition-all duration-50 rounded-e-sm" :class="isVertical ? 'w-full' : 'h-full'" :style="isVertical ? { height: `${autoplayProgress * 100}%` } : { width: `${autoplayProgress * 100}%` }" />
197
+ </div>
198
+ </div>
199
+ </component>
200
+ </template>