@marianmeres/stuic 3.1.0 → 3.2.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/Carousel/Carousel.svelte +344 -0
- package/dist/components/Carousel/Carousel.svelte.d.ts +73 -0
- package/dist/components/Carousel/README.md +134 -0
- package/dist/components/Carousel/index.css +206 -0
- package/dist/components/Carousel/index.d.ts +1 -0
- package/dist/components/Carousel/index.js +1 -0
- package/dist/components/Input/FieldInputLocalized.svelte +10 -33
- package/dist/components/Input/index.css +45 -1
- package/dist/index.css +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +5 -5
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { ItemCollection as ItemCollectionBase } from "@marianmeres/item-collection";
|
|
3
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
4
|
+
import type { Snippet } from "svelte";
|
|
5
|
+
import type { THC } from "../Thc/index.js";
|
|
6
|
+
|
|
7
|
+
export interface CarouselItem {
|
|
8
|
+
id: string | number;
|
|
9
|
+
content: THC;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
data?: Record<string, any>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface ItemCollectionItem extends CarouselItem {}
|
|
15
|
+
export interface ItemColl extends ItemCollectionBase<ItemCollectionItem> {}
|
|
16
|
+
|
|
17
|
+
export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
|
|
18
|
+
/** Array of carousel items */
|
|
19
|
+
items: CarouselItem[];
|
|
20
|
+
|
|
21
|
+
/** Number of items visible per view (default: 1) */
|
|
22
|
+
itemsPerView?: number;
|
|
23
|
+
|
|
24
|
+
/** Percentage of next item to show as peek hint (0-50) */
|
|
25
|
+
peekPercent?: number;
|
|
26
|
+
|
|
27
|
+
/** Gap between items in pixels or CSS value */
|
|
28
|
+
gap?: number | string;
|
|
29
|
+
|
|
30
|
+
/** Enable/disable active item tracking */
|
|
31
|
+
trackActive?: boolean;
|
|
32
|
+
|
|
33
|
+
/** Sync active item based on scroll position (requires trackActive) */
|
|
34
|
+
syncActiveOnScroll?: boolean;
|
|
35
|
+
|
|
36
|
+
/** Currently active item index (bindable) */
|
|
37
|
+
activeIndex?: number;
|
|
38
|
+
|
|
39
|
+
/** Currently active item value/id (bindable) */
|
|
40
|
+
value?: string | number;
|
|
41
|
+
|
|
42
|
+
/** Enable scroll snap behavior (default: true) */
|
|
43
|
+
snap?: boolean;
|
|
44
|
+
|
|
45
|
+
/** Snap alignment: start, center, end (default: start) */
|
|
46
|
+
snapAlign?: "start" | "center" | "end";
|
|
47
|
+
|
|
48
|
+
/** Enable keyboard navigation (default: true) */
|
|
49
|
+
keyboard?: boolean;
|
|
50
|
+
|
|
51
|
+
/** Allow cycling from last to first and vice versa */
|
|
52
|
+
loop?: boolean;
|
|
53
|
+
|
|
54
|
+
/** Scroll behavior for programmatic navigation (default: smooth) */
|
|
55
|
+
scrollBehavior?: ScrollBehavior;
|
|
56
|
+
|
|
57
|
+
/** Show scrollbar on hover (default: true). Set to false when using navigation buttons */
|
|
58
|
+
scrollbar?: boolean;
|
|
59
|
+
|
|
60
|
+
/** Custom class for container */
|
|
61
|
+
class?: string;
|
|
62
|
+
|
|
63
|
+
/** Custom class for the scrollable track */
|
|
64
|
+
classTrack?: string;
|
|
65
|
+
|
|
66
|
+
/** Custom class for each item wrapper */
|
|
67
|
+
classItem?: string;
|
|
68
|
+
|
|
69
|
+
/** Custom class for active item */
|
|
70
|
+
classItemActive?: string;
|
|
71
|
+
|
|
72
|
+
/** Skip all default styling */
|
|
73
|
+
unstyled?: boolean;
|
|
74
|
+
|
|
75
|
+
/** Bindable element reference */
|
|
76
|
+
el?: HTMLDivElement;
|
|
77
|
+
|
|
78
|
+
/** Callback when active item changes */
|
|
79
|
+
onActiveChange?: (item: CarouselItem, index: number) => void;
|
|
80
|
+
|
|
81
|
+
/** Custom render snippet for items (alternative to THC) */
|
|
82
|
+
renderItem?: Snippet<[{ item: CarouselItem; index: number; active: boolean }]>;
|
|
83
|
+
}
|
|
84
|
+
</script>
|
|
85
|
+
|
|
86
|
+
<script lang="ts">
|
|
87
|
+
import { ItemCollection } from "@marianmeres/item-collection";
|
|
88
|
+
import { twMerge } from "../../utils/tw-merge.js";
|
|
89
|
+
import Thc from "../Thc/Thc.svelte";
|
|
90
|
+
|
|
91
|
+
let {
|
|
92
|
+
items,
|
|
93
|
+
itemsPerView = 1,
|
|
94
|
+
peekPercent = 0,
|
|
95
|
+
gap,
|
|
96
|
+
trackActive = false,
|
|
97
|
+
syncActiveOnScroll = false,
|
|
98
|
+
activeIndex = $bindable(0),
|
|
99
|
+
value = $bindable(),
|
|
100
|
+
snap = true,
|
|
101
|
+
snapAlign = "start",
|
|
102
|
+
keyboard = true,
|
|
103
|
+
loop = false,
|
|
104
|
+
scrollBehavior = "smooth",
|
|
105
|
+
scrollbar = true,
|
|
106
|
+
class: classProp,
|
|
107
|
+
classTrack,
|
|
108
|
+
classItem,
|
|
109
|
+
classItemActive,
|
|
110
|
+
unstyled = false,
|
|
111
|
+
el = $bindable(),
|
|
112
|
+
onActiveChange,
|
|
113
|
+
renderItem,
|
|
114
|
+
...rest
|
|
115
|
+
}: Props = $props();
|
|
116
|
+
|
|
117
|
+
// Internal refs
|
|
118
|
+
let trackEl: HTMLDivElement | undefined = $state();
|
|
119
|
+
let itemEls: Record<string | number, HTMLDivElement> = $state({});
|
|
120
|
+
|
|
121
|
+
// ItemCollection for managing items and active state
|
|
122
|
+
const coll: ItemColl = $derived.by(() => {
|
|
123
|
+
const out = new ItemCollection(
|
|
124
|
+
items.map((item) => ({ ...item })),
|
|
125
|
+
{
|
|
126
|
+
idPropName: "id",
|
|
127
|
+
allowNextPrevCycle: loop,
|
|
128
|
+
}
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
// Set initial active based on value or activeIndex
|
|
132
|
+
if (value !== undefined) {
|
|
133
|
+
const idx = out.items.findIndex((item) => item.id === value);
|
|
134
|
+
if (idx > -1) out.setActiveIndex(idx);
|
|
135
|
+
} else if (activeIndex !== undefined && activeIndex >= 0) {
|
|
136
|
+
out.setActiveIndex(activeIndex);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return out;
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Sync collection changes back to bindable props
|
|
143
|
+
$effect(() => {
|
|
144
|
+
return coll.subscribe((c) => {
|
|
145
|
+
if (trackActive) {
|
|
146
|
+
value = c.active?.id;
|
|
147
|
+
activeIndex = c.activeIndex ?? 0;
|
|
148
|
+
if (c.active && c.activeIndex !== undefined) {
|
|
149
|
+
onActiveChange?.(c.active, c.activeIndex);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Flag to prevent scroll loops (when programmatic scroll triggers observer)
|
|
156
|
+
let isScrollingProgrammatically = false;
|
|
157
|
+
|
|
158
|
+
// Track active item based on scroll position using IntersectionObserver
|
|
159
|
+
$effect(() => {
|
|
160
|
+
if (!trackActive || !syncActiveOnScroll || !trackEl) return;
|
|
161
|
+
|
|
162
|
+
const observer = new IntersectionObserver(
|
|
163
|
+
(entries) => {
|
|
164
|
+
if (isScrollingProgrammatically) return;
|
|
165
|
+
|
|
166
|
+
// Find the entry with highest intersection ratio
|
|
167
|
+
let mostVisible: { id: string | number; ratio: number } | null = null;
|
|
168
|
+
|
|
169
|
+
for (const entry of entries) {
|
|
170
|
+
if (entry.isIntersecting) {
|
|
171
|
+
const id = entry.target.getAttribute("data-id");
|
|
172
|
+
if (id && (!mostVisible || entry.intersectionRatio > mostVisible.ratio)) {
|
|
173
|
+
// Parse id back to number if it was originally a number
|
|
174
|
+
const parsedId = coll.items.find((i) => String(i.id) === id)?.id;
|
|
175
|
+
if (parsedId !== undefined) {
|
|
176
|
+
mostVisible = { id: parsedId, ratio: entry.intersectionRatio };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (mostVisible && mostVisible.id !== coll.active?.id) {
|
|
183
|
+
const item = coll.items.find((i) => i.id === mostVisible!.id);
|
|
184
|
+
if (item) {
|
|
185
|
+
coll.setActive(item);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
root: trackEl,
|
|
191
|
+
threshold: [0.5, 0.75, 1.0],
|
|
192
|
+
}
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
// Observe all item elements
|
|
196
|
+
for (const id in itemEls) {
|
|
197
|
+
if (itemEls[id]) {
|
|
198
|
+
observer.observe(itemEls[id]);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return () => observer.disconnect();
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// Scroll active item into view when active changes programmatically
|
|
206
|
+
function scrollActiveIntoView() {
|
|
207
|
+
// Set flag to prevent IntersectionObserver from re-triggering
|
|
208
|
+
isScrollingProgrammatically = true;
|
|
209
|
+
|
|
210
|
+
// Use setTimeout to allow the ItemCollection state to update first
|
|
211
|
+
setTimeout(() => {
|
|
212
|
+
const activeItem = coll.active;
|
|
213
|
+
if (activeItem && itemEls[activeItem.id]) {
|
|
214
|
+
itemEls[activeItem.id]?.scrollIntoView({
|
|
215
|
+
behavior: scrollBehavior,
|
|
216
|
+
block: "nearest",
|
|
217
|
+
inline: snapAlign === "center" ? "center" : snapAlign === "end" ? "end" : "start",
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Reset flag after scroll animation completes
|
|
222
|
+
setTimeout(() => {
|
|
223
|
+
isScrollingProgrammatically = false;
|
|
224
|
+
}, scrollBehavior === "instant" ? 0 : 300);
|
|
225
|
+
}, 0);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Keyboard navigation handler
|
|
229
|
+
function handleKeydown(e: KeyboardEvent) {
|
|
230
|
+
if (!keyboard) return;
|
|
231
|
+
|
|
232
|
+
if (e.key === "ArrowRight" || e.key === "ArrowDown") {
|
|
233
|
+
e.preventDefault();
|
|
234
|
+
coll.setActiveNext();
|
|
235
|
+
scrollActiveIntoView();
|
|
236
|
+
} else if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
|
|
237
|
+
e.preventDefault();
|
|
238
|
+
coll.setActivePrevious();
|
|
239
|
+
scrollActiveIntoView();
|
|
240
|
+
} else if (e.key === "Home") {
|
|
241
|
+
e.preventDefault();
|
|
242
|
+
coll.setActiveFirst();
|
|
243
|
+
scrollActiveIntoView();
|
|
244
|
+
} else if (e.key === "End") {
|
|
245
|
+
e.preventDefault();
|
|
246
|
+
coll.setActiveLast();
|
|
247
|
+
scrollActiveIntoView();
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Public API methods
|
|
252
|
+
export function goTo(index: number) {
|
|
253
|
+
coll.setActiveIndex(index);
|
|
254
|
+
scrollActiveIntoView();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function goToId(id: string | number) {
|
|
258
|
+
const item = coll.items.find((i) => i.id === id);
|
|
259
|
+
if (item) {
|
|
260
|
+
coll.setActive(item);
|
|
261
|
+
scrollActiveIntoView();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export function next() {
|
|
266
|
+
coll.setActiveNext();
|
|
267
|
+
scrollActiveIntoView();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function previous() {
|
|
271
|
+
coll.setActivePrevious();
|
|
272
|
+
scrollActiveIntoView();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function getCollection() {
|
|
276
|
+
return coll;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Compute inline styles for gap and peek
|
|
280
|
+
let trackStyle = $derived.by(() => {
|
|
281
|
+
const styles: string[] = [];
|
|
282
|
+
if (gap !== undefined) {
|
|
283
|
+
styles.push(`--stuic-carousel-gap: ${typeof gap === "number" ? `${gap}px` : gap}`);
|
|
284
|
+
}
|
|
285
|
+
if (peekPercent > 0) {
|
|
286
|
+
styles.push(`--stuic-carousel-peek-percent: ${peekPercent}%`);
|
|
287
|
+
}
|
|
288
|
+
return styles.join("; ");
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// Helper to check if item is active
|
|
292
|
+
function isItemActive(index: number): boolean {
|
|
293
|
+
return trackActive && coll.activeIndex === index;
|
|
294
|
+
}
|
|
295
|
+
</script>
|
|
296
|
+
|
|
297
|
+
{#if coll.size}
|
|
298
|
+
<div
|
|
299
|
+
bind:this={el}
|
|
300
|
+
class={twMerge(!unstyled && "stuic-carousel", classProp)}
|
|
301
|
+
data-items-per-view={!unstyled ? itemsPerView : undefined}
|
|
302
|
+
{...rest}
|
|
303
|
+
>
|
|
304
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
305
|
+
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
306
|
+
<div
|
|
307
|
+
bind:this={trackEl}
|
|
308
|
+
class={twMerge(!unstyled && "stuic-carousel-track", classTrack)}
|
|
309
|
+
style={trackStyle || undefined}
|
|
310
|
+
tabindex={keyboard ? 0 : undefined}
|
|
311
|
+
role="region"
|
|
312
|
+
aria-label="Carousel"
|
|
313
|
+
aria-roledescription="carousel"
|
|
314
|
+
data-snap={!unstyled && snap ? "true" : undefined}
|
|
315
|
+
data-snap-align={!unstyled && snap ? snapAlign : undefined}
|
|
316
|
+
data-scrollbar={!unstyled && scrollbar ? "true" : undefined}
|
|
317
|
+
onkeydown={handleKeydown}
|
|
318
|
+
>
|
|
319
|
+
{#each coll.items as item, i (item.id)}
|
|
320
|
+
{@const active = isItemActive(i)}
|
|
321
|
+
<div
|
|
322
|
+
bind:this={itemEls[item.id]}
|
|
323
|
+
data-id={item.id}
|
|
324
|
+
class={twMerge(
|
|
325
|
+
!unstyled && "stuic-carousel-item",
|
|
326
|
+
classItem,
|
|
327
|
+
active && classItemActive
|
|
328
|
+
)}
|
|
329
|
+
role="group"
|
|
330
|
+
aria-roledescription="slide"
|
|
331
|
+
aria-label={`Slide ${i + 1} of ${coll.size}`}
|
|
332
|
+
data-active={active ? "true" : undefined}
|
|
333
|
+
data-disabled={item.disabled ? "true" : undefined}
|
|
334
|
+
>
|
|
335
|
+
{#if renderItem}
|
|
336
|
+
{@render renderItem({ item, index: i, active })}
|
|
337
|
+
{:else}
|
|
338
|
+
<Thc thc={item.content} />
|
|
339
|
+
{/if}
|
|
340
|
+
</div>
|
|
341
|
+
{/each}
|
|
342
|
+
</div>
|
|
343
|
+
</div>
|
|
344
|
+
{/if}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { ItemCollection as ItemCollectionBase } from "@marianmeres/item-collection";
|
|
2
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
3
|
+
import type { Snippet } from "svelte";
|
|
4
|
+
import type { THC } from "../Thc/index.js";
|
|
5
|
+
export interface CarouselItem {
|
|
6
|
+
id: string | number;
|
|
7
|
+
content: THC;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
data?: Record<string, any>;
|
|
10
|
+
}
|
|
11
|
+
interface ItemCollectionItem extends CarouselItem {
|
|
12
|
+
}
|
|
13
|
+
export interface ItemColl extends ItemCollectionBase<ItemCollectionItem> {
|
|
14
|
+
}
|
|
15
|
+
export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
|
|
16
|
+
/** Array of carousel items */
|
|
17
|
+
items: CarouselItem[];
|
|
18
|
+
/** Number of items visible per view (default: 1) */
|
|
19
|
+
itemsPerView?: number;
|
|
20
|
+
/** Percentage of next item to show as peek hint (0-50) */
|
|
21
|
+
peekPercent?: number;
|
|
22
|
+
/** Gap between items in pixels or CSS value */
|
|
23
|
+
gap?: number | string;
|
|
24
|
+
/** Enable/disable active item tracking */
|
|
25
|
+
trackActive?: boolean;
|
|
26
|
+
/** Sync active item based on scroll position (requires trackActive) */
|
|
27
|
+
syncActiveOnScroll?: boolean;
|
|
28
|
+
/** Currently active item index (bindable) */
|
|
29
|
+
activeIndex?: number;
|
|
30
|
+
/** Currently active item value/id (bindable) */
|
|
31
|
+
value?: string | number;
|
|
32
|
+
/** Enable scroll snap behavior (default: true) */
|
|
33
|
+
snap?: boolean;
|
|
34
|
+
/** Snap alignment: start, center, end (default: start) */
|
|
35
|
+
snapAlign?: "start" | "center" | "end";
|
|
36
|
+
/** Enable keyboard navigation (default: true) */
|
|
37
|
+
keyboard?: boolean;
|
|
38
|
+
/** Allow cycling from last to first and vice versa */
|
|
39
|
+
loop?: boolean;
|
|
40
|
+
/** Scroll behavior for programmatic navigation (default: smooth) */
|
|
41
|
+
scrollBehavior?: ScrollBehavior;
|
|
42
|
+
/** Show scrollbar on hover (default: true). Set to false when using navigation buttons */
|
|
43
|
+
scrollbar?: boolean;
|
|
44
|
+
/** Custom class for container */
|
|
45
|
+
class?: string;
|
|
46
|
+
/** Custom class for the scrollable track */
|
|
47
|
+
classTrack?: string;
|
|
48
|
+
/** Custom class for each item wrapper */
|
|
49
|
+
classItem?: string;
|
|
50
|
+
/** Custom class for active item */
|
|
51
|
+
classItemActive?: string;
|
|
52
|
+
/** Skip all default styling */
|
|
53
|
+
unstyled?: boolean;
|
|
54
|
+
/** Bindable element reference */
|
|
55
|
+
el?: HTMLDivElement;
|
|
56
|
+
/** Callback when active item changes */
|
|
57
|
+
onActiveChange?: (item: CarouselItem, index: number) => void;
|
|
58
|
+
/** Custom render snippet for items (alternative to THC) */
|
|
59
|
+
renderItem?: Snippet<[{
|
|
60
|
+
item: CarouselItem;
|
|
61
|
+
index: number;
|
|
62
|
+
active: boolean;
|
|
63
|
+
}]>;
|
|
64
|
+
}
|
|
65
|
+
declare const Carousel: import("svelte").Component<Props, {
|
|
66
|
+
goTo: (index: number) => void;
|
|
67
|
+
goToId: (id: string | number) => void;
|
|
68
|
+
next: () => void;
|
|
69
|
+
previous: () => void;
|
|
70
|
+
getCollection: () => ItemColl;
|
|
71
|
+
}, "value" | "el" | "activeIndex">;
|
|
72
|
+
type Carousel = ReturnType<typeof Carousel>;
|
|
73
|
+
export default Carousel;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Carousel
|
|
2
|
+
|
|
3
|
+
A horizontally scrollable carousel component with optional active item tracking,
|
|
4
|
+
keyboard navigation, snap scrolling, and flexible content rendering via THC.
|
|
5
|
+
|
|
6
|
+
## Props
|
|
7
|
+
|
|
8
|
+
| Prop | Type | Default | Description |
|
|
9
|
+
| ---------------- | -------------------------------------------------------------- | ---------- | ----------------------------------------------- |
|
|
10
|
+
| `items` | `CarouselItem[]` | required | Array of carousel items |
|
|
11
|
+
| `itemsPerView` | `number` | `1` | Number of items visible per view |
|
|
12
|
+
| `peekPercent` | `number` | `0` | Percentage of next item to show (0-50) |
|
|
13
|
+
| `gap` | `number \| string` | - | Gap between items |
|
|
14
|
+
| `trackActive` | `boolean` | `false` | Enable active item tracking |
|
|
15
|
+
| `activeIndex` | `number` | `0` | Active item index (bindable) |
|
|
16
|
+
| `value` | `string \| number` | - | Active item id (bindable) |
|
|
17
|
+
| `snap` | `boolean` | `true` | Enable scroll snap |
|
|
18
|
+
| `snapAlign` | `"start" \| "center" \| "end"` | `"start"` | Snap alignment |
|
|
19
|
+
| `keyboard` | `boolean` | `true` | Enable keyboard navigation |
|
|
20
|
+
| `loop` | `boolean` | `false` | Loop navigation |
|
|
21
|
+
| `scrollBehavior` | `ScrollBehavior` | `"smooth"` | Scroll behavior |
|
|
22
|
+
| `class` | `string` | - | Custom class for container |
|
|
23
|
+
| `classTrack` | `string` | - | Custom class for scroll track |
|
|
24
|
+
| `classItem` | `string` | - | Custom class for items |
|
|
25
|
+
| `classItemActive`| `string` | - | Custom class for active item |
|
|
26
|
+
| `unstyled` | `boolean` | `false` | Skip default styling |
|
|
27
|
+
| `el` | `HTMLDivElement` | - | Element reference (bindable) |
|
|
28
|
+
| `onActiveChange` | `(item: CarouselItem, index: number) => void` | - | Callback when active changes |
|
|
29
|
+
| `renderItem` | `Snippet<[{ item: CarouselItem; index: number; active: boolean }]>` | - | Custom item render snippet |
|
|
30
|
+
|
|
31
|
+
## CarouselItem Interface
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
interface CarouselItem {
|
|
35
|
+
id: string | number;
|
|
36
|
+
content: THC; // Text, HTML, Component, or Snippet
|
|
37
|
+
disabled?: boolean;
|
|
38
|
+
data?: Record<string, any>; // Custom data for renderItem
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Methods
|
|
43
|
+
|
|
44
|
+
| Method | Description |
|
|
45
|
+
| ------------ | ------------------------------ |
|
|
46
|
+
| `goTo(index)`| Navigate to item at index |
|
|
47
|
+
| `goToId(id)` | Navigate to item with id |
|
|
48
|
+
| `next()` | Navigate to next item |
|
|
49
|
+
| `previous()` | Navigate to previous item |
|
|
50
|
+
|
|
51
|
+
## Usage
|
|
52
|
+
|
|
53
|
+
### Basic
|
|
54
|
+
|
|
55
|
+
```svelte
|
|
56
|
+
<Carousel
|
|
57
|
+
items={[
|
|
58
|
+
{ id: 1, content: "Slide 1" },
|
|
59
|
+
{ id: 2, content: "Slide 2" },
|
|
60
|
+
{ id: 3, content: "Slide 3" },
|
|
61
|
+
]}
|
|
62
|
+
/>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Multiple Items Per View with Peek
|
|
66
|
+
|
|
67
|
+
```svelte
|
|
68
|
+
<Carousel items={items} itemsPerView={3} peekPercent={10} gap={16} />
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### With Active Tracking
|
|
72
|
+
|
|
73
|
+
```svelte
|
|
74
|
+
<script>
|
|
75
|
+
let activeIndex = $state(0);
|
|
76
|
+
</script>
|
|
77
|
+
|
|
78
|
+
<Carousel
|
|
79
|
+
items={items}
|
|
80
|
+
trackActive
|
|
81
|
+
bind:activeIndex
|
|
82
|
+
onActiveChange={(item, i) => console.log('Active:', item)}
|
|
83
|
+
/>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Custom Rendering
|
|
87
|
+
|
|
88
|
+
```svelte
|
|
89
|
+
<Carousel items={items}>
|
|
90
|
+
{#snippet renderItem({ item, index, active })}
|
|
91
|
+
<div class="card" class:active>
|
|
92
|
+
<h3>{item.content}</h3>
|
|
93
|
+
<p>{item.data?.description}</p>
|
|
94
|
+
</div>
|
|
95
|
+
{/snippet}
|
|
96
|
+
</Carousel>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Programmatic Navigation
|
|
100
|
+
|
|
101
|
+
```svelte
|
|
102
|
+
<script>
|
|
103
|
+
let carousel: Carousel;
|
|
104
|
+
</script>
|
|
105
|
+
|
|
106
|
+
<Carousel bind:this={carousel} items={items} trackActive />
|
|
107
|
+
|
|
108
|
+
<button onclick={() => carousel.previous()}>Previous</button>
|
|
109
|
+
<button onclick={() => carousel.next()}>Next</button>
|
|
110
|
+
<button onclick={() => carousel.goTo(0)}>First</button>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## CSS Variables
|
|
114
|
+
|
|
115
|
+
| Variable | Default | Description |
|
|
116
|
+
| --------------------------------------- | -------------------------- | ----------------------- |
|
|
117
|
+
| `--stuic-carousel-gap` | `1rem` | Gap between items |
|
|
118
|
+
| `--stuic-carousel-peek-percent` | `0%` | Peek percentage |
|
|
119
|
+
| `--stuic-carousel-item-radius` | `--radius-md` | Item border radius |
|
|
120
|
+
| `--stuic-carousel-item-bg` | `transparent` | Item background |
|
|
121
|
+
| `--stuic-carousel-item-bg-active` | `transparent` | Active item background |
|
|
122
|
+
| `--stuic-carousel-item-border` | `transparent` | Item border color |
|
|
123
|
+
| `--stuic-carousel-item-border-active` | `--stuic-color-primary` | Active border |
|
|
124
|
+
| `--stuic-carousel-item-border-width` | `0` | Item border width |
|
|
125
|
+
| `--stuic-carousel-item-border-width-active` | `2px` | Active border width |
|
|
126
|
+
| `--stuic-carousel-ring-width` | `3px` | Focus ring width |
|
|
127
|
+
| `--stuic-carousel-ring-color` | `--stuic-color-ring` | Focus ring color |
|
|
128
|
+
|
|
129
|
+
## Keyboard Navigation
|
|
130
|
+
|
|
131
|
+
- **ArrowLeft/ArrowRight**: Previous/Next item
|
|
132
|
+
- **ArrowUp/ArrowDown**: Previous/Next item (alternative)
|
|
133
|
+
- **Home**: First item
|
|
134
|
+
- **End**: Last item
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
CAROUSEL COMPONENT TOKENS
|
|
3
|
+
Override globally: :root { --stuic-carousel-gap: 2rem; }
|
|
4
|
+
Override locally: <Carousel style="--stuic-carousel-gap: 2rem;">
|
|
5
|
+
============================================================================ */
|
|
6
|
+
|
|
7
|
+
:root {
|
|
8
|
+
/* Layout tokens */
|
|
9
|
+
--stuic-carousel-gap: 1rem;
|
|
10
|
+
--stuic-carousel-padding: 0;
|
|
11
|
+
--stuic-carousel-peek-percent: 0%;
|
|
12
|
+
|
|
13
|
+
/* Item tokens */
|
|
14
|
+
--stuic-carousel-item-radius: var(--radius-md);
|
|
15
|
+
--stuic-carousel-item-bg: transparent;
|
|
16
|
+
--stuic-carousel-item-bg-active: transparent;
|
|
17
|
+
--stuic-carousel-item-border: transparent;
|
|
18
|
+
--stuic-carousel-item-border-active: var(--stuic-color-primary);
|
|
19
|
+
--stuic-carousel-item-border-width: 1px;
|
|
20
|
+
--stuic-carousel-item-border-width-active: 1px;
|
|
21
|
+
|
|
22
|
+
/* Scroll behavior */
|
|
23
|
+
--stuic-carousel-scroll-padding: 0;
|
|
24
|
+
--stuic-carousel-snap-align: start;
|
|
25
|
+
|
|
26
|
+
/* Focus ring */
|
|
27
|
+
--stuic-carousel-ring-width: 2px;
|
|
28
|
+
--stuic-carousel-ring-color: var(--stuic-color-ring);
|
|
29
|
+
|
|
30
|
+
/* Transition */
|
|
31
|
+
--stuic-carousel-transition: 150ms;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@layer components {
|
|
35
|
+
/* ============================================================================
|
|
36
|
+
BASE STYLES
|
|
37
|
+
============================================================================ */
|
|
38
|
+
|
|
39
|
+
.stuic-carousel {
|
|
40
|
+
position: relative;
|
|
41
|
+
width: 100%;
|
|
42
|
+
overflow: visible;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.stuic-carousel-track {
|
|
46
|
+
display: flex;
|
|
47
|
+
gap: var(--stuic-carousel-gap);
|
|
48
|
+
overflow-x: auto;
|
|
49
|
+
overflow-y: hidden;
|
|
50
|
+
scroll-padding: var(--stuic-carousel-scroll-padding);
|
|
51
|
+
|
|
52
|
+
/* Hide scrollbar by default */
|
|
53
|
+
scrollbar-width: none;
|
|
54
|
+
-ms-overflow-style: none;
|
|
55
|
+
|
|
56
|
+
/* Mobile-friendly */
|
|
57
|
+
-webkit-overflow-scrolling: touch;
|
|
58
|
+
touch-action: pan-x pan-y;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* Hide scrollbar for non-scrollbar mode */
|
|
62
|
+
.stuic-carousel-track::-webkit-scrollbar {
|
|
63
|
+
display: none;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* Always show scrollbar space to prevent height jump, but thumb invisible until hover */
|
|
67
|
+
.stuic-carousel-track[data-scrollbar="true"] {
|
|
68
|
+
scrollbar-width: thin;
|
|
69
|
+
scrollbar-color: transparent transparent;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.stuic-carousel-track[data-scrollbar="true"]::-webkit-scrollbar {
|
|
73
|
+
display: block;
|
|
74
|
+
height: 8px;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.stuic-carousel-track[data-scrollbar="true"]::-webkit-scrollbar-track {
|
|
78
|
+
background: transparent;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.stuic-carousel-track[data-scrollbar="true"]::-webkit-scrollbar-thumb {
|
|
82
|
+
background: transparent;
|
|
83
|
+
border-radius: 4px;
|
|
84
|
+
transition: background 150ms;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* Show scrollbar thumb on hover */
|
|
88
|
+
.stuic-carousel-track[data-scrollbar="true"]:hover {
|
|
89
|
+
scrollbar-color: var(--stuic-color-border, #ccc) transparent;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.stuic-carousel-track[data-scrollbar="true"]:hover::-webkit-scrollbar-thumb {
|
|
93
|
+
background: var(--stuic-color-border, #ccc);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.stuic-carousel-track[data-scrollbar="true"]:hover::-webkit-scrollbar-thumb:hover {
|
|
97
|
+
background: var(--stuic-color-border-hover, #999);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* Snap scrolling */
|
|
101
|
+
.stuic-carousel-track[data-snap="true"] {
|
|
102
|
+
scroll-snap-type: x mandatory;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* Focus styles for keyboard navigation */
|
|
106
|
+
.stuic-carousel-track:focus {
|
|
107
|
+
outline: none;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.stuic-carousel-track:focus-visible {
|
|
111
|
+
outline: var(--stuic-carousel-ring-width) solid var(--stuic-carousel-ring-color);
|
|
112
|
+
outline-offset: 2px;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* ============================================================================
|
|
116
|
+
ITEM STYLES
|
|
117
|
+
============================================================================ */
|
|
118
|
+
|
|
119
|
+
.stuic-carousel-item {
|
|
120
|
+
flex: 0 0 auto;
|
|
121
|
+
/* Width calculated via CSS custom property */
|
|
122
|
+
width: var(--_item-width, 100%);
|
|
123
|
+
min-width: 0;
|
|
124
|
+
|
|
125
|
+
border-radius: var(--stuic-carousel-item-radius);
|
|
126
|
+
background: var(--stuic-carousel-item-bg);
|
|
127
|
+
border: var(--stuic-carousel-item-border-width) solid
|
|
128
|
+
var(--stuic-carousel-item-border);
|
|
129
|
+
|
|
130
|
+
transition:
|
|
131
|
+
border-color var(--stuic-carousel-transition),
|
|
132
|
+
background var(--stuic-carousel-transition),
|
|
133
|
+
border-width var(--stuic-carousel-transition);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/* Snap alignment */
|
|
137
|
+
.stuic-carousel-track[data-snap="true"] .stuic-carousel-item {
|
|
138
|
+
scroll-snap-align: var(--stuic-carousel-snap-align, start);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.stuic-carousel-track[data-snap-align="center"] .stuic-carousel-item {
|
|
142
|
+
scroll-snap-align: center;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.stuic-carousel-track[data-snap-align="end"] .stuic-carousel-item {
|
|
146
|
+
scroll-snap-align: end;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* Active item */
|
|
150
|
+
.stuic-carousel-item[data-active="true"] {
|
|
151
|
+
background: var(--stuic-carousel-item-bg-active);
|
|
152
|
+
border-color: var(--stuic-carousel-item-border-active);
|
|
153
|
+
border-width: var(--stuic-carousel-item-border-width-active);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/* Disabled item */
|
|
157
|
+
.stuic-carousel-item[data-disabled="true"] {
|
|
158
|
+
opacity: 0.5;
|
|
159
|
+
pointer-events: none;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/* ============================================================================
|
|
163
|
+
ITEMS PER VIEW CALCULATIONS
|
|
164
|
+
Width = (100% - (N-1)*gap - peek) / N
|
|
165
|
+
============================================================================ */
|
|
166
|
+
|
|
167
|
+
/* 1 item per view (default) */
|
|
168
|
+
.stuic-carousel[data-items-per-view="1"] .stuic-carousel-item {
|
|
169
|
+
--_item-width: calc(100% - var(--stuic-carousel-peek-percent, 0%));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/* 2 items per view */
|
|
173
|
+
.stuic-carousel[data-items-per-view="2"] .stuic-carousel-item {
|
|
174
|
+
--_item-width: calc(
|
|
175
|
+
(100% - var(--stuic-carousel-gap) - var(--stuic-carousel-peek-percent, 0%)) / 2
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/* 3 items per view */
|
|
180
|
+
.stuic-carousel[data-items-per-view="3"] .stuic-carousel-item {
|
|
181
|
+
--_item-width: calc(
|
|
182
|
+
(100% - 2 * var(--stuic-carousel-gap) - var(--stuic-carousel-peek-percent, 0%)) / 3
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/* 4 items per view */
|
|
187
|
+
.stuic-carousel[data-items-per-view="4"] .stuic-carousel-item {
|
|
188
|
+
--_item-width: calc(
|
|
189
|
+
(100% - 3 * var(--stuic-carousel-gap) - var(--stuic-carousel-peek-percent, 0%)) / 4
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/* 5 items per view */
|
|
194
|
+
.stuic-carousel[data-items-per-view="5"] .stuic-carousel-item {
|
|
195
|
+
--_item-width: calc(
|
|
196
|
+
(100% - 4 * var(--stuic-carousel-gap) - var(--stuic-carousel-peek-percent, 0%)) / 5
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* 6 items per view */
|
|
201
|
+
.stuic-carousel[data-items-per-view="6"] .stuic-carousel-item {
|
|
202
|
+
--_item-width: calc(
|
|
203
|
+
(100% - 5 * var(--stuic-carousel-gap) - var(--stuic-carousel-peek-percent, 0%)) / 6
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Carousel, type Props as CarouselProps, type CarouselItem, type ItemColl as CarouselItemCollection, } from "./Carousel.svelte";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Carousel, } from "./Carousel.svelte";
|
|
@@ -243,32 +243,13 @@
|
|
|
243
243
|
setValidationResult,
|
|
244
244
|
});
|
|
245
245
|
|
|
246
|
-
const INPUT_CLS =
|
|
247
|
-
"w-full",
|
|
248
|
-
// "rounded bg-neutral-50 dark:bg-neutral-800",
|
|
249
|
-
// "focus:outline-none focus:ring-0",
|
|
250
|
-
// "border border-neutral-300 dark:border-neutral-600",
|
|
251
|
-
// "focus:border-neutral-400 focus:dark:border-neutral-500",
|
|
252
|
-
// "focus-visible:outline-none focus-visible:ring-0",
|
|
253
|
-
].join(" ");
|
|
254
|
-
|
|
255
|
-
const INPUT_EXPANDED_CLS = [
|
|
256
|
-
// "w-full",
|
|
257
|
-
// "rounded bg-neutral-50 dark:bg-neutral-800",
|
|
258
|
-
// "focus:outline-none focus:ring-0",
|
|
259
|
-
"border border-neutral-300 dark:border-neutral-600",
|
|
260
|
-
// "focus:border-neutral-400 focus:dark:border-neutral-500",
|
|
261
|
-
// "focus-visible:outline-none focus-visible:ring-0",
|
|
262
|
-
].join(" ");
|
|
246
|
+
const INPUT_CLS = "w-full";
|
|
263
247
|
|
|
264
248
|
const BTN_CLS = [
|
|
249
|
+
"toggle-btn",
|
|
265
250
|
"px-2 rounded-r block",
|
|
266
|
-
"opacity-60 hover:opacity-100",
|
|
267
251
|
"min-w-[44px] min-h-[44px]",
|
|
268
252
|
"flex items-center justify-center",
|
|
269
|
-
"hover:bg-neutral-200 dark:hover:bg-neutral-600",
|
|
270
|
-
// "focus-visible:outline-neutral-400",
|
|
271
|
-
// "disabled:opacity-25 disabled:cursor-not-allowed disabled:hover:bg-transparent",
|
|
272
253
|
].join(" ");
|
|
273
254
|
</script>
|
|
274
255
|
|
|
@@ -294,19 +275,14 @@
|
|
|
294
275
|
{validation}
|
|
295
276
|
{style}
|
|
296
277
|
>
|
|
297
|
-
<div class="w-full flex">
|
|
278
|
+
<div class="stuic-input-localized w-full flex">
|
|
298
279
|
<div class="flex-1">
|
|
299
280
|
{#if !expanded}
|
|
300
281
|
{#if multiline}
|
|
301
282
|
<textarea
|
|
302
283
|
value={entries.find((e) => e.language === _defaultLanguage)?.value ?? ""}
|
|
303
284
|
oninput={(e) => updateEntry(_defaultLanguage, e.currentTarget.value)}
|
|
304
|
-
class={twMerge(
|
|
305
|
-
INPUT_CLS,
|
|
306
|
-
expanded && INPUT_EXPANDED_CLS,
|
|
307
|
-
"min-h-16",
|
|
308
|
-
classLanguageInput
|
|
309
|
-
)}
|
|
285
|
+
class={twMerge(INPUT_CLS, "min-h-16", classLanguageInput)}
|
|
310
286
|
{disabled}
|
|
311
287
|
{tabindex}
|
|
312
288
|
{placeholder}
|
|
@@ -320,30 +296,31 @@
|
|
|
320
296
|
type="text"
|
|
321
297
|
value={entries.find((e) => e.language === _defaultLanguage)?.value ?? ""}
|
|
322
298
|
oninput={(e) => updateEntry(_defaultLanguage, e.currentTarget.value)}
|
|
323
|
-
class={twMerge(INPUT_CLS,
|
|
299
|
+
class={twMerge(INPUT_CLS, classLanguageInput)}
|
|
324
300
|
{disabled}
|
|
325
301
|
{tabindex}
|
|
326
302
|
{placeholder}
|
|
327
303
|
/>
|
|
328
304
|
{/if}
|
|
329
305
|
{:else}
|
|
330
|
-
<div class="">
|
|
306
|
+
<div class="expanded-wrap">
|
|
331
307
|
<!-- Expanded: all language rows -->
|
|
332
308
|
{#each sortedLanguages as lang, idx (lang)}
|
|
333
309
|
<div
|
|
334
310
|
class={twMerge(
|
|
335
311
|
"flex-1 flex gap-2 items-center pl-2",
|
|
336
|
-
idx > 0 && "
|
|
312
|
+
idx > 0 && "entry-divider",
|
|
337
313
|
classEntry
|
|
338
314
|
)}
|
|
339
315
|
>
|
|
340
316
|
<div
|
|
341
317
|
class={twMerge(
|
|
318
|
+
"lang-label",
|
|
342
319
|
"shrink-0 min-w-8",
|
|
343
320
|
"flex",
|
|
344
|
-
"text-sm font-medium
|
|
321
|
+
"text-sm font-medium uppercase",
|
|
345
322
|
lang === _defaultLanguage &&
|
|
346
|
-
"after:content-['*'] after:
|
|
323
|
+
"lang-label-default after:content-['*'] after:pl-0.5",
|
|
347
324
|
classLanguageLabel
|
|
348
325
|
)}
|
|
349
326
|
>
|
|
@@ -59,6 +59,13 @@
|
|
|
59
59
|
--stuic-checkbox-checked-bg: var(--stuic-input-accent);
|
|
60
60
|
--stuic-checkbox-checked-border: var(--stuic-input-accent);
|
|
61
61
|
|
|
62
|
+
/* FieldInputLocalized specific */
|
|
63
|
+
--stuic-input-localized-divider: var(--stuic-color-border);
|
|
64
|
+
--stuic-input-localized-toggle-text: var(--stuic-color-muted-foreground);
|
|
65
|
+
--stuic-input-localized-toggle-text-hover: var(--stuic-color-foreground);
|
|
66
|
+
--stuic-input-localized-toggle-hover-bg: var(--stuic-color-muted);
|
|
67
|
+
--stuic-input-localized-label-text: var(--stuic-color-muted-foreground);
|
|
68
|
+
|
|
62
69
|
/* FieldOptions specific */
|
|
63
70
|
--stuic-field-options-divider: var(--stuic-color-border);
|
|
64
71
|
--stuic-field-options-control-text: var(--stuic-color-muted-foreground);
|
|
@@ -217,7 +224,11 @@
|
|
|
217
224
|
.stuic-input .input-wrap.invalid:focus-within {
|
|
218
225
|
border-color: var(--stuic-input-accent-error);
|
|
219
226
|
box-shadow: 0 0 0 var(--stuic-input-ring-width)
|
|
220
|
-
color-mix(
|
|
227
|
+
color-mix(
|
|
228
|
+
in srgb,
|
|
229
|
+
var(--stuic-input-accent-error) 20%,
|
|
230
|
+
var(--stuic-color-background)
|
|
231
|
+
);
|
|
221
232
|
}
|
|
222
233
|
|
|
223
234
|
/* Disabled state */
|
|
@@ -541,4 +552,37 @@
|
|
|
541
552
|
.stuic-field-options-icon--selected {
|
|
542
553
|
opacity: 1;
|
|
543
554
|
}
|
|
555
|
+
|
|
556
|
+
/* ============================================================================
|
|
557
|
+
FIELD INPUT LOCALIZED
|
|
558
|
+
============================================================================ */
|
|
559
|
+
|
|
560
|
+
.stuic-input-localized .entry-divider {
|
|
561
|
+
border-top: 1px solid var(--stuic-input-localized-divider);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
.stuic-input-localized .toggle-btn {
|
|
565
|
+
color: var(--stuic-input-localized-toggle-text);
|
|
566
|
+
transition:
|
|
567
|
+
background var(--stuic-input-transition),
|
|
568
|
+
color var(--stuic-input-transition);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
.stuic-input-localized .toggle-btn:hover:not(:disabled) {
|
|
572
|
+
color: var(--stuic-input-localized-toggle-text-hover);
|
|
573
|
+
background: var(--stuic-input-localized-toggle-hover-bg);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
.stuic-input-localized .lang-label {
|
|
577
|
+
color: var(--stuic-input-localized-label-text);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
.stuic-input-localized .lang-label-default::after {
|
|
581
|
+
opacity: 0.5;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
.stuic-input-localized .expanded-wrap {
|
|
585
|
+
border-right: 1px solid var(--stuic-input-localized-divider);
|
|
586
|
+
/* border-radius: var(--stuic-input-radius); */
|
|
587
|
+
}
|
|
544
588
|
}
|
package/dist/index.css
CHANGED
|
@@ -30,6 +30,7 @@ In practice:
|
|
|
30
30
|
@import "./components/Button/index.css";
|
|
31
31
|
@import "./components/ButtonGroupRadio/index.css";
|
|
32
32
|
@import "./components/Collapsible/index.css";
|
|
33
|
+
@import "./components/Carousel/index.css";
|
|
33
34
|
@import "./components/CommandMenu/index.css";
|
|
34
35
|
@import "./components/DismissibleMessage/index.css";
|
|
35
36
|
@import "./components/DropdownMenu/index.css";
|
package/dist/index.d.ts
CHANGED
|
@@ -29,6 +29,7 @@ export * from "./components/Avatar/index.js";
|
|
|
29
29
|
export * from "./components/Backdrop/index.js";
|
|
30
30
|
export * from "./components/Button/index.js";
|
|
31
31
|
export * from "./components/ButtonGroupRadio/index.js";
|
|
32
|
+
export * from "./components/Carousel/index.js";
|
|
32
33
|
export * from "./components/Collapsible/index.js";
|
|
33
34
|
export * from "./components/ColorScheme/index.js";
|
|
34
35
|
export * from "./components/CommandMenu/index.js";
|
package/dist/index.js
CHANGED
|
@@ -30,6 +30,7 @@ export * from "./components/Avatar/index.js";
|
|
|
30
30
|
export * from "./components/Backdrop/index.js";
|
|
31
31
|
export * from "./components/Button/index.js";
|
|
32
32
|
export * from "./components/ButtonGroupRadio/index.js";
|
|
33
|
+
export * from "./components/Carousel/index.js";
|
|
33
34
|
export * from "./components/Collapsible/index.js";
|
|
34
35
|
export * from "./components/ColorScheme/index.js";
|
|
35
36
|
export * from "./components/CommandMenu/index.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marianmeres/stuic",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"files": [
|
|
5
5
|
"dist",
|
|
6
6
|
"!dist/**/*.test.*",
|
|
@@ -26,22 +26,22 @@
|
|
|
26
26
|
"@marianmeres/icons-fns": "^5.0.0",
|
|
27
27
|
"@marianmeres/random-human-readable": "^1.6.1",
|
|
28
28
|
"@sveltejs/adapter-auto": "^4.0.0",
|
|
29
|
-
"@sveltejs/kit": "^2.50.
|
|
29
|
+
"@sveltejs/kit": "^2.50.2",
|
|
30
30
|
"@sveltejs/package": "^2.5.7",
|
|
31
31
|
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
|
32
32
|
"@tailwindcss/cli": "^4.1.18",
|
|
33
33
|
"@tailwindcss/forms": "^0.5.11",
|
|
34
34
|
"@tailwindcss/typography": "^0.5.19",
|
|
35
35
|
"@tailwindcss/vite": "^4.1.18",
|
|
36
|
-
"@types/node": "^25.
|
|
36
|
+
"@types/node": "^25.2.0",
|
|
37
37
|
"dotenv": "^16.6.1",
|
|
38
38
|
"eslint": "^9.39.2",
|
|
39
39
|
"globals": "^16.5.0",
|
|
40
40
|
"prettier": "^3.8.1",
|
|
41
41
|
"prettier-plugin-svelte": "^3.4.1",
|
|
42
42
|
"publint": "^0.3.17",
|
|
43
|
-
"svelte": "^5.49.
|
|
44
|
-
"svelte-check": "^4.3.
|
|
43
|
+
"svelte": "^5.49.1",
|
|
44
|
+
"svelte-check": "^4.3.6",
|
|
45
45
|
"tailwindcss": "^4.1.18",
|
|
46
46
|
"tsx": "^4.21.0",
|
|
47
47
|
"typescript": "^5.9.3",
|