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.
- package/LICENSE +21 -0
- package/README.md +212 -0
- package/dist/module.d.mts +19 -0
- package/dist/module.json +12 -0
- package/dist/module.mjs +322 -0
- package/dist/runtime/assets/hero.css +1 -0
- package/dist/runtime/components/navigation/HeroNavigation.d.vue.ts +18 -0
- package/dist/runtime/components/navigation/HeroNavigation.vue +68 -0
- package/dist/runtime/components/navigation/HeroNavigation.vue.d.ts +18 -0
- package/dist/runtime/components/navigation/HeroPagination.d.vue.ts +22 -0
- package/dist/runtime/components/navigation/HeroPagination.vue +50 -0
- package/dist/runtime/components/navigation/HeroPagination.vue.d.ts +22 -0
- package/dist/runtime/components/slider/HeroSlide.d.vue.ts +66 -0
- package/dist/runtime/components/slider/HeroSlide.vue +124 -0
- package/dist/runtime/components/slider/HeroSlide.vue.d.ts +66 -0
- package/dist/runtime/components/slider/index.d.vue.ts +69 -0
- package/dist/runtime/components/slider/index.vue +200 -0
- package/dist/runtime/components/slider/index.vue.d.ts +69 -0
- package/dist/runtime/components/video/HeroSlideVideo.d.vue.ts +39 -0
- package/dist/runtime/components/video/HeroSlideVideo.vue +116 -0
- package/dist/runtime/components/video/HeroSlideVideo.vue.d.ts +39 -0
- package/dist/runtime/components/video/HeroVideoControls.d.vue.ts +12 -0
- package/dist/runtime/components/video/HeroVideoControls.vue +87 -0
- package/dist/runtime/components/video/HeroVideoControls.vue.d.ts +12 -0
- package/dist/runtime/components/video/HeroVideoScrubber.d.vue.ts +64 -0
- package/dist/runtime/components/video/HeroVideoScrubber.vue +50 -0
- package/dist/runtime/components/video/HeroVideoScrubber.vue.d.ts +64 -0
- package/dist/runtime/composables/_autoplay.d.ts +25 -0
- package/dist/runtime/composables/_autoplay.js +72 -0
- package/dist/runtime/composables/_gsap.d.ts +60 -0
- package/dist/runtime/composables/_gsap.js +135 -0
- package/dist/runtime/composables/_hls.d.ts +35 -0
- package/dist/runtime/composables/_hls.js +88 -0
- package/dist/runtime/composables/_slides.d.ts +26 -0
- package/dist/runtime/composables/_slides.js +40 -0
- package/dist/runtime/composables/_swiper.d.ts +23 -0
- package/dist/runtime/composables/_swiper.js +52 -0
- package/dist/runtime/composables/_video.d.ts +30 -0
- package/dist/runtime/composables/_video.js +89 -0
- package/dist/runtime/composables/useHeroSlider.d.ts +24 -0
- package/dist/runtime/composables/useHeroSlider.js +131 -0
- package/dist/runtime/hero-swiper-modules.d.ts +4 -0
- package/dist/runtime/types.d.ts +221 -0
- package/dist/runtime/types.js +0 -0
- package/dist/runtime/utils.d.ts +22 -0
- package/dist/runtime/utils.js +50 -0
- package/dist/types.d.mts +9 -0
- package/package.json +94 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { type Ref, type WatchSource } from 'vue';
|
|
2
|
+
import { type MaybeComputedElementRef } from '@vueuse/core';
|
|
3
|
+
/** Any GSAP plugin accepted by gsap.registerPlugin(). */
|
|
4
|
+
export type GSAPPlugin = object;
|
|
5
|
+
export interface UseGSAPOptions {
|
|
6
|
+
/** Root element or ref to scope GSAP selectors to. */
|
|
7
|
+
scope?: MaybeComputedElementRef;
|
|
8
|
+
/** GSAP plugins to register before the callback runs. e.g. [ScrollTrigger, Draggable] */
|
|
9
|
+
plugins?: GSAPPlugin[];
|
|
10
|
+
/** Respect prefers-reduced-motion. When true, callback is skipped. @default true */
|
|
11
|
+
respectReducedMotion?: boolean;
|
|
12
|
+
/** Reactive sources that trigger re-initialization (reverts then re-runs callback after nextTick). */
|
|
13
|
+
watchSources?: WatchSource[];
|
|
14
|
+
}
|
|
15
|
+
export interface UseGSAPReturn {
|
|
16
|
+
/** The gsap instance with plugins registered. Use instead of importing gsap directly. */
|
|
17
|
+
gsap: typeof import('gsap').gsap;
|
|
18
|
+
/** The GSAP context, undefined before mount / on server. */
|
|
19
|
+
context: Readonly<Ref<gsap.Context | undefined>>;
|
|
20
|
+
/** Wrap a function so animations are tracked by this context. */
|
|
21
|
+
contextSafe: <T extends (...args: any[]) => any>(fn: T) => T;
|
|
22
|
+
/** Create a gsap.timeline() tracked by this context. */
|
|
23
|
+
timeline: (vars?: gsap.TimelineVars) => gsap.core.Timeline;
|
|
24
|
+
/** Revert and re-run the animation callback. */
|
|
25
|
+
refresh: () => void;
|
|
26
|
+
/** Manually kill the context. */
|
|
27
|
+
kill: () => void;
|
|
28
|
+
/** Whether the user prefers reduced motion (reactive). */
|
|
29
|
+
prefersReducedMotion: Readonly<Ref<boolean>>;
|
|
30
|
+
}
|
|
31
|
+
export interface UseGSAPCallbackContext {
|
|
32
|
+
gsap: typeof import('gsap').gsap;
|
|
33
|
+
context: gsap.Context;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Vue composable that wraps `gsap.context()` for safe, scoped GSAP animations.
|
|
37
|
+
*
|
|
38
|
+
* - Creates animations in `onMounted` (DOM is available).
|
|
39
|
+
* - Scopes selectors to a container element via `gsap.context()`.
|
|
40
|
+
* - Automatically reverts all animations and ScrollTriggers on unmount.
|
|
41
|
+
* - Supports `prefers-reduced-motion` — callback is skipped when preferred.
|
|
42
|
+
* - Provides `contextSafe()` for creating animations in event handlers.
|
|
43
|
+
* - Returns a `timeline()` helper that auto-tracks timelines in the context.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```vue
|
|
47
|
+
* <script setup>
|
|
48
|
+
* const container = ref<HTMLElement | null>(null)
|
|
49
|
+
*
|
|
50
|
+
* const { gsap, contextSafe, timeline } = useGSAP((self) => {
|
|
51
|
+
* self.gsap.to('.box', { x: 100, duration: 0.6 })
|
|
52
|
+
* }, { scope: container })
|
|
53
|
+
*
|
|
54
|
+
* const onClick = contextSafe(() => {
|
|
55
|
+
* gsap.to('.box', { rotation: 360 })
|
|
56
|
+
* })
|
|
57
|
+
* </script>
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export declare function useGSAP(callbackOrOptions?: ((self: UseGSAPCallbackContext) => void) | UseGSAPOptions, maybeOptions?: UseGSAPOptions): UseGSAPReturn;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getCurrentInstance,
|
|
3
|
+
nextTick,
|
|
4
|
+
onMounted,
|
|
5
|
+
onUnmounted,
|
|
6
|
+
readonly,
|
|
7
|
+
ref,
|
|
8
|
+
shallowRef,
|
|
9
|
+
watch
|
|
10
|
+
} from "vue";
|
|
11
|
+
import { unrefElement, useMediaQuery } from "@vueuse/core";
|
|
12
|
+
import { gsap } from "gsap";
|
|
13
|
+
import { ScrollTrigger } from "gsap/ScrollTrigger";
|
|
14
|
+
function createSSRNoOp() {
|
|
15
|
+
const noop = (() => {
|
|
16
|
+
});
|
|
17
|
+
return {
|
|
18
|
+
gsap: new Proxy({}, { get: () => noop }),
|
|
19
|
+
context: readonly(shallowRef(void 0)),
|
|
20
|
+
contextSafe: (fn) => fn,
|
|
21
|
+
timeline: () => ({}),
|
|
22
|
+
refresh: noop,
|
|
23
|
+
kill: noop,
|
|
24
|
+
prefersReducedMotion: readonly(ref(false))
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
let _coreReady = false;
|
|
28
|
+
function ensureCorePlugins() {
|
|
29
|
+
if (_coreReady) return;
|
|
30
|
+
_coreReady = true;
|
|
31
|
+
gsap.registerPlugin(ScrollTrigger);
|
|
32
|
+
}
|
|
33
|
+
export function useGSAP(callbackOrOptions, maybeOptions) {
|
|
34
|
+
if (import.meta.env.SSR) {
|
|
35
|
+
return createSSRNoOp();
|
|
36
|
+
}
|
|
37
|
+
ensureCorePlugins();
|
|
38
|
+
let callback;
|
|
39
|
+
let options;
|
|
40
|
+
if (typeof callbackOrOptions === "function") {
|
|
41
|
+
callback = callbackOrOptions;
|
|
42
|
+
options = maybeOptions ?? {};
|
|
43
|
+
} else {
|
|
44
|
+
callback = void 0;
|
|
45
|
+
options = callbackOrOptions ?? {};
|
|
46
|
+
}
|
|
47
|
+
const {
|
|
48
|
+
scope,
|
|
49
|
+
plugins,
|
|
50
|
+
respectReducedMotion = true,
|
|
51
|
+
watchSources
|
|
52
|
+
} = options;
|
|
53
|
+
if (plugins && plugins.length > 0) {
|
|
54
|
+
gsap.registerPlugin(...plugins);
|
|
55
|
+
}
|
|
56
|
+
const context = shallowRef();
|
|
57
|
+
const prefersReducedMotion = useMediaQuery("(prefers-reduced-motion: reduce)");
|
|
58
|
+
let stopWatchSources;
|
|
59
|
+
function revert() {
|
|
60
|
+
context.value?.revert();
|
|
61
|
+
context.value = void 0;
|
|
62
|
+
}
|
|
63
|
+
function init() {
|
|
64
|
+
revert();
|
|
65
|
+
if (respectReducedMotion && prefersReducedMotion.value) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const el = scope ? unrefElement(scope) : void 0;
|
|
69
|
+
if (scope && !el) return;
|
|
70
|
+
context.value = gsap.context((ctx) => {
|
|
71
|
+
callback?.({ gsap, context: ctx });
|
|
72
|
+
}, el ?? void 0);
|
|
73
|
+
}
|
|
74
|
+
function refresh() {
|
|
75
|
+
init();
|
|
76
|
+
}
|
|
77
|
+
function kill() {
|
|
78
|
+
stopWatchSources?.();
|
|
79
|
+
revert();
|
|
80
|
+
}
|
|
81
|
+
function contextSafe(fn) {
|
|
82
|
+
return ((...args) => {
|
|
83
|
+
if (!context.value) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
let result;
|
|
87
|
+
context.value.add(() => {
|
|
88
|
+
result = fn(...args);
|
|
89
|
+
});
|
|
90
|
+
return result;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
function timeline(vars) {
|
|
94
|
+
if (context.value) {
|
|
95
|
+
let tl;
|
|
96
|
+
context.value.add(() => {
|
|
97
|
+
tl = gsap.timeline(vars);
|
|
98
|
+
});
|
|
99
|
+
return tl ?? gsap.timeline(vars);
|
|
100
|
+
}
|
|
101
|
+
return gsap.timeline(vars);
|
|
102
|
+
}
|
|
103
|
+
const instance = getCurrentInstance();
|
|
104
|
+
if (instance) {
|
|
105
|
+
onMounted(() => {
|
|
106
|
+
init();
|
|
107
|
+
});
|
|
108
|
+
onUnmounted(() => {
|
|
109
|
+
revert();
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
if (watchSources && watchSources.length > 0) {
|
|
113
|
+
stopWatchSources = watch(watchSources, () => {
|
|
114
|
+
nextTick(() => init());
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
if (respectReducedMotion) {
|
|
118
|
+
watch(prefersReducedMotion, (reduced) => {
|
|
119
|
+
if (reduced) {
|
|
120
|
+
revert();
|
|
121
|
+
} else {
|
|
122
|
+
init();
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
gsap,
|
|
128
|
+
context: readonly(context),
|
|
129
|
+
contextSafe,
|
|
130
|
+
timeline,
|
|
131
|
+
refresh,
|
|
132
|
+
kill,
|
|
133
|
+
prefersReducedMotion: readonly(prefersReducedMotion)
|
|
134
|
+
};
|
|
135
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type Ref, type MaybeRef } from 'vue';
|
|
2
|
+
export interface UseHlsOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Start loading segments immediately or wait until play.
|
|
5
|
+
* @default true
|
|
6
|
+
*/
|
|
7
|
+
autoLoad?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface UseHlsReturn {
|
|
10
|
+
/** Whether hls.js is loading/attaching */
|
|
11
|
+
loading: Ref<boolean>;
|
|
12
|
+
/** Error message if HLS setup failed */
|
|
13
|
+
error: Ref<string | null>;
|
|
14
|
+
/** Available quality levels (heights) once manifest is parsed */
|
|
15
|
+
qualities: Ref<number[]>;
|
|
16
|
+
/** Set quality: 0 = auto, otherwise the height value */
|
|
17
|
+
setQuality: (height: number) => void;
|
|
18
|
+
/** Destroy the HLS instance manually */
|
|
19
|
+
destroy: () => void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Composable for HLS video playback.
|
|
23
|
+
*
|
|
24
|
+
* Uses `hls.js` when available, falls back to native HLS (Safari).
|
|
25
|
+
* The `<video>` element is still controlled by VueUse's `useMediaControls`.
|
|
26
|
+
*
|
|
27
|
+
* `hls.js` is dynamically imported — only loaded when an `.m3u8` source is used.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* const videoRef = useTemplateRef<HTMLVideoElement>('videoRef')
|
|
32
|
+
* const { loading, error, qualities, setQuality } = useHls(videoRef, () => props.hlsSrc)
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export declare function useHls(videoEl: MaybeRef<HTMLVideoElement | null | undefined>, src: MaybeRef<string>, options?: UseHlsOptions): UseHlsReturn;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { ref, watch, toValue } from "vue";
|
|
2
|
+
import { tryOnScopeDispose } from "@vueuse/core";
|
|
3
|
+
export function useHls(videoEl, src, options = {}) {
|
|
4
|
+
const { autoLoad = true } = options;
|
|
5
|
+
const loading = ref(false);
|
|
6
|
+
const error = ref(null);
|
|
7
|
+
const qualities = ref([]);
|
|
8
|
+
let hls = null;
|
|
9
|
+
let HlsClass = null;
|
|
10
|
+
async function init() {
|
|
11
|
+
const el = toValue(videoEl);
|
|
12
|
+
const url = toValue(src);
|
|
13
|
+
cleanup();
|
|
14
|
+
if (!el || !url) return;
|
|
15
|
+
if (el.canPlayType("application/vnd.apple.mpegurl")) {
|
|
16
|
+
el.src = url;
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
loading.value = true;
|
|
20
|
+
error.value = null;
|
|
21
|
+
try {
|
|
22
|
+
if (!HlsClass) {
|
|
23
|
+
const mod = await import("hls.js");
|
|
24
|
+
HlsClass = mod.default || mod;
|
|
25
|
+
}
|
|
26
|
+
if (!HlsClass.isSupported()) {
|
|
27
|
+
el.src = url;
|
|
28
|
+
loading.value = false;
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
hls = new HlsClass({
|
|
32
|
+
autoStartLoad: autoLoad
|
|
33
|
+
});
|
|
34
|
+
hls.loadSource(url);
|
|
35
|
+
hls.attachMedia(el);
|
|
36
|
+
hls.on(HlsClass.Events.MANIFEST_PARSED, () => {
|
|
37
|
+
if (!hls) return;
|
|
38
|
+
qualities.value = hls.levels.map((l) => l.height);
|
|
39
|
+
loading.value = false;
|
|
40
|
+
});
|
|
41
|
+
hls.on(HlsClass.Events.ERROR, (_event, data) => {
|
|
42
|
+
if (data.fatal) {
|
|
43
|
+
error.value = `HLS error: ${data.type} \u2014 ${data.details}`;
|
|
44
|
+
loading.value = false;
|
|
45
|
+
if (data.type === HlsClass.ErrorTypes.NETWORK_ERROR) {
|
|
46
|
+
hls?.startLoad();
|
|
47
|
+
} else if (data.type === HlsClass.ErrorTypes.MEDIA_ERROR) {
|
|
48
|
+
hls?.recoverMediaError();
|
|
49
|
+
} else {
|
|
50
|
+
cleanup();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
if (!autoLoad) {
|
|
55
|
+
const onPlay = () => {
|
|
56
|
+
el.removeEventListener("play", onPlay);
|
|
57
|
+
hls?.startLoad();
|
|
58
|
+
};
|
|
59
|
+
el.addEventListener("play", onPlay);
|
|
60
|
+
}
|
|
61
|
+
} catch (e) {
|
|
62
|
+
error.value = e instanceof Error ? e.message : "Failed to load hls.js";
|
|
63
|
+
loading.value = false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function cleanup() {
|
|
67
|
+
if (hls) {
|
|
68
|
+
hls.destroy();
|
|
69
|
+
hls = null;
|
|
70
|
+
}
|
|
71
|
+
qualities.value = [];
|
|
72
|
+
}
|
|
73
|
+
function setQuality(height) {
|
|
74
|
+
if (!hls) return;
|
|
75
|
+
if (height === 0) {
|
|
76
|
+
hls.currentLevel = -1;
|
|
77
|
+
} else {
|
|
78
|
+
const idx = hls.levels.findIndex((l) => l.height === height);
|
|
79
|
+
if (idx !== -1) hls.currentLevel = idx;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function destroy() {
|
|
83
|
+
cleanup();
|
|
84
|
+
}
|
|
85
|
+
watch([() => toValue(videoEl), () => toValue(src)], () => init(), { immediate: true });
|
|
86
|
+
tryOnScopeDispose(cleanup);
|
|
87
|
+
return { loading, error, qualities, setQuality, destroy };
|
|
88
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { MaybeRefOrGetter, Ref } from 'vue';
|
|
2
|
+
import type { HeroSlide, ResolvedSlideConfig } from '#hero/types';
|
|
3
|
+
/**
|
|
4
|
+
* Creates reactive slide state — active slide, per-slide config resolution,
|
|
5
|
+
* video detection, and animation class computation.
|
|
6
|
+
*
|
|
7
|
+
* @param slides - Reactive array of slide definitions
|
|
8
|
+
* @param activeIndex - Currently active slide index
|
|
9
|
+
* @param previousIndex - Previously active slide index (for leave animations)
|
|
10
|
+
* @param displayDefaults - Default visibility flags for UI controls
|
|
11
|
+
* @param videoEnabled - Whether video backgrounds are enabled
|
|
12
|
+
* @param enterAnimation - Default enter animation class
|
|
13
|
+
* @param leaveAnimation - Default leave animation class
|
|
14
|
+
* @returns Computed slide state and animation helpers
|
|
15
|
+
*/
|
|
16
|
+
export declare function createSlideState(slides: MaybeRefOrGetter<HeroSlide[]>, activeIndex: Ref<number>, previousIndex: Ref<number>, displayDefaults: {
|
|
17
|
+
showPagination?: boolean;
|
|
18
|
+
showNavigation?: boolean;
|
|
19
|
+
showProgress?: boolean;
|
|
20
|
+
showVideoControls?: boolean;
|
|
21
|
+
}, videoEnabled?: boolean, enterAnimation?: string, leaveAnimation?: string): {
|
|
22
|
+
activeSlide: import("vue").ComputedRef<HeroSlide>;
|
|
23
|
+
isActiveSlideVideo: import("vue").ComputedRef<boolean>;
|
|
24
|
+
activeSlideConfig: import("vue").ComputedRef<ResolvedSlideConfig>;
|
|
25
|
+
animationClass: (index: number) => string;
|
|
26
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { computed, toValue } from "vue";
|
|
2
|
+
import { isVideoUrl } from "#hero/utils";
|
|
3
|
+
export function createSlideState(slides, activeIndex, previousIndex, displayDefaults, videoEnabled = true, enterAnimation = "", leaveAnimation = "") {
|
|
4
|
+
const activeSlide = computed(() => toValue(slides)[activeIndex.value] ?? { bgSrc: "" });
|
|
5
|
+
const isActiveSlideVideo = computed(() => {
|
|
6
|
+
if (!videoEnabled) return false;
|
|
7
|
+
const slide = activeSlide.value;
|
|
8
|
+
return slide ? isVideoUrl(slide.bgSrc) : false;
|
|
9
|
+
});
|
|
10
|
+
const activeSlideConfig = computed(() => {
|
|
11
|
+
const cfg = activeSlide.value?.config ?? {};
|
|
12
|
+
return {
|
|
13
|
+
showPagination: cfg.showPagination ?? displayDefaults.showPagination ?? true,
|
|
14
|
+
showNavigation: cfg.showNavigation ?? displayDefaults.showNavigation ?? true,
|
|
15
|
+
showProgress: cfg.showProgress ?? displayDefaults.showProgress ?? true,
|
|
16
|
+
showVideoControls: cfg.showVideoControls ?? displayDefaults.showVideoControls ?? true,
|
|
17
|
+
videoLoop: cfg.videoLoop ?? false,
|
|
18
|
+
mediaControlsOptions: cfg.mediaControlsOptions
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
function resolveAnimation(index) {
|
|
22
|
+
const slide = toValue(slides)[index];
|
|
23
|
+
return {
|
|
24
|
+
enter: slide?.animation?.enter || enterAnimation || void 0,
|
|
25
|
+
leave: slide?.animation?.leave || leaveAnimation || void 0
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function animationClass(index) {
|
|
29
|
+
const anim = resolveAnimation(index);
|
|
30
|
+
if (index === activeIndex.value && anim.enter) return anim.enter;
|
|
31
|
+
if (index === previousIndex.value && anim.leave) return anim.leave;
|
|
32
|
+
return "";
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
activeSlide,
|
|
36
|
+
isActiveSlideVideo,
|
|
37
|
+
activeSlideConfig,
|
|
38
|
+
animationClass
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { MaybeRefOrGetter } from 'vue';
|
|
2
|
+
import type { Swiper } from 'swiper';
|
|
3
|
+
import type { HeroSlide } from '#hero/types';
|
|
4
|
+
/**
|
|
5
|
+
* Creates the Swiper instance state — tracks active/snap indices,
|
|
6
|
+
* provides navigation methods, and handles slide change events.
|
|
7
|
+
*
|
|
8
|
+
* @param slides - Reactive array of slide definitions (for total count)
|
|
9
|
+
* @returns Swiper state refs and navigation functions
|
|
10
|
+
*/
|
|
11
|
+
export declare function createSwiperState(slides: MaybeRefOrGetter<HeroSlide[]>): {
|
|
12
|
+
swiperInstance: import("vue").ShallowRef<Swiper | undefined, Swiper | undefined>;
|
|
13
|
+
activeIndex: import("vue").Ref<number, number>;
|
|
14
|
+
previousIndex: import("vue").Ref<number, number>;
|
|
15
|
+
snapIndex: import("vue").Ref<number, number>;
|
|
16
|
+
totalSnaps: import("vue").Ref<number, number>;
|
|
17
|
+
onSwiper: (swiper: Swiper) => void;
|
|
18
|
+
onSlideChange: () => void;
|
|
19
|
+
advanceSlide: () => void;
|
|
20
|
+
next: () => void;
|
|
21
|
+
prev: () => void;
|
|
22
|
+
goTo: (index: number) => void;
|
|
23
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { ref, shallowRef, toValue } from "vue";
|
|
2
|
+
export function createSwiperState(slides) {
|
|
3
|
+
const swiperInstance = shallowRef();
|
|
4
|
+
const activeIndex = ref(0);
|
|
5
|
+
const previousIndex = ref(-1);
|
|
6
|
+
const snapIndex = ref(0);
|
|
7
|
+
const totalSnaps = ref(1);
|
|
8
|
+
function updateSnapInfo(swiper) {
|
|
9
|
+
snapIndex.value = swiper.snapIndex ?? 0;
|
|
10
|
+
totalSnaps.value = swiper.snapGrid?.length ?? toValue(slides).length;
|
|
11
|
+
}
|
|
12
|
+
function onSwiper(swiper) {
|
|
13
|
+
swiperInstance.value = swiper;
|
|
14
|
+
updateSnapInfo(swiper);
|
|
15
|
+
}
|
|
16
|
+
function onSlideChange() {
|
|
17
|
+
if (!swiperInstance.value) return;
|
|
18
|
+
previousIndex.value = activeIndex.value;
|
|
19
|
+
activeIndex.value = swiperInstance.value.activeIndex;
|
|
20
|
+
updateSnapInfo(swiperInstance.value);
|
|
21
|
+
}
|
|
22
|
+
function advanceSlide() {
|
|
23
|
+
if (!swiperInstance.value) return;
|
|
24
|
+
if (snapIndex.value + 1 >= totalSnaps.value) swiperInstance.value.slideTo(0);
|
|
25
|
+
else swiperInstance.value.slideNext();
|
|
26
|
+
}
|
|
27
|
+
function next() {
|
|
28
|
+
advanceSlide();
|
|
29
|
+
}
|
|
30
|
+
function prev() {
|
|
31
|
+
if (!swiperInstance.value) return;
|
|
32
|
+
const total = toValue(slides).length;
|
|
33
|
+
if (snapIndex.value <= 0) swiperInstance.value.slideTo(total - 1);
|
|
34
|
+
else swiperInstance.value.slidePrev();
|
|
35
|
+
}
|
|
36
|
+
function goTo(index) {
|
|
37
|
+
swiperInstance.value?.slideTo(index);
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
swiperInstance,
|
|
41
|
+
activeIndex,
|
|
42
|
+
previousIndex,
|
|
43
|
+
snapIndex,
|
|
44
|
+
totalSnaps,
|
|
45
|
+
onSwiper,
|
|
46
|
+
onSlideChange,
|
|
47
|
+
advanceSlide,
|
|
48
|
+
next,
|
|
49
|
+
prev,
|
|
50
|
+
goTo
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Ref } from 'vue';
|
|
2
|
+
import type { VideoMediaControls } from '#hero/types';
|
|
3
|
+
/**
|
|
4
|
+
* Creates reactive video state management for the slider.
|
|
5
|
+
* Maintains a registry of video controls keyed by slide index and
|
|
6
|
+
* proxies the active slide's controls as computed refs with safe defaults.
|
|
7
|
+
*
|
|
8
|
+
* @param activeIndex - Currently active slide index
|
|
9
|
+
* @param videoEnabled - Whether video features are enabled
|
|
10
|
+
* @returns Video state refs and control functions
|
|
11
|
+
*/
|
|
12
|
+
export declare function createVideoState(activeIndex: Ref<number>, videoEnabled: boolean): {
|
|
13
|
+
activeControls: import("vue").ComputedRef<VideoMediaControls | null>;
|
|
14
|
+
videoPlaying: import("vue").ComputedRef<boolean>;
|
|
15
|
+
videoCurrentTime: import("vue").ComputedRef<number>;
|
|
16
|
+
videoDuration: import("vue").ComputedRef<number>;
|
|
17
|
+
videoBuffered: import("vue").ComputedRef<number>;
|
|
18
|
+
videoVolume: import("vue").ComputedRef<number>;
|
|
19
|
+
videoMuted: import("vue").ComputedRef<boolean>;
|
|
20
|
+
videoWaiting: import("vue").ComputedRef<boolean>;
|
|
21
|
+
videoEnded: import("vue").ComputedRef<boolean>;
|
|
22
|
+
videoToggle: () => void;
|
|
23
|
+
videoSeek: (time: number) => void;
|
|
24
|
+
videoScrubStart: () => void;
|
|
25
|
+
videoScrubEnd: () => void;
|
|
26
|
+
videoSetVolume: (v: number) => void;
|
|
27
|
+
videoToggleMute: () => void;
|
|
28
|
+
registerSlideVideo: (index: number, controls: VideoMediaControls) => void;
|
|
29
|
+
unregisterSlideVideo: (index: number) => void;
|
|
30
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { computed, ref } from "vue";
|
|
2
|
+
export function createVideoState(activeIndex, videoEnabled) {
|
|
3
|
+
const controlsMap = videoEnabled ? /* @__PURE__ */ new Map() : null;
|
|
4
|
+
const controlsVersion = ref(0);
|
|
5
|
+
const activeControls = computed(() => {
|
|
6
|
+
if (!controlsMap) return null;
|
|
7
|
+
void controlsVersion.value;
|
|
8
|
+
return controlsMap.get(activeIndex.value) ?? null;
|
|
9
|
+
});
|
|
10
|
+
function registerSlideVideo(index, controls) {
|
|
11
|
+
if (!controlsMap) return;
|
|
12
|
+
controlsMap.set(index, controls);
|
|
13
|
+
controlsVersion.value++;
|
|
14
|
+
}
|
|
15
|
+
function unregisterSlideVideo(index) {
|
|
16
|
+
if (!controlsMap) return;
|
|
17
|
+
controlsMap.delete(index);
|
|
18
|
+
controlsVersion.value++;
|
|
19
|
+
}
|
|
20
|
+
const videoPlaying = computed(() => activeControls.value?.playing.value ?? false);
|
|
21
|
+
const videoCurrentTime = computed(() => activeControls.value?.currentTime.value ?? 0);
|
|
22
|
+
const videoDuration = computed(() => activeControls.value?.duration.value ?? 0);
|
|
23
|
+
const videoVolume = computed(() => activeControls.value?.volume.value ?? 0);
|
|
24
|
+
const videoMuted = computed(() => activeControls.value?.muted.value ?? false);
|
|
25
|
+
const videoWaiting = computed(() => activeControls.value?.waiting.value ?? false);
|
|
26
|
+
const videoEnded = computed(() => activeControls.value?.ended.value ?? false);
|
|
27
|
+
const videoBuffered = computed(() => {
|
|
28
|
+
const controls = activeControls.value;
|
|
29
|
+
if (!controls) return 0;
|
|
30
|
+
const buffered = controls.buffered.value;
|
|
31
|
+
if (!buffered.length) return 0;
|
|
32
|
+
return buffered[buffered.length - 1]?.[1] ?? 0;
|
|
33
|
+
});
|
|
34
|
+
const wasPlayingBeforeScrub = ref(false);
|
|
35
|
+
function videoScrubStart() {
|
|
36
|
+
const c = activeControls.value;
|
|
37
|
+
if (!c) return;
|
|
38
|
+
wasPlayingBeforeScrub.value = c.playing.value;
|
|
39
|
+
if (c.playing.value) {
|
|
40
|
+
c.playing.value = false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function videoScrubEnd() {
|
|
44
|
+
const c = activeControls.value;
|
|
45
|
+
if (!c) return;
|
|
46
|
+
if (wasPlayingBeforeScrub.value) {
|
|
47
|
+
c.playing.value = true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function videoToggle() {
|
|
51
|
+
const c = activeControls.value;
|
|
52
|
+
if (!c) return;
|
|
53
|
+
c.playing.value = !c.playing.value;
|
|
54
|
+
}
|
|
55
|
+
function videoSeek(time) {
|
|
56
|
+
const c = activeControls.value;
|
|
57
|
+
if (!c) return;
|
|
58
|
+
c.currentTime.value = time;
|
|
59
|
+
}
|
|
60
|
+
function videoSetVolume(v) {
|
|
61
|
+
const c = activeControls.value;
|
|
62
|
+
if (!c) return;
|
|
63
|
+
c.volume.value = v;
|
|
64
|
+
}
|
|
65
|
+
function videoToggleMute() {
|
|
66
|
+
const c = activeControls.value;
|
|
67
|
+
if (!c) return;
|
|
68
|
+
c.muted.value = !c.muted.value;
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
activeControls,
|
|
72
|
+
videoPlaying,
|
|
73
|
+
videoCurrentTime,
|
|
74
|
+
videoDuration,
|
|
75
|
+
videoBuffered,
|
|
76
|
+
videoVolume,
|
|
77
|
+
videoMuted,
|
|
78
|
+
videoWaiting,
|
|
79
|
+
videoEnded,
|
|
80
|
+
videoToggle,
|
|
81
|
+
videoSeek,
|
|
82
|
+
videoScrubStart,
|
|
83
|
+
videoScrubEnd,
|
|
84
|
+
videoSetVolume,
|
|
85
|
+
videoToggleMute,
|
|
86
|
+
registerSlideVideo,
|
|
87
|
+
unregisterSlideVideo
|
|
88
|
+
};
|
|
89
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { MaybeRefOrGetter } from 'vue';
|
|
2
|
+
import type { HeroSlide, UseHeroSliderOptions, UseHeroSliderReturn } from '#hero/types';
|
|
3
|
+
/**
|
|
4
|
+
* Main composable for the hero slider. Composes navigation, slides, autoplay,
|
|
5
|
+
* and video state into a single flat API.
|
|
6
|
+
*
|
|
7
|
+
* @param containerRef - Root DOM element ref (or component instance ref) for hover detection and GSAP scoping
|
|
8
|
+
* @param slides - Reactive array of slide definitions
|
|
9
|
+
* @param options - Swiper config, animation classes, and display defaults
|
|
10
|
+
* @returns Flat object with all slider state and controls
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```vue
|
|
14
|
+
* <script setup>
|
|
15
|
+
* const container = ref<HTMLElement | null>(null)
|
|
16
|
+
* const slides = [{ bgSrc: '/hero.jpg', title: 'Welcome' }]
|
|
17
|
+
* const slider = useHeroSlider(container, slides, {
|
|
18
|
+
* swiperOptions: { autoplay: { delay: 3000 } },
|
|
19
|
+
* enterAnimation: 'hero-fadeIn hero-animated',
|
|
20
|
+
* })
|
|
21
|
+
* </script>
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare function useHeroSlider(containerRef: MaybeRefOrGetter<HTMLElement | null>, slides: MaybeRefOrGetter<HeroSlide[]>, options?: UseHeroSliderOptions): UseHeroSliderReturn;
|