@spaethtech/svelte-ui 0.14.0 → 0.15.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.
Files changed (54) hide show
  1. package/.claude/skills/svelte-ui/SKILL.md +14 -11
  2. package/dist/components/Carousel/Carousel.svelte +147 -0
  3. package/dist/components/Carousel/Carousel.svelte.d.ts +38 -0
  4. package/dist/components/Carousel/index.d.ts +1 -0
  5. package/dist/components/Carousel/index.js +1 -0
  6. package/dist/components/Combobox/Combobox.svelte +62 -31
  7. package/dist/components/Combobox/Combobox.svelte.d.ts +1 -1
  8. package/dist/components/CommandPalette/CommandPalette.svelte +203 -0
  9. package/dist/components/CommandPalette/CommandPalette.svelte.d.ts +23 -0
  10. package/dist/components/CommandPalette/index.d.ts +2 -0
  11. package/dist/components/CommandPalette/index.js +1 -0
  12. package/dist/components/ContextMenu/ContextMenu.svelte +58 -0
  13. package/dist/components/ContextMenu/ContextMenu.svelte.d.ts +16 -0
  14. package/dist/components/ContextMenu/index.d.ts +1 -0
  15. package/dist/components/ContextMenu/index.js +1 -0
  16. package/dist/components/EmptyState/EmptyState.svelte +68 -0
  17. package/dist/components/EmptyState/EmptyState.svelte.d.ts +19 -0
  18. package/dist/components/EmptyState/index.d.ts +1 -0
  19. package/dist/components/EmptyState/index.js +1 -0
  20. package/dist/components/Kbd/Kbd.svelte +53 -0
  21. package/dist/components/Kbd/Kbd.svelte.d.ts +15 -0
  22. package/dist/components/Kbd/index.d.ts +1 -0
  23. package/dist/components/Kbd/index.js +1 -0
  24. package/dist/components/Pagination/Pagination.svelte +58 -43
  25. package/dist/components/Pagination/Pagination.svelte.d.ts +7 -2
  26. package/dist/components/Rating.svelte +169 -30
  27. package/dist/components/Rating.svelte.d.ts +22 -4
  28. package/dist/components/ScrollArea/ScrollArea.svelte +71 -0
  29. package/dist/components/ScrollArea/ScrollArea.svelte.d.ts +15 -0
  30. package/dist/components/ScrollArea/index.d.ts +1 -0
  31. package/dist/components/ScrollArea/index.js +1 -0
  32. package/dist/components/Slider/Slider.svelte +5 -3
  33. package/dist/components/Splitter/Splitter.svelte +127 -0
  34. package/dist/components/Splitter/Splitter.svelte.d.ts +18 -0
  35. package/dist/components/Splitter/index.d.ts +1 -0
  36. package/dist/components/Splitter/index.js +1 -0
  37. package/dist/components/Stat/Stat.svelte +95 -0
  38. package/dist/components/Stat/Stat.svelte.d.ts +21 -0
  39. package/dist/components/Stat/index.d.ts +1 -0
  40. package/dist/components/Stat/index.js +1 -0
  41. package/dist/components/Timeline/Timeline.svelte +98 -0
  42. package/dist/components/Timeline/Timeline.svelte.d.ts +21 -0
  43. package/dist/components/Timeline/index.d.ts +2 -0
  44. package/dist/components/Timeline/index.js +1 -0
  45. package/dist/components/TokenInput/TokenInput.svelte +12 -6
  46. package/dist/components/Tree/Tree.svelte +178 -0
  47. package/dist/components/Tree/Tree.svelte.d.ts +26 -0
  48. package/dist/components/Tree/index.d.ts +2 -0
  49. package/dist/components/Tree/index.js +1 -0
  50. package/dist/index.d.ts +13 -2
  51. package/dist/index.js +10 -1
  52. package/docs/components.md +125 -22
  53. package/docs/usage.md +180 -13
  54. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ export { default as Stat } from "./Stat.svelte";
@@ -0,0 +1,98 @@
1
+ <!--
2
+ /**
3
+ * Timeline — a vertical sequence of events: a marker (dot or icon) on a connecting line, with a
4
+ * title / time / description per item. Per-item `variant` colours its marker. `--ui-*` tokens.
5
+ * See Timeline.spec.md.
6
+ */
7
+ -->
8
+ <script lang="ts" module>
9
+ import type { Snippet } from "svelte";
10
+ import type { Variant } from "../../types/variants.js";
11
+ export type TimelineItem = {
12
+ title: string;
13
+ time?: string;
14
+ description?: string;
15
+ icon?: Snippet;
16
+ variant?: Variant;
17
+ content?: Snippet;
18
+ };
19
+ </script>
20
+
21
+ <script lang="ts">
22
+ import { variantToken } from "../../types/variants.js";
23
+ import type { Size } from "../../types/sizes.js";
24
+ import { responsiveClasses, type Responsive } from "../../types/responsive.js";
25
+
26
+ let {
27
+ items,
28
+ variant = "primary",
29
+ size = "md",
30
+ class: cls = "",
31
+ }: {
32
+ items: TimelineItem[];
33
+ variant?: Variant;
34
+ size?: Responsive<Size>;
35
+ class?: string;
36
+ } = $props();
37
+
38
+ const dotSize: Record<Size, string> = { sm: "w-2.5 h-2.5", md: "w-3 h-3", lg: "w-3.5 h-3.5" };
39
+ const iconBox: Record<Size, string> = {
40
+ sm: "w-6 h-6 [&_svg]:w-3.5 [&_svg]:h-3.5",
41
+ md: "w-8 h-8 [&_svg]:w-4 [&_svg]:h-4",
42
+ lg: "w-9 h-9 [&_svg]:w-5 [&_svg]:h-5",
43
+ };
44
+ const titleSize: Record<Size, string> = { sm: "text-sm", md: "text-base", lg: "text-lg" };
45
+ const tokenFor = (item: TimelineItem) => variantToken[item.variant ?? variant];
46
+ </script>
47
+
48
+ <ol class="flex flex-col {cls}">
49
+ {#each items as item, i (i)}
50
+ {@const last = i === items.length - 1}
51
+ <li class="flex gap-3">
52
+ <!-- Marker + connecting line. -->
53
+ <div class="flex flex-col items-center">
54
+ {#if item.icon}
55
+ <span
56
+ class="inline-flex shrink-0 items-center justify-center rounded-full {responsiveClasses(
57
+ size,
58
+ iconBox,
59
+ )}"
60
+ style="background-color: color-mix(in srgb, var({tokenFor(item)}) 16%, transparent); color: color-mix(in srgb, var({tokenFor(item)}) var(--ui-tint-strong), var(--ui-color-text));"
61
+ aria-hidden="true"
62
+ >
63
+ {@render item.icon()}
64
+ </span>
65
+ {:else}
66
+ <span
67
+ class="mt-1.5 shrink-0 rounded-full {responsiveClasses(size, dotSize)}"
68
+ style="background-color: var({tokenFor(item)});"
69
+ aria-hidden="true"
70
+ ></span>
71
+ {/if}
72
+ {#if !last}
73
+ <span class="my-1 w-px flex-1 [background-color:var(--ui-border-color)]" aria-hidden="true"></span>
74
+ {/if}
75
+ </div>
76
+
77
+ <!-- Content. -->
78
+ <div class="min-w-0 flex-1 pb-6">
79
+ <div class="flex flex-wrap items-baseline gap-x-2">
80
+ <span class="font-medium [color:var(--ui-color-text)] {responsiveClasses(size, titleSize)}">
81
+ {item.title}
82
+ </span>
83
+ {#if item.time}
84
+ <span class="text-xs [color:color-mix(in_srgb,var(--ui-color-text)_50%,transparent)]">
85
+ {item.time}
86
+ </span>
87
+ {/if}
88
+ </div>
89
+ {#if item.description}
90
+ <p class="mt-0.5 text-sm [color:color-mix(in_srgb,var(--ui-color-text)_65%,transparent)]">
91
+ {item.description}
92
+ </p>
93
+ {/if}
94
+ {#if item.content}<div class="mt-2">{@render item.content()}</div>{/if}
95
+ </div>
96
+ </li>
97
+ {/each}
98
+ </ol>
@@ -0,0 +1,21 @@
1
+ import type { Snippet } from "svelte";
2
+ import type { Variant } from "../../types/variants.js";
3
+ export type TimelineItem = {
4
+ title: string;
5
+ time?: string;
6
+ description?: string;
7
+ icon?: Snippet;
8
+ variant?: Variant;
9
+ content?: Snippet;
10
+ };
11
+ import type { Size } from "../../types/sizes.js";
12
+ import { type Responsive } from "../../types/responsive.js";
13
+ type $$ComponentProps = {
14
+ items: TimelineItem[];
15
+ variant?: Variant;
16
+ size?: Responsive<Size>;
17
+ class?: string;
18
+ };
19
+ declare const Timeline: import("svelte").Component<$$ComponentProps, {}, "">;
20
+ type Timeline = ReturnType<typeof Timeline>;
21
+ export default Timeline;
@@ -0,0 +1,2 @@
1
+ export { default as Timeline } from "./Timeline.svelte";
2
+ export type { TimelineItem } from "./Timeline.svelte";
@@ -0,0 +1 @@
1
+ export { default as Timeline } from "./Timeline.svelte";
@@ -62,14 +62,19 @@
62
62
  : `color-mix(in srgb, var(${token}) var(--ui-tint-border-hover), transparent)`,
63
63
  );
64
64
 
65
+ // Vertical padding is kept small enough that a single token row (chip + 1px border box + padding)
66
+ // never exceeds the tier's `min-h`, so the field height is CONSTANT whether empty or holding tokens.
67
+ // Multi-row wrapping still grows the field; row spacing comes from the flex `gap`, not padding.
65
68
  const fieldSize: Record<Size, string> = {
66
- sm: "min-h-6 px-1.5 py-0.5 gap-1 text-xs",
67
- md: "min-h-8 px-2 py-1 gap-1.5 text-sm",
68
- lg: "min-h-10 px-2.5 py-1.5 gap-2 text-base",
69
+ sm: "min-h-6 px-1.5 py-0 gap-1 text-xs",
70
+ md: "min-h-8 px-2 py-[2px] gap-1.5 text-sm",
71
+ lg: "min-h-10 px-2.5 py-[2px] gap-2 text-base",
69
72
  };
70
- const chipSize = $derived<Size>(
71
- (typeof size === "string" ? size : "md") === "lg" ? "md" : "sm",
72
- );
73
+ const baseSize = $derived<Size>(typeof size === "string" ? size : "md");
74
+ const chipSize = $derived<Size>(baseSize === "lg" ? "md" : "sm");
75
+ // A default `sm` chip is 24px — exactly the sm FIELD height, so it can't fit inside the border box.
76
+ // Only in the sm field, cap the chip to 20px (still fits the min-h with room to center).
77
+ const chipHeightClass = $derived(baseSize === "sm" ? "!h-5" : "");
73
78
 
74
79
  let draft = $state("");
75
80
 
@@ -115,6 +120,7 @@
115
120
  text={v}
116
121
  variant={variant === "ghost" ? "secondary" : variant}
117
122
  size={chipSize}
123
+ class={chipHeightClass}
118
124
  removable
119
125
  {disabled}
120
126
  onremove={() => removeAt(i)}
@@ -0,0 +1,178 @@
1
+ <!--
2
+ /**
3
+ * Tree — a hierarchical, expandable tree view (ARIA `tree`). Data-driven nodes; expand/collapse +
4
+ * single-select with roving-tabindex keyboard nav (↑/↓ move, →/← expand/collapse, Enter/Space select).
5
+ * Shared `variant`/`size` axes, `--ui-*` tokens. See Tree.spec.md.
6
+ */
7
+ -->
8
+ <script lang="ts" module>
9
+ import type { Snippet } from "svelte";
10
+ export type TreeNode = {
11
+ label: string;
12
+ /** Stable key; falls back to the index path when omitted. */
13
+ id?: string;
14
+ icon?: Snippet;
15
+ children?: TreeNode[];
16
+ disabled?: boolean;
17
+ };
18
+ </script>
19
+
20
+ <script lang="ts">
21
+ import IconChevronRight from "~icons/mdi/chevron-right";
22
+ import type { Variant } from "../../types/variants.js";
23
+ import { variantToken } from "../../types/variants.js";
24
+ import type { Size } from "../../types/sizes.js";
25
+ import { responsiveClasses, type Responsive } from "../../types/responsive.js";
26
+
27
+ let {
28
+ items,
29
+ expanded = $bindable([]),
30
+ selected = $bindable(""),
31
+ onselect,
32
+ variant = "primary",
33
+ size = "md",
34
+ class: cls = "",
35
+ }: {
36
+ items: TreeNode[];
37
+ /** Keys of expanded nodes (bindable). */
38
+ expanded?: string[];
39
+ /** Key of the selected node (bindable). */
40
+ selected?: string;
41
+ onselect?: (node: TreeNode, key: string) => void;
42
+ variant?: Variant;
43
+ size?: Responsive<Size>;
44
+ class?: string;
45
+ } = $props();
46
+
47
+ const token = $derived(variantToken[variant]);
48
+ const rowH: Record<Size, string> = { sm: "h-7 text-xs", md: "h-8 text-sm", lg: "h-10 text-base" };
49
+ const iconCls: Record<Size, string> = {
50
+ sm: "[&_svg]:w-3.5 [&_svg]:h-3.5",
51
+ md: "[&_svg]:w-4 [&_svg]:h-4",
52
+ lg: "[&_svg]:w-5 [&_svg]:h-5",
53
+ };
54
+
55
+ type Flat = { key: string; node: TreeNode; level: number; hasChildren: boolean; open: boolean };
56
+ // Flattened list of VISIBLE nodes (children shown only under expanded parents) — drives rendering,
57
+ // roving tabindex, and arrow-key navigation.
58
+ const flat = $derived.by<Flat[]>(() => {
59
+ const out: Flat[] = [];
60
+ const walk = (nodes: TreeNode[], level: number, prefix: string) => {
61
+ nodes.forEach((node, i) => {
62
+ const key = node.id ?? `${prefix}${i}`;
63
+ const hasChildren = !!node.children?.length;
64
+ const open = expanded.includes(key);
65
+ out.push({ key, node, level, hasChildren, open });
66
+ if (hasChildren && open) walk(node.children!, level + 1, `${key}>`);
67
+ });
68
+ };
69
+ walk(items, 0, "");
70
+ return out;
71
+ });
72
+
73
+ let rowRefs = $state<Record<string, HTMLElement | undefined>>({});
74
+ let activeKey = $state("");
75
+ const tabKey = $derived(
76
+ flat.some((f) => f.key === activeKey) ? activeKey : (flat.find((f) => !f.node.disabled)?.key ?? ""),
77
+ );
78
+
79
+ const toggle = (key: string) =>
80
+ (expanded = expanded.includes(key) ? expanded.filter((k) => k !== key) : [...expanded, key]);
81
+ function selectNode(f: Flat) {
82
+ if (f.node.disabled) return;
83
+ selected = f.key;
84
+ onselect?.(f.node, f.key);
85
+ if (f.hasChildren) toggle(f.key);
86
+ }
87
+ const focusKey = (key: string) => {
88
+ activeKey = key;
89
+ rowRefs[key]?.focus();
90
+ };
91
+
92
+ function onKeydown(e: KeyboardEvent) {
93
+ const idx = flat.findIndex((f) => f.key === tabKey);
94
+ if (idx < 0) return;
95
+ const f = flat[idx];
96
+ switch (e.key) {
97
+ case "ArrowDown":
98
+ e.preventDefault();
99
+ if (idx < flat.length - 1) focusKey(flat[idx + 1].key);
100
+ break;
101
+ case "ArrowUp":
102
+ e.preventDefault();
103
+ if (idx > 0) focusKey(flat[idx - 1].key);
104
+ break;
105
+ case "ArrowRight":
106
+ e.preventDefault();
107
+ if (f.hasChildren && !f.open) toggle(f.key);
108
+ else if (f.hasChildren && flat[idx + 1]) focusKey(flat[idx + 1].key);
109
+ break;
110
+ case "ArrowLeft":
111
+ e.preventDefault();
112
+ if (f.hasChildren && f.open) toggle(f.key);
113
+ else {
114
+ // move to parent (previous node at a lower level)
115
+ for (let j = idx - 1; j >= 0; j--)
116
+ if (flat[j].level < f.level) {
117
+ focusKey(flat[j].key);
118
+ break;
119
+ }
120
+ }
121
+ break;
122
+ case "Home":
123
+ e.preventDefault();
124
+ focusKey(flat[0].key);
125
+ break;
126
+ case "End":
127
+ e.preventDefault();
128
+ focusKey(flat[flat.length - 1].key);
129
+ break;
130
+ case "Enter":
131
+ case " ":
132
+ e.preventDefault();
133
+ selectNode(f);
134
+ break;
135
+ }
136
+ }
137
+ </script>
138
+
139
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
140
+ <ul role="tree" class="flex flex-col {responsiveClasses(size, iconCls)} {cls}" onkeydown={onKeydown}>
141
+ {#each flat as f (f.key)}
142
+ {@const active = selected === f.key}
143
+ <li role="none" class="list-none">
144
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
145
+ <div
146
+ bind:this={rowRefs[f.key]}
147
+ role="treeitem"
148
+ aria-level={f.level + 1}
149
+ aria-selected={active}
150
+ aria-expanded={f.hasChildren ? f.open : undefined}
151
+ aria-disabled={f.node.disabled || undefined}
152
+ tabindex={f.key === tabKey ? 0 : -1}
153
+ onclick={() => selectNode(f)}
154
+ onfocus={() => (activeKey = f.key)}
155
+ style="padding-inline-start: {f.level * 1.25 + 0.5}rem; {active
156
+ ? `background-color: color-mix(in srgb, var(${token}) 16%, transparent);`
157
+ : ''}"
158
+ class="group flex items-center gap-1.5 rounded-[var(--ui-border-radius)] pr-2 transition-colors {responsiveClasses(
159
+ size,
160
+ rowH,
161
+ )} {f.node.disabled
162
+ ? 'opacity-50 cursor-not-allowed'
163
+ : 'cursor-pointer hover:[background-color:var(--ui-color-hover)]'} focus-visible:[outline:2px_solid_color-mix(in_srgb,var(--ui-color-text)_70%,transparent)] focus-visible:[outline-offset:-2px]"
164
+ >
165
+ {#if f.hasChildren}
166
+ <span
167
+ class="inline-flex shrink-0 items-center justify-center transition-transform duration-150 {f.open
168
+ ? 'rotate-90'
169
+ : ''}"><IconChevronRight /></span>
170
+ {:else}
171
+ <span class="inline-block w-4 shrink-0"></span>
172
+ {/if}
173
+ {#if f.node.icon}<span class="inline-flex shrink-0">{@render f.node.icon()}</span>{/if}
174
+ <span class="truncate [color:var(--ui-color-text)]">{f.node.label}</span>
175
+ </div>
176
+ </li>
177
+ {/each}
178
+ </ul>
@@ -0,0 +1,26 @@
1
+ import type { Snippet } from "svelte";
2
+ export type TreeNode = {
3
+ label: string;
4
+ /** Stable key; falls back to the index path when omitted. */
5
+ id?: string;
6
+ icon?: Snippet;
7
+ children?: TreeNode[];
8
+ disabled?: boolean;
9
+ };
10
+ import type { Variant } from "../../types/variants.js";
11
+ import type { Size } from "../../types/sizes.js";
12
+ import { type Responsive } from "../../types/responsive.js";
13
+ type $$ComponentProps = {
14
+ items: TreeNode[];
15
+ /** Keys of expanded nodes (bindable). */
16
+ expanded?: string[];
17
+ /** Key of the selected node (bindable). */
18
+ selected?: string;
19
+ onselect?: (node: TreeNode, key: string) => void;
20
+ variant?: Variant;
21
+ size?: Responsive<Size>;
22
+ class?: string;
23
+ };
24
+ declare const Tree: import("svelte").Component<$$ComponentProps, {}, "selected" | "expanded">;
25
+ type Tree = ReturnType<typeof Tree>;
26
+ export default Tree;
@@ -0,0 +1,2 @@
1
+ export { default as Tree } from "./Tree.svelte";
2
+ export type { TreeNode } from "./Tree.svelte";
@@ -0,0 +1 @@
1
+ export { default as Tree } from "./Tree.svelte";
package/dist/index.d.ts CHANGED
@@ -2,6 +2,8 @@ export { default as Alert } from "./components/Alert.svelte";
2
2
  export { default as Banner } from "./components/Banner.svelte";
3
3
  export { default as Badge } from "./components/Badge/Badge.svelte";
4
4
  export { Chip } from "./components/Chip/index.js";
5
+ export { Kbd } from "./components/Kbd/index.js";
6
+ export { EmptyState } from "./components/EmptyState/index.js";
5
7
  export { Avatar, AvatarGroup } from "./components/Avatar/index.js";
6
8
  export type { AvatarShape, AvatarStatus, AvatarItem } from "./components/Avatar/index.js";
7
9
  export { default as Button } from "./components/Button.svelte";
@@ -10,6 +12,9 @@ export { default as Card } from "./components/Card.svelte";
10
12
  export { default as CardHeader } from "./components/CardHeader.svelte";
11
13
  export { default as CardBody } from "./components/CardBody.svelte";
12
14
  export { default as CardFooter } from "./components/CardFooter.svelte";
15
+ export { Stat } from "./components/Stat/index.js";
16
+ export { Timeline } from "./components/Timeline/index.js";
17
+ export type { TimelineItem } from "./components/Timeline/index.js";
13
18
  export { default as Toaster } from "./components/Toast/Toaster.svelte";
14
19
  export { toast, getToasts, dismissToast, type ToastVariant, type ToastItem, } from "./components/Toast/toast.svelte.js";
15
20
  export { default as Dialog } from "./components/Dialog.svelte";
@@ -38,13 +43,16 @@ export { default as DatePicker } from "./components/DatePicker.svelte";
38
43
  export { default as TimeSpinner } from "./components/TimeSpinner.svelte";
39
44
  export { default as TimePicker } from "./components/TimePicker.svelte";
40
45
  export { default as TimeRangeInput } from "./components/TimeRangeInput.svelte";
41
- export { DateRangePicker } from "./components/DateRangePicker/index.js";
42
46
  export { FileUpload } from "./components/FileUpload/index.js";
43
- export type { RangePreset } from "./components/DateRangePicker/index.js";
44
47
  export { default as ThemeSelector } from "./components/ThemeSelector.svelte";
45
48
  export { default as ThemeToggle } from "./components/ThemeToggle.svelte";
46
49
  export { default as Popup } from "./components/Popup.svelte";
47
50
  export { default as Menu } from "./components/Menu.svelte";
51
+ export { ContextMenu } from "./components/ContextMenu/index.js";
52
+ export { CommandPalette } from "./components/CommandPalette/index.js";
53
+ export type { Command } from "./components/CommandPalette/index.js";
54
+ export { Tree } from "./components/Tree/index.js";
55
+ export type { TreeNode } from "./components/Tree/index.js";
48
56
  export type { MenuItem } from "./data/table/types.js";
49
57
  export { ButtonGroup } from "./components/ButtonGroup/index.js";
50
58
  export type { ButtonGroupItem, ButtonGroupSelect } from "./components/ButtonGroup/index.js";
@@ -54,6 +62,9 @@ export { default as Radio } from "./components/Radio.svelte";
54
62
  export { default as FieldGroup } from "./components/FieldGroup.svelte";
55
63
  export { default as Grid } from "./components/Grid.svelte";
56
64
  export { Divider } from "./components/Divider/index.js";
65
+ export { ScrollArea } from "./components/ScrollArea/index.js";
66
+ export { Splitter } from "./components/Splitter/index.js";
67
+ export { Carousel } from "./components/Carousel/index.js";
57
68
  export type { Columns, Gap } from "./components/field-group.js";
58
69
  export { Stepper } from "./components/Stepper/index.js";
59
70
  export type { StepItem } from "./components/Stepper/index.js";
package/dist/index.js CHANGED
@@ -3,6 +3,8 @@ export { default as Alert } from "./components/Alert.svelte";
3
3
  export { default as Banner } from "./components/Banner.svelte";
4
4
  export { default as Badge } from "./components/Badge/Badge.svelte";
5
5
  export { Chip } from "./components/Chip/index.js";
6
+ export { Kbd } from "./components/Kbd/index.js";
7
+ export { EmptyState } from "./components/EmptyState/index.js";
6
8
  export { Avatar, AvatarGroup } from "./components/Avatar/index.js";
7
9
  export { default as Button } from "./components/Button.svelte";
8
10
  export { default as ButtonDropdown } from "./components/ButtonDropdown.svelte";
@@ -10,6 +12,8 @@ export { default as Card } from "./components/Card.svelte";
10
12
  export { default as CardHeader } from "./components/CardHeader.svelte";
11
13
  export { default as CardBody } from "./components/CardBody.svelte";
12
14
  export { default as CardFooter } from "./components/CardFooter.svelte";
15
+ export { Stat } from "./components/Stat/index.js";
16
+ export { Timeline } from "./components/Timeline/index.js";
13
17
  // Toast notifications — `toast.*` pushes from anywhere; mount <Toaster /> once at the app root.
14
18
  export { default as Toaster } from "./components/Toast/Toaster.svelte";
15
19
  export { toast, getToasts, dismissToast, } from "./components/Toast/toast.svelte.js";
@@ -40,7 +44,6 @@ export { default as DatePicker } from "./components/DatePicker.svelte";
40
44
  export { default as TimeSpinner } from "./components/TimeSpinner.svelte";
41
45
  export { default as TimePicker } from "./components/TimePicker.svelte";
42
46
  export { default as TimeRangeInput } from "./components/TimeRangeInput.svelte";
43
- export { DateRangePicker } from "./components/DateRangePicker/index.js";
44
47
  export { FileUpload } from "./components/FileUpload/index.js";
45
48
  // Theme Components
46
49
  export { default as ThemeSelector } from "./components/ThemeSelector.svelte";
@@ -48,6 +51,9 @@ export { default as ThemeToggle } from "./components/ThemeToggle.svelte";
48
51
  // Utility Components
49
52
  export { default as Popup } from "./components/Popup.svelte";
50
53
  export { default as Menu } from "./components/Menu.svelte";
54
+ export { ContextMenu } from "./components/ContextMenu/index.js";
55
+ export { CommandPalette } from "./components/CommandPalette/index.js";
56
+ export { Tree } from "./components/Tree/index.js";
51
57
  export { ButtonGroup } from "./components/ButtonGroup/index.js";
52
58
  export { default as Checkbox } from "./components/Checkbox.svelte";
53
59
  export { default as Toggle } from "./components/Toggle.svelte";
@@ -55,6 +61,9 @@ export { default as Radio } from "./components/Radio.svelte";
55
61
  export { default as FieldGroup } from "./components/FieldGroup.svelte";
56
62
  export { default as Grid } from "./components/Grid.svelte";
57
63
  export { Divider } from "./components/Divider/index.js";
64
+ export { ScrollArea } from "./components/ScrollArea/index.js";
65
+ export { Splitter } from "./components/Splitter/index.js";
66
+ export { Carousel } from "./components/Carousel/index.js";
58
67
  export { Stepper } from "./components/Stepper/index.js";
59
68
  export { default as Disclosure } from "./components/Disclosure.svelte";
60
69
  export { default as Accordion } from "./components/Accordion.svelte";
@@ -122,20 +122,6 @@ Drag-and-drop + click file field with thumbnails, remove, and optional progress.
122
122
  `<input type=file>`; labelled remove ×s
123
123
  - **Composition**: `Button` + `Progress`; FieldChrome for label/error
124
124
 
125
- ### DateRangePicker
126
-
127
- Start–end date range with a presets rail; composes `Calendar mode="range"` + `Input` + `Popup`.
128
-
129
- - **Location**: `src/lib/components/DateRangePicker/DateRangePicker.svelte`
130
- - **Axes**: `variant`, `size`
131
- - **Props**: `start`/`end` (bindable ISO), `min`/`max`, `presets` (`RangePreset[]`; default set, `[]`
132
- hides the rail), `format`, `placeholder`, plus FieldChrome (`label`/`aside`/`error`/`description`/
133
- `id`/`required`), `class`
134
- - **Behavior**: readonly `Input` trigger shows the formatted range; the popup pairs a presets column
135
- (`Button` ghost) with the range calendar; a preset or the second calendar pick closes it
136
- - **Note**: `DatePicker range` covers a bare range (object value, no presets); this is the
137
- presets-first, `bind:start`/`bind:end` variant
138
-
139
125
  ### Slider
140
126
 
141
127
  Draggable range input — a single value or a `[min, max]` pair.
@@ -285,10 +271,15 @@ independent. Propagates `size`/`variant`/`disabled` to children.
285
271
 
286
272
  ### Rating
287
273
 
288
- Read-only star-rating display (5-star, from a 10-point average).
274
+ Star rating in two modes: a read-only display, or an interactive picker.
289
275
 
290
276
  - **Location**: `src/lib/components/Rating.svelte`
291
- - **Props**: `average` (number), `count` (number)
277
+ - **Display mode** (default): `average` (0–10 score, shown as 5 stars = average/2), `count` (votes).
278
+ - **Interactive mode**: `interactive` + `value` (bindable, 0..`max`). Click a star, hover to preview,
279
+ or focus and use ←/→ · ↑/↓ · Home/End. `allowHalf` enables half-star selection; `onchange`,
280
+ `readonly`, `disabled`. Rendered as a `role="slider"` (same a11y pattern as `Slider`).
281
+ - **Shared**: `variant` (filled-star tint; default gold), `size`, `showValue` (numeric readout —
282
+ defaults on in display mode, off interactive), `label` (slider a11y label).
292
283
 
293
284
  ## Specialized Input Components
294
285
 
@@ -399,13 +390,19 @@ dialog (operators, casts, dynamic `now()` dates, and a worked examples table). T
399
390
  Standalone page navigator — the reusable extraction of `DataTable`'s footer pager.
400
391
 
401
392
  - **Location**: `src/lib/components/Pagination/Pagination.svelte`
402
- - **Axes**: `variant` (active page fill), `size`
393
+ - **Axes**: `variant` (tints the band **surface + button hover**, like DataTable's footer — NOT a
394
+ filled button; omit for a neutral band, `ghost` for a transparent one), `size` (scales band, Select,
395
+ buttons, and the page field)
403
396
  - **Props**: `page` (bindable), `total`, `perPage` (bindable), `perPageOptions` (→ rows-per-page
404
- `Select`), `siblingCount`/`boundaryCount`, `showEdges` (first/last), `showTotal`, `disabled`, `class`
405
- - **Behavior**: numbered buttons with ellipsis for long ranges (active = filled, rest `ghost`);
406
- prev/next/first/last disable at the ends; choosing `perPage` resets to page 1; `page` clamps to range
407
- - **Accessibility**: `<nav aria-label="Pagination">`; active page `aria-current="page"`
408
- - **Composition**: `Button` + `Select` only (no raw `<button>`)
397
+ `Select` on the left), `showEdges` (first/last, default **true**), `showTotal` (range text, default
398
+ **true**), `borderless` (drop the band border/surface), `disabled`, `class`
399
+ - **Layout**: a tinted, bordered band (matches `DataTable`'s footer) per-page `Select` left; range
400
+ text + a compact `First · Prev · [page]/N · Next · Last` stepper right. The page indicator is an
401
+ **editable `NumberInput`** (min 1, max N, clamped) — type a page + Enter to jump; ↑/↓ step it.
402
+ - **Behavior**: prev/next/first/last disable at the ends; choosing `perPage` resets to page 1; `page`
403
+ clamps to range; jump commits on change (Enter/blur)
404
+ - **Accessibility**: `<nav aria-label="Pagination">`; the jump field is `aria-label="Page number"`
405
+ - **Composition**: `Button` + `Select` + `NumberInput` only (no raw `<button>`/`<input>`)
409
406
 
410
407
  ## Overlays
411
408
 
@@ -617,3 +614,109 @@ src/lib/components/ComponentName/
617
614
  ```
618
615
  src/lib/components/ComponentName.svelte
619
616
  ```
617
+
618
+ ### Kbd
619
+
620
+ Keyboard-key display (`<kbd>` keycap).
621
+
622
+ - **Location**: `src/lib/components/Kbd/Kbd.svelte`
623
+ - **Axes**: `size`
624
+ - **Props**: `keys` (`string[]` combo, joined with `+`), `text`/`children` (single key), `class`
625
+ - **Theming**: `--ui-color-surface` fill + `--ui-border-color` border with a shallow inset bottom shadow
626
+
627
+ ### EmptyState
628
+
629
+ Zero-data / empty placeholder — icon + title + description + optional action.
630
+
631
+ - **Location**: `src/lib/components/EmptyState/EmptyState.svelte`
632
+ - **Axes**: `variant` (icon-badge accent), `size`
633
+ - **Props**: `title`, `description`, `icon` (snippet; default inbox), `action` (CTA snippet),
634
+ `children`, `class`
635
+
636
+ ### Stat
637
+
638
+ Metric/KPI card — label, value, delta, icon. Built on `Card`.
639
+
640
+ - **Location**: `src/lib/components/Stat/Stat.svelte`
641
+ - **Axes**: `variant` (icon-badge accent), `size` (value scale)
642
+ - **Props**: `label`, `value`, `delta` (number → arrow; string → as-is), `deltaLabel`, `invertDelta`
643
+ (negative is good), `icon` (snippet), `class`
644
+ - **Theming**: rising delta success / falling danger / flat muted; icon badge tints from the variant
645
+
646
+ ### Timeline
647
+
648
+ Vertical event sequence — marker + connecting line + title/time/description.
649
+
650
+ - **Location**: `src/lib/components/Timeline/Timeline.svelte`
651
+ - **Axes**: `variant` (default marker accent; per-item override), `size`
652
+ - **Props**: `items` (`TimelineItem[]` = `{ title, time?, description?, icon?, variant?, content? }`), `class`
653
+ - **Theming**: icon marker = variant token @16% badge; dot = solid token; connector `--ui-border-color`
654
+
655
+ ### ContextMenu
656
+
657
+ Right-click menu over any content; opens `Menu` at the pointer.
658
+
659
+ - **Location**: `src/lib/components/ContextMenu/ContextMenu.svelte`
660
+ - **Props**: `items` (`MenuItem[]`), `size`, `variant`, `disabled` (leaves the native menu), `class`,
661
+ `children` (the right-clickable content)
662
+ - **Composition**: `Menu` anchored to a 0-size point at the pointer; flips/shifts at edges
663
+
664
+ ### Tree
665
+
666
+ Hierarchical, expandable tree view (ARIA `tree`).
667
+
668
+ - **Location**: `src/lib/components/Tree/Tree.svelte`
669
+ - **Axes**: `variant` (selected-row tint), `size`
670
+ - **Props**: `items` (`TreeNode[]` = `{ label, id?, icon?, children?, disabled? }`), `expanded`
671
+ (bindable `string[]`), `selected` (bindable key), `onselect`, `class`
672
+ - **Keyboard**: roving tabindex — ↑/↓ move, → expand/into child, ← collapse/to parent, Enter/Space
673
+ select, Home/End
674
+ - **Accessibility**: `role="tree"`/`treeitem` with `aria-level`/`-expanded`/`-selected`/`-disabled`
675
+
676
+ ### ScrollArea
677
+
678
+ Scroll container with a themed thin scrollbar.
679
+
680
+ - **Location**: `src/lib/components/ScrollArea/ScrollArea.svelte`
681
+ - **Props**: `orientation` (`vertical`/`horizontal`/`both`), `maxHeight`/`height` (CSS length), `variant`
682
+ (thumb accent; default neutral text-tint), `class`, `children`
683
+ - **Theming**: thumb = accent @28% (hover @45%), transparent track, rounded; Firefox `scrollbar-color`/
684
+ `-width:thin` + WebKit `::-webkit-scrollbar` both styled
685
+
686
+ ### Splitter
687
+
688
+ Two resizable panes with a draggable divider.
689
+
690
+ - **Location**: `src/lib/components/Splitter/Splitter.svelte`
691
+ - **Props**: `start`/`end` (snippets), `orientation` (`horizontal`/`vertical`), `size` (bindable %),
692
+ `min`/`max`/`step`, `variant` (handle accent), `disabled`, `class`
693
+ - **Interaction**: drag the handle or focus it and use ←/→ (or ↑/↓) + Home/End
694
+ - **Accessibility**: WAI-ARIA window-splitter — `role="separator"`, `aria-orientation`,
695
+ `aria-valuenow`/`min`/`max`, focusable
696
+
697
+ ### Carousel
698
+
699
+ Sliding, one-at-a-time viewport with controls + dot indicators.
700
+
701
+ - **Location**: `src/lib/components/Carousel/Carousel.svelte`
702
+ - **Props**: `items` (`T[]`) + `slide` (`Snippet<[item, index]>`), `index` (bindable), `loop`,
703
+ `autoplay` (ms; pauses on hover/focus), `controls`, `indicators`, `variant`, `ariaLabel`, `class`
704
+ - **Interaction**: prev/next `Button`s, clickable dots, ←/→ keys; `loop` wraps or clamps at ends
705
+ - **Accessibility**: `role="region"` carousel + per-slide `role="group"` (`aria-label="N of M"`, hidden
706
+ when off); dots are `<button>`s with `aria-current`
707
+
708
+ ### CommandPalette
709
+
710
+ ⌘K/Ctrl+K fuzzy command launcher — a top-centered modal overlay with a search field + filtered,
711
+ grouped, keyboard-navigable command list.
712
+
713
+ - **Location**: `src/lib/components/CommandPalette/CommandPalette.svelte`
714
+ - **Props**: `open` (bindable), `commands` (`Command[]` = `{ label, keywords?, group?, icon?,
715
+ shortcut?, disabled?, onrun }`), `hotkey` (default `"mod+k"`; `mod` = ⌘ on macOS / Ctrl elsewhere;
716
+ `""` disables), `placeholder`, `empty` (no-match text), `onopen`
717
+ - **Interaction**: hotkey toggles open; typing filters (label + keywords); ↑/↓ move (wrapping), Enter
718
+ runs the highlighted command, Esc / backdrop close; shortcuts render as `Kbd`
719
+ - **Behavior**: portals to `<body>`; on open resets query/highlight, locks scroll, focuses input; on
720
+ close restores focus. Disabled commands are inert.
721
+ - **Composition**: `Input` (search) + `Kbd` (shortcuts); `role="dialog"`/`combobox`/`listbox` wiring
722
+ - **Exports**: `CommandPalette` + the `Command` type