@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.
- package/dist/Carousel.svelte +158 -0
- package/dist/CarouselContext.js +1 -0
- package/dist/CarouselItem.svelte +26 -0
- package/dist/index.js +4 -0
- package/dist/types.js +1 -0
- package/package.json +34 -0
|
@@ -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
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
|
+
}
|