@lumen-design/carousel 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,158 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte'
3
+ import type { CarouselDirection, CarouselIndicatorTrigger, CarouselIndicatorPosition } from './types'
4
+ import { setContext, onMount, onDestroy } from 'svelte'
5
+ import { CAROUSEL_CONTEXT_KEY, type CarouselContext } from './CarouselContext'
6
+ import Icon from '@lumen-design/icon'
7
+ import { ChevronLeft, ChevronRight, ChevronUp, ChevronDown } from 'lucide'
8
+
9
+ interface CarouselProps {
10
+ initialIndex?: number
11
+ height?: string
12
+ autoplay?: boolean
13
+ interval?: number
14
+ loop?: boolean
15
+ direction?: CarouselDirection
16
+ indicatorTrigger?: CarouselIndicatorTrigger
17
+ indicatorPosition?: CarouselIndicatorPosition
18
+ arrow?: 'always' | 'hover' | 'never'
19
+ itemCount?: number
20
+ children?: Snippet
21
+ class?: string
22
+ }
23
+
24
+ let {
25
+ initialIndex = 0,
26
+ height = '300px',
27
+ autoplay = true,
28
+ interval = 3000,
29
+ loop = true,
30
+ direction = 'horizontal',
31
+ indicatorTrigger = 'click',
32
+ indicatorPosition,
33
+ arrow = 'hover',
34
+ itemCount = 0,
35
+ children,
36
+ class: cls = '',
37
+ ...attrs
38
+ }: CarouselProps = $props()
39
+
40
+ let activeIndex = $state(0)
41
+ let timer: ReturnType<typeof setInterval> | null = null
42
+ let isHovering = $state(false)
43
+
44
+ // Initialize activeIndex from prop
45
+ $effect(() => {
46
+ if (initialIndex >= 0 && initialIndex < itemCount) {
47
+ activeIndex = initialIndex
48
+ }
49
+ })
50
+
51
+ const context: CarouselContext = {
52
+ get activeIndex() {
53
+ return activeIndex
54
+ },
55
+ get direction() {
56
+ return direction
57
+ },
58
+ registerItem: () => 0,
59
+ }
60
+
61
+ setContext(CAROUSEL_CONTEXT_KEY, context)
62
+
63
+ const prev = (): void => {
64
+ if (itemCount === 0) return
65
+ if (activeIndex > 0) {
66
+ activeIndex--
67
+ } else if (loop) {
68
+ activeIndex = itemCount - 1
69
+ }
70
+ }
71
+
72
+ const next = (): void => {
73
+ if (itemCount === 0) return
74
+ if (activeIndex < itemCount - 1) {
75
+ activeIndex++
76
+ } else if (loop) {
77
+ activeIndex = 0
78
+ }
79
+ }
80
+
81
+ const goTo = (index: number): void => {
82
+ activeIndex = index
83
+ }
84
+
85
+ const handleIndicator = (index: number, trigger: 'hover' | 'click'): void => {
86
+ if (indicatorTrigger === trigger) {
87
+ goTo(index)
88
+ }
89
+ }
90
+
91
+ const startAutoplay = (): void => {
92
+ if (autoplay && !timer && itemCount > 1) {
93
+ timer = setInterval(next, interval)
94
+ }
95
+ }
96
+
97
+ const stopAutoplay = (): void => {
98
+ if (timer) {
99
+ clearInterval(timer)
100
+ timer = null
101
+ }
102
+ }
103
+
104
+ const handleMouseEnter = (): void => {
105
+ isHovering = true
106
+ stopAutoplay()
107
+ }
108
+
109
+ const handleMouseLeave = (): void => {
110
+ isHovering = false
111
+ startAutoplay()
112
+ }
113
+
114
+ onMount(() => {
115
+ startAutoplay()
116
+ })
117
+
118
+ onDestroy(() => {
119
+ stopAutoplay()
120
+ })
121
+
122
+ const showArrow = $derived(arrow === 'always' || (arrow === 'hover' && isHovering))
123
+ const classes = $derived(`lm-carousel lm-carousel--${direction}${indicatorPosition === 'outside' ? ' lm-carousel--indicator-outside' : ''}${cls ? ` ${cls}` : ''}`)
124
+
125
+ /** 指示器索引数组(缓存避免重复创建) */
126
+ const indicatorIndices = $derived(Array.from({ length: itemCount }, (_, i) => i))
127
+ </script>
128
+
129
+ <div class={classes} style="height:{height}" onmouseenter={handleMouseEnter} onmouseleave={handleMouseLeave} {...attrs}>
130
+ <div class="lm-carousel__container" style="transform:{direction === 'horizontal' ? `translateX(-${activeIndex * 100}%)` : `translateY(-${activeIndex * 100}%)`}">
131
+ {#if children}
132
+ {@render children()}
133
+ {/if}
134
+ </div>
135
+
136
+ {#if showArrow && itemCount > 1}
137
+ <button type="button" class="lm-carousel__arrow lm-carousel__arrow--prev" onclick={prev} aria-label="Previous slide">
138
+ <Icon icon={direction === 'horizontal' ? ChevronLeft : ChevronUp} size={18} />
139
+ </button>
140
+ <button type="button" class="lm-carousel__arrow lm-carousel__arrow--next" onclick={next} aria-label="Next slide">
141
+ <Icon icon={direction === 'horizontal' ? ChevronRight : ChevronDown} size={18} />
142
+ </button>
143
+ {/if}
144
+
145
+ {#if indicatorPosition !== 'none' && itemCount > 1}
146
+ <div class="lm-carousel__indicators">
147
+ {#each indicatorIndices as i (i)}
148
+ <button
149
+ type="button"
150
+ class="lm-carousel__indicator{activeIndex === i ? ' is-active' : ''}"
151
+ onclick={() => handleIndicator(i, 'click')}
152
+ onmouseenter={() => handleIndicator(i, 'hover')}
153
+ aria-label="Go to slide {i + 1}"
154
+ ></button>
155
+ {/each}
156
+ </div>
157
+ {/if}
158
+ </div>
@@ -0,0 +1 @@
1
+ export const CAROUSEL_CONTEXT_KEY = Symbol('carousel');
@@ -0,0 +1,26 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte'
3
+ import { getContext } from 'svelte'
4
+ import { CAROUSEL_CONTEXT_KEY, type CarouselContext } from './CarouselContext'
5
+
6
+ interface CarouselItemProps {
7
+ name?: string
8
+ label?: string
9
+ index?: number
10
+ children?: Snippet
11
+ class?: string
12
+ }
13
+
14
+ let { name = '', label = '', index = 0, children, class: cls = '', ...attrs }: CarouselItemProps = $props()
15
+
16
+ const carousel = getContext<CarouselContext>(CAROUSEL_CONTEXT_KEY)
17
+
18
+ const isActive = $derived(index === carousel?.activeIndex)
19
+ const classes = $derived(`lm-carousel__item${isActive ? ' is-active' : ''}${cls ? ` ${cls}` : ''}`)
20
+ </script>
21
+
22
+ <div class={classes} {...attrs}>
23
+ {#if children}
24
+ {@render children()}
25
+ {/if}
26
+ </div>
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ import Carousel from './Carousel.svelte';
2
+ import CarouselItem from './CarouselItem.svelte';
3
+ export { Carousel, CarouselItem };
4
+ export default Carousel;
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@lumen-design/carousel",
3
+ "version": "0.0.2",
4
+ "description": "Carousel component for Lumen UI",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "svelte": "./dist/index.js",
13
+ "import": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "build": "svelte-package -i src -o dist --types",
21
+ "build:watch": "svelte-package -i src -o dist --types -w"
22
+ },
23
+ "dependencies": {
24
+ "@lumen-design/icon": "0.0.2",
25
+ "lucide": "^0.563.0"
26
+ },
27
+ "devDependencies": {
28
+ "@sveltejs/package": "^2.5.7",
29
+ "svelte": "5.48.2"
30
+ },
31
+ "peerDependencies": {
32
+ "svelte": "^5.0.0"
33
+ }
34
+ }