@marianmeres/stuic 3.8.1 → 3.9.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/AGENTS.md CHANGED
@@ -94,3 +94,16 @@ src/lib/
94
94
  | `src/lib/themes/css/stone.css` | Default theme (generated) |
95
95
  | `src/lib/components/Button/` | Reference component |
96
96
  | `scripts/generate-theme.ts` | CLI: `pnpm run build:theme:all` |
97
+
98
+ ---
99
+
100
+ ## Svelte MCP Server
101
+
102
+ You have access to the Svelte MCP server providing comprehensive Svelte 5 documentation.
103
+ This is a **component library**, not a SvelteKit application — skip SvelteKit-specific guidance (routing, load functions, hooks, adapters, etc.).
104
+
105
+ ### Available Tools
106
+ 1. **list-sections**: Call FIRST to discover available documentation sections.
107
+ 2. **get-documentation**: Fetch full docs for specific sections (runes, lifecycle, snippets, etc.).
108
+ 3. **svelte-autofixer**: Validate Svelte code for correctness — always run before finalizing component changes.
109
+ 4. **playground-link**: Generate a Svelte Playground link — only after user confirms they want one.
@@ -0,0 +1,513 @@
1
+ <script lang="ts" module>
2
+ import type { Snippet } from "svelte";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+ import type { TranslateFn } from "../../types.js";
5
+ import { isPlainObject } from "../../utils/is-plain-object.js";
6
+ import { replaceMap } from "../../utils/replace-map.js";
7
+
8
+ // i18n ready
9
+ function t_default(
10
+ k: string,
11
+ values: false | null | undefined | Record<string, string | number> = null,
12
+ fallback: string | boolean = "",
13
+ _i18nSpanWrap: boolean = true
14
+ ) {
15
+ const m: Record<string, string> = {
16
+ empty_cart: "Your cart is empty",
17
+ unit_price_each: "{price} each",
18
+ quantity_label: "Qty: {quantity}",
19
+ remove_item: "Remove",
20
+ total_label: "Total",
21
+ item_count_1: "1 item",
22
+ item_count_n: "{count} items",
23
+ decrease_quantity: "Decrease quantity",
24
+ increase_quantity: "Increase quantity",
25
+ };
26
+ let out = m[k] ?? fallback ?? k;
27
+ return isPlainObject(values)
28
+ ? replaceMap(out, values as any, {
29
+ preSearchKeyTransform: (k) => `{${k}}`,
30
+ })
31
+ : out;
32
+ }
33
+
34
+ /** A single item in the cart */
35
+ export interface CartComponentItem {
36
+ /** Unique item identifier */
37
+ id: string;
38
+ /** Product name (displayed, used as link text) */
39
+ name: string;
40
+ /** Link to product page */
41
+ href?: string;
42
+ /** Short description (shown below name if provided) */
43
+ description?: string;
44
+ /** Image URL for product thumbnail */
45
+ thumbnailSrc?: string;
46
+ /** Image alt text (defaults to name) */
47
+ thumbnailAlt?: string;
48
+ /** Price per unit (cents integer recommended) */
49
+ unitPrice: number;
50
+ /** Current quantity */
51
+ quantity: number;
52
+ /** Pre-computed total for this line */
53
+ lineTotal: number;
54
+ /** Unit label: "pcs", "kg", etc. */
55
+ unit?: string;
56
+ /** Increment/decrement step (default 1) */
57
+ quantityStep?: number;
58
+ /** Floor for decrement (default 0) */
59
+ minQuantity?: number;
60
+ /** Ceiling for increment */
61
+ maxQuantity?: number;
62
+ }
63
+
64
+ /** Layout variant */
65
+ export type CartVariant = "default" | "compact";
66
+
67
+ export interface Props
68
+ extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
69
+ /** Cart items to display */
70
+ items: CartComponentItem[];
71
+
72
+ /** Layout variant. "compact" = smaller thumbnails, tighter spacing, scrollable, implicitly readonly */
73
+ variant?: CartVariant;
74
+
75
+ /** Format a numeric price for display. Default: (v) => (v / 100).toFixed(2) */
76
+ formatPrice?: (value: number) => string;
77
+
78
+ /** Called when quantity changes (not called in readonly/compact mode) */
79
+ onQuantityChange?: (id: string, newQuantity: number) => void;
80
+ /** Called when remove is clicked (not called in readonly/compact mode) */
81
+ onRemove?: (id: string) => void;
82
+
83
+ /** Hide all thumbnails */
84
+ noThumbnails?: boolean;
85
+ /** Hide all interactive controls — used for checkout summary */
86
+ readonly?: boolean;
87
+ /** Show loading skeleton instead of content */
88
+ loading?: boolean;
89
+ /** Set of item IDs currently being updated (shown with reduced opacity) */
90
+ updatingItems?: Set<string>;
91
+
92
+ /** Override thumbnail rendering */
93
+ thumbnail?: Snippet<[{ item: CartComponentItem }]>;
94
+ /** Override entire item row rendering */
95
+ itemRow?: Snippet<
96
+ [{
97
+ item: CartComponentItem;
98
+ isUpdating: boolean;
99
+ readonly: boolean;
100
+ formatPrice: (v: number) => string;
101
+ }]
102
+ >;
103
+ /** Override/extend summary section */
104
+ summary?: Snippet<
105
+ [{
106
+ items: CartComponentItem[];
107
+ total: number;
108
+ itemCount: number;
109
+ formatPrice: (v: number) => string;
110
+ }]
111
+ >;
112
+ /** Custom empty state */
113
+ empty?: Snippet;
114
+ /** Content after the summary (e.g., CTA buttons) */
115
+ footer?: Snippet<
116
+ [{
117
+ items: CartComponentItem[];
118
+ total: number;
119
+ itemCount: number;
120
+ }]
121
+ >;
122
+
123
+ /** Optional translate function */
124
+ t?: TranslateFn;
125
+
126
+ /** Skip all default styling */
127
+ unstyled?: boolean;
128
+ /** Additional CSS classes for the root container */
129
+ class?: string;
130
+ /** Bindable element reference */
131
+ el?: HTMLDivElement;
132
+ }
133
+ </script>
134
+
135
+ <script lang="ts">
136
+ import { twMerge } from "../../utils/tw-merge.js";
137
+ import { Breakpoint } from "../../utils/breakpoint.svelte.js";
138
+ import Skeleton from "../Skeleton/Skeleton.svelte";
139
+
140
+ let {
141
+ items,
142
+ variant = "default",
143
+ formatPrice = (v: number) => (v / 100).toFixed(2),
144
+ onQuantityChange,
145
+ onRemove,
146
+ noThumbnails = false,
147
+ readonly: readonlyProp = false,
148
+ loading = false,
149
+ updatingItems = new Set<string>(),
150
+ thumbnail,
151
+ itemRow,
152
+ summary,
153
+ empty,
154
+ footer,
155
+ t = t_default,
156
+ unstyled = false,
157
+ class: classProp,
158
+ el = $bindable(),
159
+ ...rest
160
+ }: Props = $props();
161
+
162
+ // --- Responsive ---
163
+ const bp = Breakpoint.instance;
164
+ let isDesktop = $derived(bp.md);
165
+ let isCompact = $derived(variant === "compact");
166
+ let isReadonly = $derived(readonlyProp || isCompact);
167
+
168
+ // --- Derived ---
169
+ let total = $derived(items.reduce((sum, i) => sum + i.lineTotal, 0));
170
+ let itemCount = $derived(items.reduce((sum, i) => sum + i.quantity, 0));
171
+
172
+ // --- Inline editing ---
173
+ let editingItemId = $state<string | null>(null);
174
+
175
+ function handleQuantityInputCommit(
176
+ itemId: string,
177
+ currentQty: number,
178
+ inputValue: string
179
+ ) {
180
+ editingItemId = null;
181
+ const newQty = parseInt(inputValue, 10);
182
+ if (!isNaN(newQty) && newQty !== currentQty && newQty >= 0) {
183
+ onQuantityChange?.(itemId, newQty);
184
+ }
185
+ }
186
+
187
+ function decrementQuantity(item: CartComponentItem) {
188
+ const step = item.quantityStep ?? 1;
189
+ const min = item.minQuantity ?? 0;
190
+ const newQty = Math.max(min, item.quantity - step);
191
+ if (newQty !== item.quantity) {
192
+ onQuantityChange?.(item.id, newQty);
193
+ }
194
+ }
195
+
196
+ function incrementQuantity(item: CartComponentItem) {
197
+ const step = item.quantityStep ?? 1;
198
+ const newQty = item.quantity + step;
199
+ if (item.maxQuantity != null && newQty > item.maxQuantity) return;
200
+ onQuantityChange?.(item.id, newQty);
201
+ }
202
+
203
+ // --- Auto-focus action (avoids a11y autofocus warning) ---
204
+ function autoFocusAndSelect(node: HTMLInputElement) {
205
+ node.focus();
206
+ node.select();
207
+ }
208
+
209
+ // --- CSS ---
210
+ let rootClass = $derived(
211
+ unstyled ? classProp : twMerge("stuic-cart", classProp)
212
+ );
213
+ </script>
214
+
215
+ <!-- Root container -->
216
+ <div
217
+ bind:this={el}
218
+ class={rootClass}
219
+ data-variant={!unstyled ? variant : undefined}
220
+ data-loading={!unstyled && loading ? "" : undefined}
221
+ data-readonly={!unstyled && isReadonly ? "" : undefined}
222
+ {...rest}
223
+ >
224
+ {#if loading}
225
+ <!-- Loading skeleton -->
226
+ <div class={!unstyled ? "stuic-cart-skeleton" : undefined}>
227
+ {#each [1, 2, 3] as _, i (i)}
228
+ <div class={!unstyled ? "stuic-cart-skeleton-item" : undefined}>
229
+ <Skeleton
230
+ variant="rectangle"
231
+ width={isCompact ? "3rem" : "4rem"}
232
+ height={isCompact ? "3rem" : "4rem"}
233
+ rounded="0.375rem"
234
+ />
235
+ <div class={!unstyled ? "stuic-cart-skeleton-content" : undefined}>
236
+ <Skeleton height="1.25rem" width="60%" />
237
+ <Skeleton height="0.875rem" width="30%" />
238
+ {#if !isReadonly}
239
+ <Skeleton height="2rem" width="8rem" class="mt-2" />
240
+ {/if}
241
+ </div>
242
+ <Skeleton height="1.5rem" width="4rem" />
243
+ </div>
244
+ {/each}
245
+ </div>
246
+ {:else if items.length === 0}
247
+ <!-- Empty state -->
248
+ <div class={!unstyled ? "stuic-cart-empty" : undefined}>
249
+ {#if empty}
250
+ {@render empty()}
251
+ {:else}
252
+ <p>{t("empty_cart")}</p>
253
+ {/if}
254
+ </div>
255
+ {:else}
256
+ <!-- Item list -->
257
+ <div
258
+ class={!unstyled ? "stuic-cart-items" : undefined}
259
+ data-variant={!unstyled ? variant : undefined}
260
+ >
261
+ {#each items as item (item.id)}
262
+ {@const isUpdating = updatingItems.has(item.id)}
263
+ {#if itemRow}
264
+ {@render itemRow({
265
+ item,
266
+ isUpdating,
267
+ readonly: isReadonly,
268
+ formatPrice,
269
+ })}
270
+ {:else}
271
+ <div
272
+ class={!unstyled ? "stuic-cart-item" : undefined}
273
+ data-variant={!unstyled ? variant : undefined}
274
+ data-updating={!unstyled && isUpdating ? "" : undefined}
275
+ >
276
+ <!-- Thumbnail -->
277
+ {#if !noThumbnails}
278
+ <div class={!unstyled ? "stuic-cart-item-thumbnail" : undefined}
279
+ data-variant={!unstyled ? variant : undefined}
280
+ >
281
+ {#if thumbnail}
282
+ {@render thumbnail({ item })}
283
+ {:else if item.thumbnailSrc}
284
+ {#if item.href}
285
+ <a href={item.href}>
286
+ <img
287
+ src={item.thumbnailSrc}
288
+ alt={item.thumbnailAlt ?? item.name}
289
+ class={!unstyled ? "stuic-cart-item-image" : undefined}
290
+ />
291
+ </a>
292
+ {:else}
293
+ <img
294
+ src={item.thumbnailSrc}
295
+ alt={item.thumbnailAlt ?? item.name}
296
+ class={!unstyled ? "stuic-cart-item-image" : undefined}
297
+ />
298
+ {/if}
299
+ {:else}
300
+ <div
301
+ class={!unstyled ? "stuic-cart-item-placeholder" : undefined}
302
+ ></div>
303
+ {/if}
304
+ </div>
305
+ {/if}
306
+
307
+ <!-- Info section -->
308
+ <div class={!unstyled ? "stuic-cart-item-info" : undefined}>
309
+ {#if item.href}
310
+ <a
311
+ href={item.href}
312
+ class={!unstyled
313
+ ? "stuic-cart-item-name"
314
+ : undefined}
315
+ >
316
+ {item.name}
317
+ </a>
318
+ {:else}
319
+ <span
320
+ class={!unstyled
321
+ ? "stuic-cart-item-name"
322
+ : undefined}
323
+ >
324
+ {item.name}
325
+ </span>
326
+ {/if}
327
+
328
+ {#if item.description && !isCompact}
329
+ <div
330
+ class={!unstyled
331
+ ? "stuic-cart-item-description"
332
+ : undefined}
333
+ >
334
+ {item.description}
335
+ </div>
336
+ {/if}
337
+
338
+ <div
339
+ class={!unstyled
340
+ ? "stuic-cart-item-unit-price"
341
+ : undefined}
342
+ >
343
+ {t("unit_price_each", {
344
+ price: formatPrice(item.unitPrice),
345
+ })}
346
+ </div>
347
+
348
+ {#if isReadonly}
349
+ <!-- Readonly quantity display -->
350
+ <div
351
+ class={!unstyled
352
+ ? "stuic-cart-item-quantity-readonly"
353
+ : undefined}
354
+ >
355
+ {t("quantity_label", {
356
+ quantity: item.quantity,
357
+ })}{#if item.unit}&nbsp;{item.unit}{/if}
358
+ </div>
359
+ {:else}
360
+ <!-- Interactive quantity controls -->
361
+ <div
362
+ class={!unstyled
363
+ ? "stuic-cart-item-controls"
364
+ : undefined}
365
+ >
366
+ <div
367
+ class={!unstyled
368
+ ? "stuic-cart-quantity"
369
+ : undefined}
370
+ >
371
+ <button
372
+ type="button"
373
+ class={!unstyled
374
+ ? "stuic-cart-quantity-button"
375
+ : undefined}
376
+ disabled={isUpdating ||
377
+ item.quantity <=
378
+ (item.minQuantity ?? 0)}
379
+ onclick={() =>
380
+ decrementQuantity(item)}
381
+ aria-label={t(
382
+ "decrease_quantity"
383
+ )}
384
+ >
385
+ &minus;
386
+ </button>
387
+ {#if editingItemId === item.id}
388
+ <input
389
+ type="number"
390
+ min={item.minQuantity ?? 0}
391
+ max={item.maxQuantity}
392
+ step={item.quantityStep ?? 1}
393
+ class={!unstyled
394
+ ? "stuic-cart-quantity-input"
395
+ : undefined}
396
+ value={item.quantity}
397
+ onblur={(e) =>
398
+ handleQuantityInputCommit(
399
+ item.id,
400
+ item.quantity,
401
+ e.currentTarget.value
402
+ )}
403
+ onkeydown={(e) => {
404
+ if (e.key === "Enter") {
405
+ handleQuantityInputCommit(
406
+ item.id,
407
+ item.quantity,
408
+ e.currentTarget.value
409
+ );
410
+ } else if (
411
+ e.key === "Escape"
412
+ ) {
413
+ editingItemId = null;
414
+ }
415
+ }}
416
+ use:autoFocusAndSelect
417
+ />
418
+ {:else}
419
+ <button
420
+ type="button"
421
+ class={!unstyled
422
+ ? "stuic-cart-quantity-value"
423
+ : undefined}
424
+ onclick={() =>
425
+ (editingItemId = item.id)}
426
+ disabled={isUpdating}
427
+ >
428
+ {item.quantity}
429
+ </button>
430
+ {/if}
431
+ <button
432
+ type="button"
433
+ class={!unstyled
434
+ ? "stuic-cart-quantity-button"
435
+ : undefined}
436
+ disabled={isUpdating ||
437
+ (item.maxQuantity != null &&
438
+ item.quantity >=
439
+ item.maxQuantity)}
440
+ onclick={() =>
441
+ incrementQuantity(item)}
442
+ aria-label={t(
443
+ "increase_quantity"
444
+ )}
445
+ >
446
+ +
447
+ </button>
448
+ </div>
449
+ {#if item.unit}
450
+ <span
451
+ class={!unstyled
452
+ ? "stuic-cart-item-unit"
453
+ : undefined}
454
+ >
455
+ {item.unit}
456
+ </span>
457
+ {/if}
458
+ <button
459
+ type="button"
460
+ class={!unstyled
461
+ ? "stuic-cart-remove"
462
+ : undefined}
463
+ disabled={isUpdating}
464
+ onclick={() => onRemove?.(item.id)}
465
+ >
466
+ {t("remove_item")}
467
+ </button>
468
+ </div>
469
+ {/if}
470
+ </div>
471
+
472
+ <!-- Line total -->
473
+ <div
474
+ class={!unstyled
475
+ ? "stuic-cart-item-total"
476
+ : undefined}
477
+ >
478
+ {formatPrice(item.lineTotal)}
479
+ </div>
480
+ </div>
481
+ {/if}
482
+ {/each}
483
+ </div>
484
+
485
+ <!-- Summary -->
486
+ {#if summary}
487
+ {@render summary({ items, total, itemCount, formatPrice })}
488
+ {:else}
489
+ <div class={!unstyled ? "stuic-cart-summary" : undefined}
490
+ data-variant={!unstyled ? variant : undefined}
491
+ >
492
+ <span class={!unstyled ? "stuic-cart-summary-label" : undefined}>
493
+ {t("total_label")}
494
+ ({itemCount === 1
495
+ ? t("item_count_1")
496
+ : t("item_count_n", { count: itemCount })})
497
+ </span>
498
+ <span class={!unstyled ? "stuic-cart-summary-total" : undefined}
499
+ data-variant={!unstyled ? variant : undefined}
500
+ >
501
+ {formatPrice(total)}
502
+ </span>
503
+ </div>
504
+ {/if}
505
+
506
+ <!-- Footer -->
507
+ {#if footer}
508
+ <div class={!unstyled ? "stuic-cart-footer" : undefined}>
509
+ {@render footer({ items, total, itemCount })}
510
+ </div>
511
+ {/if}
512
+ {/if}
513
+ </div>
@@ -0,0 +1,97 @@
1
+ import type { Snippet } from "svelte";
2
+ import type { HTMLAttributes } from "svelte/elements";
3
+ import type { TranslateFn } from "../../types.js";
4
+ /** A single item in the cart */
5
+ export interface CartComponentItem {
6
+ /** Unique item identifier */
7
+ id: string;
8
+ /** Product name (displayed, used as link text) */
9
+ name: string;
10
+ /** Link to product page */
11
+ href?: string;
12
+ /** Short description (shown below name if provided) */
13
+ description?: string;
14
+ /** Image URL for product thumbnail */
15
+ thumbnailSrc?: string;
16
+ /** Image alt text (defaults to name) */
17
+ thumbnailAlt?: string;
18
+ /** Price per unit (cents integer recommended) */
19
+ unitPrice: number;
20
+ /** Current quantity */
21
+ quantity: number;
22
+ /** Pre-computed total for this line */
23
+ lineTotal: number;
24
+ /** Unit label: "pcs", "kg", etc. */
25
+ unit?: string;
26
+ /** Increment/decrement step (default 1) */
27
+ quantityStep?: number;
28
+ /** Floor for decrement (default 0) */
29
+ minQuantity?: number;
30
+ /** Ceiling for increment */
31
+ maxQuantity?: number;
32
+ }
33
+ /** Layout variant */
34
+ export type CartVariant = "default" | "compact";
35
+ export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
36
+ /** Cart items to display */
37
+ items: CartComponentItem[];
38
+ /** Layout variant. "compact" = smaller thumbnails, tighter spacing, scrollable, implicitly readonly */
39
+ variant?: CartVariant;
40
+ /** Format a numeric price for display. Default: (v) => (v / 100).toFixed(2) */
41
+ formatPrice?: (value: number) => string;
42
+ /** Called when quantity changes (not called in readonly/compact mode) */
43
+ onQuantityChange?: (id: string, newQuantity: number) => void;
44
+ /** Called when remove is clicked (not called in readonly/compact mode) */
45
+ onRemove?: (id: string) => void;
46
+ /** Hide all thumbnails */
47
+ noThumbnails?: boolean;
48
+ /** Hide all interactive controls — used for checkout summary */
49
+ readonly?: boolean;
50
+ /** Show loading skeleton instead of content */
51
+ loading?: boolean;
52
+ /** Set of item IDs currently being updated (shown with reduced opacity) */
53
+ updatingItems?: Set<string>;
54
+ /** Override thumbnail rendering */
55
+ thumbnail?: Snippet<[{
56
+ item: CartComponentItem;
57
+ }]>;
58
+ /** Override entire item row rendering */
59
+ itemRow?: Snippet<[
60
+ {
61
+ item: CartComponentItem;
62
+ isUpdating: boolean;
63
+ readonly: boolean;
64
+ formatPrice: (v: number) => string;
65
+ }
66
+ ]>;
67
+ /** Override/extend summary section */
68
+ summary?: Snippet<[
69
+ {
70
+ items: CartComponentItem[];
71
+ total: number;
72
+ itemCount: number;
73
+ formatPrice: (v: number) => string;
74
+ }
75
+ ]>;
76
+ /** Custom empty state */
77
+ empty?: Snippet;
78
+ /** Content after the summary (e.g., CTA buttons) */
79
+ footer?: Snippet<[
80
+ {
81
+ items: CartComponentItem[];
82
+ total: number;
83
+ itemCount: number;
84
+ }
85
+ ]>;
86
+ /** Optional translate function */
87
+ t?: TranslateFn;
88
+ /** Skip all default styling */
89
+ unstyled?: boolean;
90
+ /** Additional CSS classes for the root container */
91
+ class?: string;
92
+ /** Bindable element reference */
93
+ el?: HTMLDivElement;
94
+ }
95
+ declare const Cart: import("svelte").Component<Props, {}, "el">;
96
+ type Cart = ReturnType<typeof Cart>;
97
+ export default Cart;
@@ -0,0 +1,185 @@
1
+ # Cart
2
+
3
+ A reusable shopping cart component for displaying cart items with quantity controls,
4
+ pricing, and a summary section. Supports interactive (full cart) and readonly (checkout
5
+ summary) modes, plus a compact variant suitable for popover previews.
6
+
7
+ ## Usage
8
+
9
+ ```svelte
10
+ <script>
11
+ import { Cart } from "@marianmeres/stuic";
12
+
13
+ let items = [
14
+ {
15
+ id: "prod-1",
16
+ name: "Widget Pro",
17
+ href: "/products/widget-pro",
18
+ thumbnailSrc: "/images/widget.jpg",
19
+ unitPrice: 1999, // cents
20
+ quantity: 2,
21
+ lineTotal: 3998,
22
+ },
23
+ {
24
+ id: "prod-2",
25
+ name: "Gadget Lite",
26
+ unitPrice: 499,
27
+ quantity: 1,
28
+ lineTotal: 499,
29
+ },
30
+ ];
31
+
32
+ function handleQuantityChange(id, newQuantity) {
33
+ // sync with server, then update items
34
+ }
35
+
36
+ function handleRemove(id) {
37
+ // remove from server, then update items
38
+ }
39
+ </script>
40
+
41
+ <Cart
42
+ {items}
43
+ formatPrice={(v) => `$${(v / 100).toFixed(2)}`}
44
+ onQuantityChange={handleQuantityChange}
45
+ onRemove={handleRemove}
46
+ />
47
+ ```
48
+
49
+ ### Readonly Mode (checkout summary)
50
+
51
+ ```svelte
52
+ <Cart {items} readonly formatPrice={(v) => `$${(v / 100).toFixed(2)}`} />
53
+ ```
54
+
55
+ ### Compact Variant (for popovers)
56
+
57
+ ```svelte
58
+ <Cart
59
+ {items}
60
+ variant="compact"
61
+ formatPrice={(v) => `$${(v / 100).toFixed(2)}`}
62
+ >
63
+ {#snippet footer({ total, itemCount })}
64
+ <div class="flex gap-2">
65
+ <a href="/cart">View Cart</a>
66
+ <a href="/checkout">Checkout</a>
67
+ </div>
68
+ {/snippet}
69
+ </Cart>
70
+ ```
71
+
72
+ ### Integration with @marianmeres/ecsuite
73
+
74
+ ```svelte
75
+ <script>
76
+ import { Cart } from "@marianmeres/stuic";
77
+ import type { EnrichedCartItem } from "@marianmeres/ecsuite";
78
+ import type { CartComponentItem } from "@marianmeres/stuic";
79
+
80
+ // Map from ecsuite's EnrichedCartItem to Cart's CartComponentItem
81
+ function toCartItems(enriched: EnrichedCartItem[]): CartComponentItem[] {
82
+ return enriched.map((ei) => ({
83
+ id: ei.product_id,
84
+ name: ei.product?.name ?? "Unknown Product",
85
+ href: ei.product?.slug ? `/products/${ei.product.slug}` : undefined,
86
+ description: ei.product?.short_description,
87
+ thumbnailSrc: getProductImage(ei.product), // your image helper
88
+ unitPrice: ei.product?.price ?? 0,
89
+ quantity: ei.quantity,
90
+ lineTotal: ei.lineTotal,
91
+ }));
92
+ }
93
+
94
+ // Use with ecsuite's CartManager
95
+ async function handleQuantityChange(id: string, qty: number) {
96
+ await suite.cart.updateItemQuantity(id, qty);
97
+ items = toCartItems(await suite.cart.getEnrichedItems(suite.product));
98
+ }
99
+
100
+ async function handleRemove(id: string) {
101
+ await suite.cart.removeItem(id);
102
+ items = toCartItems(await suite.cart.getEnrichedItems(suite.product));
103
+ }
104
+ </script>
105
+ ```
106
+
107
+ ## Props
108
+
109
+ | Prop | Type | Default | Description |
110
+ |------|------|---------|-------------|
111
+ | `items` | `CartComponentItem[]` | required | Cart items to display |
112
+ | `variant` | `"default" \| "compact"` | `"default"` | Layout variant. Compact is smaller, scrollable, implicitly readonly |
113
+ | `formatPrice` | `(value: number) => string` | `(v) => (v / 100).toFixed(2)` | Format numeric price for display |
114
+ | `onQuantityChange` | `(id: string, qty: number) => void` | — | Called when quantity changes |
115
+ | `onRemove` | `(id: string) => void` | — | Called when remove is clicked |
116
+ | `readonly` | `boolean` | `false` | Hide interactive controls |
117
+ | `loading` | `boolean` | `false` | Show loading skeleton |
118
+ | `updatingItems` | `Set<string>` | `new Set()` | Item IDs currently being updated |
119
+ | `t` | `TranslateFn` | built-in | Translation function |
120
+ | `unstyled` | `boolean` | `false` | Skip all default styling |
121
+ | `class` | `string` | — | Additional CSS classes |
122
+ | `el` | `HTMLDivElement` | — | Bindable element reference |
123
+
124
+ ### CartComponentItem
125
+
126
+ | Field | Type | Required | Description |
127
+ |-------|------|----------|-------------|
128
+ | `id` | `string` | yes | Unique identifier |
129
+ | `name` | `string` | yes | Product name |
130
+ | `href` | `string` | — | Link to product page |
131
+ | `description` | `string` | — | Short description |
132
+ | `thumbnailSrc` | `string` | — | Image URL |
133
+ | `thumbnailAlt` | `string` | — | Image alt (defaults to name) |
134
+ | `unitPrice` | `number` | yes | Price per unit (cents) |
135
+ | `quantity` | `number` | yes | Current quantity |
136
+ | `lineTotal` | `number` | yes | Pre-computed line total |
137
+ | `unit` | `string` | — | Unit label ("pcs", "kg") |
138
+ | `quantityStep` | `number` | — | +/- step (default 1) |
139
+ | `minQuantity` | `number` | — | Min quantity (default 0) |
140
+ | `maxQuantity` | `number` | — | Max quantity |
141
+
142
+ ### Snippets
143
+
144
+ | Snippet | Params | Description |
145
+ |---------|--------|-------------|
146
+ | `thumbnail` | `{ item }` | Override thumbnail rendering |
147
+ | `itemRow` | `{ item, isUpdating, readonly, formatPrice }` | Override entire item row |
148
+ | `summary` | `{ items, total, itemCount, formatPrice }` | Override summary section |
149
+ | `empty` | — | Custom empty state |
150
+ | `footer` | `{ items, total, itemCount }` | Content after summary (CTAs) |
151
+
152
+ ## CSS Variables
153
+
154
+ | Variable | Default | Description |
155
+ |----------|---------|-------------|
156
+ | `--stuic-cart-gap` | `1rem` | Gap between items |
157
+ | `--stuic-cart-item-padding` | `1rem` | Item card padding |
158
+ | `--stuic-cart-item-radius` | `var(--radius-lg)` | Item border radius |
159
+ | `--stuic-cart-item-border-color` | `var(--stuic-color-border)` | Item border color |
160
+ | `--stuic-cart-item-bg` | `var(--stuic-color-background)` | Item background |
161
+ | `--stuic-cart-thumbnail-size` | `4rem` | Thumbnail dimensions |
162
+ | `--stuic-cart-thumbnail-size-sm` | `3rem` | Compact thumbnail dimensions |
163
+ | `--stuic-cart-thumbnail-radius` | `var(--radius-md)` | Thumbnail border radius |
164
+ | `--stuic-cart-thumbnail-bg` | `var(--stuic-color-muted)` | Thumbnail placeholder bg |
165
+ | `--stuic-cart-quantity-border-color` | `var(--stuic-color-border)` | Quantity control border |
166
+ | `--stuic-cart-quantity-button-size` | `2rem` | Quantity button dimensions |
167
+ | `--stuic-cart-remove-color` | `var(--stuic-color-destructive)` | Remove button color |
168
+ | `--stuic-cart-summary-border-color` | `var(--stuic-color-border)` | Summary separator |
169
+ | `--stuic-cart-compact-max-height` | `12rem` | Compact variant scroll height |
170
+ | `--stuic-cart-compact-item-padding` | `0.5rem` | Compact item padding |
171
+ | `--stuic-cart-transition` | `150ms` | Transition duration |
172
+
173
+ ## Translation Keys
174
+
175
+ | Key | Default | Description |
176
+ |-----|---------|-------------|
177
+ | `empty_cart` | "Your cart is empty" | Empty state text |
178
+ | `unit_price_each` | "{price} each" | Unit price label |
179
+ | `quantity_label` | "Qty: {quantity}" | Readonly quantity display |
180
+ | `remove_item` | "Remove" | Remove button text |
181
+ | `total_label` | "Total" | Summary label |
182
+ | `item_count_1` | "1 item" | Singular item count |
183
+ | `item_count_n` | "{count} items" | Plural item count |
184
+ | `decrease_quantity` | "Decrease quantity" | Aria label for − button |
185
+ | `increase_quantity` | "Increase quantity" | Aria label for + button |
@@ -0,0 +1,409 @@
1
+ /* ============================================================================
2
+ CART COMPONENT TOKENS
3
+ Override globally: :root { --stuic-cart-item-padding: 0.5rem; }
4
+ Override locally: <Cart style="--stuic-cart-item-padding: 0.5rem;">
5
+ ============================================================================ */
6
+
7
+ :root {
8
+ /* Layout */
9
+ --stuic-cart-gap: 1rem;
10
+ --stuic-cart-transition: 150ms;
11
+
12
+ /* Item card */
13
+ --stuic-cart-item-padding: 1rem;
14
+ --stuic-cart-item-radius: var(--radius-lg);
15
+ --stuic-cart-item-border-color: var(--stuic-color-border);
16
+ --stuic-cart-item-bg: var(--stuic-color-background);
17
+
18
+ /* Thumbnail */
19
+ --stuic-cart-thumbnail-size: 4rem;
20
+ --stuic-cart-thumbnail-size-sm: 3rem;
21
+ --stuic-cart-thumbnail-radius: var(--radius-md);
22
+ --stuic-cart-thumbnail-bg: var(--stuic-color-muted);
23
+
24
+ /* Quantity controls */
25
+ --stuic-cart-quantity-border-color: var(--stuic-color-border);
26
+ --stuic-cart-quantity-button-size: 2rem;
27
+ --stuic-cart-quantity-button-bg-hover: var(--stuic-color-muted);
28
+ --stuic-cart-quantity-input-bg: var(--stuic-color-input);
29
+
30
+ /* Remove button */
31
+ --stuic-cart-remove-color: var(--stuic-color-destructive);
32
+ --stuic-cart-remove-color-hover: var(--stuic-color-destructive-hover);
33
+
34
+ /* Summary */
35
+ --stuic-cart-summary-border-color: var(--stuic-color-border);
36
+
37
+ /* Compact variant */
38
+ --stuic-cart-compact-max-height: 12rem;
39
+ --stuic-cart-compact-item-padding: 0.5rem;
40
+ }
41
+
42
+ @layer components {
43
+ /* ============================================================================
44
+ BASE STYLES
45
+ ============================================================================ */
46
+
47
+ .stuic-cart {
48
+ display: flex;
49
+ flex-direction: column;
50
+ }
51
+
52
+ /* ============================================================================
53
+ SKELETON (loading state)
54
+ ============================================================================ */
55
+
56
+ .stuic-cart-skeleton {
57
+ display: flex;
58
+ flex-direction: column;
59
+ gap: var(--stuic-cart-gap);
60
+ }
61
+
62
+ .stuic-cart-skeleton-item {
63
+ display: flex;
64
+ align-items: flex-start;
65
+ gap: 1rem;
66
+ padding: var(--stuic-cart-item-padding);
67
+ border: 1px solid var(--stuic-cart-item-border-color);
68
+ border-radius: var(--stuic-cart-item-radius);
69
+ background: var(--stuic-cart-item-bg);
70
+ }
71
+
72
+ .stuic-cart-skeleton-content {
73
+ flex: 1;
74
+ min-width: 0;
75
+ display: flex;
76
+ flex-direction: column;
77
+ gap: 0.5rem;
78
+ }
79
+
80
+ /* ============================================================================
81
+ EMPTY STATE
82
+ ============================================================================ */
83
+
84
+ .stuic-cart-empty {
85
+ text-align: center;
86
+ padding: 3rem 1rem;
87
+ opacity: 0.7;
88
+ }
89
+
90
+ /* ============================================================================
91
+ ITEMS LIST
92
+ ============================================================================ */
93
+
94
+ .stuic-cart-items {
95
+ display: flex;
96
+ flex-direction: column;
97
+ gap: var(--stuic-cart-gap);
98
+ }
99
+
100
+ .stuic-cart-items[data-variant="compact"] {
101
+ max-height: var(--stuic-cart-compact-max-height);
102
+ overflow-y: auto;
103
+ gap: 0;
104
+ }
105
+
106
+ /* ============================================================================
107
+ ITEM CARD
108
+ ============================================================================ */
109
+
110
+ .stuic-cart-item {
111
+ display: flex;
112
+ align-items: flex-start;
113
+ gap: 1rem;
114
+ padding: var(--stuic-cart-item-padding);
115
+ border: 1px solid var(--stuic-cart-item-border-color);
116
+ border-radius: var(--stuic-cart-item-radius);
117
+ background: var(--stuic-cart-item-bg);
118
+ transition: opacity var(--stuic-cart-transition);
119
+ }
120
+
121
+ .stuic-cart-item[data-variant="compact"] {
122
+ padding: var(--stuic-cart-compact-item-padding);
123
+ border-radius: 0;
124
+ border-left: 0;
125
+ border-right: 0;
126
+ border-bottom: 0;
127
+ }
128
+
129
+ .stuic-cart-item[data-variant="compact"]:first-child {
130
+ border-top: 0;
131
+ }
132
+
133
+ .stuic-cart-item[data-updating] {
134
+ opacity: 0.5;
135
+ pointer-events: none;
136
+ }
137
+
138
+ /* ============================================================================
139
+ THUMBNAIL
140
+ ============================================================================ */
141
+
142
+ .stuic-cart-item-thumbnail {
143
+ flex-shrink: 0;
144
+ width: var(--stuic-cart-thumbnail-size);
145
+ height: var(--stuic-cart-thumbnail-size);
146
+ border-radius: var(--stuic-cart-thumbnail-radius);
147
+ background: var(--stuic-cart-thumbnail-bg);
148
+ overflow: hidden;
149
+ display: flex;
150
+ align-items: center;
151
+ justify-content: center;
152
+ }
153
+
154
+ .stuic-cart-item-thumbnail[data-variant="compact"] {
155
+ width: var(--stuic-cart-thumbnail-size-sm);
156
+ height: var(--stuic-cart-thumbnail-size-sm);
157
+ }
158
+
159
+ .stuic-cart-item-image {
160
+ width: 100%;
161
+ height: 100%;
162
+ object-fit: cover;
163
+ }
164
+
165
+ .stuic-cart-item-placeholder {
166
+ width: 100%;
167
+ height: 100%;
168
+ background: var(--stuic-cart-thumbnail-bg);
169
+ opacity: 0.3;
170
+ }
171
+
172
+ /* ============================================================================
173
+ ITEM INFO
174
+ ============================================================================ */
175
+
176
+ .stuic-cart-item-info {
177
+ flex: 1;
178
+ min-width: 0;
179
+ }
180
+
181
+ a.stuic-cart-item-name {
182
+ font-weight: var(--font-weight-medium);
183
+ display: block;
184
+ overflow: hidden;
185
+ text-overflow: ellipsis;
186
+ white-space: nowrap;
187
+ text-decoration: none;
188
+ color: inherit;
189
+ }
190
+
191
+ a.stuic-cart-item-name:hover {
192
+ text-decoration: underline;
193
+ }
194
+
195
+ span.stuic-cart-item-name {
196
+ font-weight: var(--font-weight-medium);
197
+ display: block;
198
+ overflow: hidden;
199
+ text-overflow: ellipsis;
200
+ white-space: nowrap;
201
+ }
202
+
203
+ .stuic-cart-item-description {
204
+ font-size: var(--text-sm);
205
+ opacity: 0.6;
206
+ margin-top: 0.125rem;
207
+ overflow: hidden;
208
+ text-overflow: ellipsis;
209
+ white-space: nowrap;
210
+ }
211
+
212
+ .stuic-cart-item-unit-price {
213
+ font-size: var(--text-sm);
214
+ opacity: 0.6;
215
+ margin-top: 0.25rem;
216
+ }
217
+
218
+ .stuic-cart-item-quantity-readonly {
219
+ font-size: var(--text-sm);
220
+ opacity: 0.6;
221
+ margin-top: 0.25rem;
222
+ }
223
+
224
+ /* ============================================================================
225
+ QUANTITY CONTROLS
226
+ ============================================================================ */
227
+
228
+ .stuic-cart-item-controls {
229
+ display: flex;
230
+ align-items: center;
231
+ gap: 0.75rem;
232
+ margin-top: 0.75rem;
233
+ flex-wrap: wrap;
234
+ }
235
+
236
+ .stuic-cart-quantity {
237
+ display: inline-flex;
238
+ align-items: center;
239
+ border: 1px solid var(--stuic-cart-quantity-border-color);
240
+ border-radius: var(--radius-md);
241
+ overflow: hidden;
242
+ }
243
+
244
+ .stuic-cart-quantity-button {
245
+ display: flex;
246
+ align-items: center;
247
+ justify-content: center;
248
+ width: var(--stuic-cart-quantity-button-size);
249
+ height: var(--stuic-cart-quantity-button-size);
250
+ background: transparent;
251
+ border: none;
252
+ cursor: pointer;
253
+ font-size: 1rem;
254
+ line-height: 1;
255
+ color: inherit;
256
+ transition: background var(--stuic-cart-transition);
257
+ -webkit-tap-highlight-color: transparent;
258
+ }
259
+
260
+ .stuic-cart-quantity-button:hover:not(:disabled) {
261
+ background: var(--stuic-cart-quantity-button-bg-hover);
262
+ }
263
+
264
+ .stuic-cart-quantity-button:disabled {
265
+ opacity: 0.3;
266
+ cursor: not-allowed;
267
+ }
268
+
269
+ .stuic-cart-quantity-value {
270
+ display: flex;
271
+ align-items: center;
272
+ justify-content: center;
273
+ min-width: 2.5rem;
274
+ height: var(--stuic-cart-quantity-button-size);
275
+ background: transparent;
276
+ border: none;
277
+ cursor: text;
278
+ font-size: var(--text-sm);
279
+ font-weight: var(--font-weight-medium);
280
+ text-align: center;
281
+ color: inherit;
282
+ transition: background var(--stuic-cart-transition);
283
+ -webkit-tap-highlight-color: transparent;
284
+ }
285
+
286
+ .stuic-cart-quantity-value:hover:not(:disabled) {
287
+ background: var(--stuic-cart-quantity-input-bg);
288
+ }
289
+
290
+ .stuic-cart-quantity-value:disabled {
291
+ cursor: default;
292
+ }
293
+
294
+ .stuic-cart-quantity-input {
295
+ width: 3rem;
296
+ height: var(--stuic-cart-quantity-button-size);
297
+ text-align: center;
298
+ font-size: var(--text-sm);
299
+ font-weight: var(--font-weight-medium);
300
+ border: none;
301
+ background: var(--stuic-cart-quantity-input-bg);
302
+ color: inherit;
303
+ outline: none;
304
+ -moz-appearance: textfield;
305
+ }
306
+
307
+ .stuic-cart-quantity-input::-webkit-inner-spin-button,
308
+ .stuic-cart-quantity-input::-webkit-outer-spin-button {
309
+ -webkit-appearance: none;
310
+ margin: 0;
311
+ }
312
+
313
+ .stuic-cart-item-unit {
314
+ font-size: var(--text-sm);
315
+ opacity: 0.6;
316
+ }
317
+
318
+ /* ============================================================================
319
+ REMOVE BUTTON
320
+ ============================================================================ */
321
+
322
+ .stuic-cart-remove {
323
+ background: none;
324
+ border: none;
325
+ cursor: pointer;
326
+ font-size: var(--text-sm);
327
+ color: var(--stuic-cart-remove-color);
328
+ padding: 0;
329
+ -webkit-tap-highlight-color: transparent;
330
+ transition: color var(--stuic-cart-transition);
331
+ }
332
+
333
+ .stuic-cart-remove:hover:not(:disabled) {
334
+ text-decoration: underline;
335
+ color: var(--stuic-cart-remove-color-hover);
336
+ }
337
+
338
+ .stuic-cart-remove:disabled {
339
+ opacity: 0.3;
340
+ cursor: not-allowed;
341
+ }
342
+
343
+ /* ============================================================================
344
+ LINE TOTAL
345
+ ============================================================================ */
346
+
347
+ .stuic-cart-item-total {
348
+ flex-shrink: 0;
349
+ text-align: right;
350
+ font-weight: var(--font-weight-bold);
351
+ font-size: var(--text-lg);
352
+ white-space: nowrap;
353
+ }
354
+
355
+ .stuic-cart-item[data-variant="compact"] .stuic-cart-item-total {
356
+ font-size: var(--text-base);
357
+ }
358
+
359
+ /* ============================================================================
360
+ SUMMARY
361
+ ============================================================================ */
362
+
363
+ .stuic-cart-summary {
364
+ display: flex;
365
+ justify-content: space-between;
366
+ align-items: center;
367
+ margin-top: 1.5rem;
368
+ padding-top: 1.5rem;
369
+ border-top: 1px solid var(--stuic-cart-summary-border-color);
370
+ }
371
+
372
+ .stuic-cart-summary[data-variant="compact"] {
373
+ margin-top: 0;
374
+ padding: var(--stuic-cart-compact-item-padding);
375
+ padding-top: calc(var(--stuic-cart-compact-item-padding) + 0.25rem);
376
+ border-top: 1px solid var(--stuic-cart-summary-border-color);
377
+ }
378
+
379
+ .stuic-cart-summary-label {
380
+ font-size: var(--text-lg);
381
+ }
382
+
383
+ .stuic-cart-summary[data-variant="compact"] .stuic-cart-summary-label {
384
+ font-size: var(--text-base);
385
+ }
386
+
387
+ .stuic-cart-summary-total {
388
+ font-size: var(--text-2xl);
389
+ font-weight: var(--font-weight-bold);
390
+ }
391
+
392
+ .stuic-cart-summary-total[data-variant="compact"] {
393
+ font-size: var(--text-lg);
394
+ }
395
+
396
+ /* ============================================================================
397
+ FOOTER
398
+ ============================================================================ */
399
+
400
+ .stuic-cart-footer {
401
+ margin-top: 1.5rem;
402
+ }
403
+
404
+ .stuic-cart-summary[data-variant="compact"] + .stuic-cart-footer {
405
+ margin-top: 0;
406
+ padding: var(--stuic-cart-compact-item-padding);
407
+ border-top: 1px solid var(--stuic-cart-summary-border-color);
408
+ }
409
+ }
@@ -0,0 +1 @@
1
+ export { default as Cart, type Props as CartProps, type CartComponentItem, type CartVariant, } from "./Cart.svelte";
@@ -0,0 +1 @@
1
+ export { default as Cart, } from "./Cart.svelte";
@@ -23,7 +23,11 @@
23
23
  select_row: "Select row",
24
24
  };
25
25
  let out = m[k] ?? fallback ?? k;
26
- return isPlainObject(values) ? replaceMap(out, values as any) : out;
26
+ return isPlainObject(values)
27
+ ? replaceMap(out, values as any, {
28
+ preSearchKeyTransform: (k) => `{${k}}`,
29
+ })
30
+ : out;
27
31
  }
28
32
 
29
33
  export interface DataTableColumn<T = Record<string, any>> {
@@ -7,6 +7,7 @@
7
7
  /* prettier-ignore */
8
8
  :root {
9
9
  /* Structure */
10
+ --stuic-data-table-layout: fixed;
10
11
  --stuic-data-table-radius: var(--radius-md);
11
12
  --stuic-data-table-border-width: 1px;
12
13
  --stuic-data-table-border-color: var(--stuic-color-border);
@@ -83,7 +84,7 @@
83
84
  .stuic-data-table table {
84
85
  width: 100%;
85
86
  border-collapse: collapse;
86
- table-layout: fixed;
87
+ table-layout: var(--stuic-data-table-layout);
87
88
  }
88
89
 
89
90
  /* ============================================================================
@@ -605,17 +605,23 @@
605
605
  isOpen ? BodyScroll.lock() : BodyScroll.unlock();
606
606
  });
607
607
 
608
- // Click outside handler
609
- onClickOutside(
608
+ // Click outside handler — only active when open (prevents stale refs on destroy)
609
+ const _clickOutside = onClickOutside(
610
610
  () => wrapperEl,
611
611
  () => {
612
612
  if (closeOnClickOutside && isOpen) {
613
613
  isOpen = false;
614
614
  triggerEl?.focus();
615
615
  }
616
- }
616
+ },
617
+ { immediate: false }
617
618
  );
618
619
 
620
+ $effect(() => {
621
+ if (isOpen && wrapperEl) _clickOutside.start();
622
+ else _clickOutside.stop();
623
+ });
624
+
619
625
  // Helper to generate item IDs
620
626
  function itemId(id: string | number): string {
621
627
  return `${dropdownId}-item-${id}`;
@@ -106,11 +106,18 @@
106
106
  };
107
107
  }
108
108
 
109
- onClickOutside(
109
+ // Click outside handler — only active when visible (prevents stale refs on destroy)
110
+ const _clickOutside = onClickOutside(
110
111
  () => box,
111
- () => !noClickOutsideClose && close()
112
+ () => !noClickOutsideClose && close(),
113
+ { immediate: false }
112
114
  );
113
115
 
116
+ $effect(() => {
117
+ if (visible && box) _clickOutside.start();
118
+ else _clickOutside.stop();
119
+ });
120
+
114
121
  $effect(() => {
115
122
  // noop if we're undefined ($effect runs immediately as onMount)
116
123
  if (visible === undefined || noScrollLock) return;
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/Cart/index.css";
33
34
  @import "./components/Carousel/index.css";
34
35
  @import "./components/CommandMenu/index.css";
35
36
  @import "./components/DataTable/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/Cart/index.js";
32
33
  export * from "./components/Carousel/index.js";
33
34
  export * from "./components/Collapsible/index.js";
34
35
  export * from "./components/ColorScheme/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/Cart/index.js";
33
34
  export * from "./components/Carousel/index.js";
34
35
  export * from "./components/Collapsible/index.js";
35
36
  export * from "./components/ColorScheme/index.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marianmeres/stuic",
3
- "version": "3.8.1",
3
+ "version": "3.9.0",
4
4
  "files": [
5
5
  "dist",
6
6
  "!dist/**/*.test.*",
@@ -46,14 +46,14 @@
46
46
  "@tailwindcss/forms": "^0.5.11",
47
47
  "@tailwindcss/typography": "^0.5.19",
48
48
  "@tailwindcss/vite": "^4.1.18",
49
- "@types/node": "^25.2.1",
49
+ "@types/node": "^25.2.2",
50
50
  "dotenv": "^16.6.1",
51
51
  "eslint": "^9.39.2",
52
52
  "globals": "^16.5.0",
53
53
  "prettier": "^3.8.1",
54
54
  "prettier-plugin-svelte": "^3.4.1",
55
55
  "publint": "^0.3.17",
56
- "svelte": "^5.49.2",
56
+ "svelte": "^5.50.0",
57
57
  "svelte-check": "^4.3.6",
58
58
  "tailwindcss": "^4.1.18",
59
59
  "tsx": "^4.21.0",