@spaethtech/svelte-ui 0.14.1-dev.65.3b37f4b → 0.15.1-dev.67.fbf3875

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 (46) hide show
  1. package/.claude/skills/svelte-ui/SKILL.md +5 -5
  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/CommandPalette/CommandPalette.svelte +203 -0
  7. package/dist/components/CommandPalette/CommandPalette.svelte.d.ts +23 -0
  8. package/dist/components/CommandPalette/index.d.ts +2 -0
  9. package/dist/components/CommandPalette/index.js +1 -0
  10. package/dist/components/ContextMenu/ContextMenu.svelte +58 -0
  11. package/dist/components/ContextMenu/ContextMenu.svelte.d.ts +16 -0
  12. package/dist/components/ContextMenu/index.d.ts +1 -0
  13. package/dist/components/ContextMenu/index.js +1 -0
  14. package/dist/components/EmptyState/EmptyState.svelte +68 -0
  15. package/dist/components/EmptyState/EmptyState.svelte.d.ts +19 -0
  16. package/dist/components/EmptyState/index.d.ts +1 -0
  17. package/dist/components/EmptyState/index.js +1 -0
  18. package/dist/components/Kbd/Kbd.svelte +53 -0
  19. package/dist/components/Kbd/Kbd.svelte.d.ts +15 -0
  20. package/dist/components/Kbd/index.d.ts +1 -0
  21. package/dist/components/Kbd/index.js +1 -0
  22. package/dist/components/ScrollArea/ScrollArea.svelte +71 -0
  23. package/dist/components/ScrollArea/ScrollArea.svelte.d.ts +15 -0
  24. package/dist/components/ScrollArea/index.d.ts +1 -0
  25. package/dist/components/ScrollArea/index.js +1 -0
  26. package/dist/components/Splitter/Splitter.svelte +127 -0
  27. package/dist/components/Splitter/Splitter.svelte.d.ts +18 -0
  28. package/dist/components/Splitter/index.d.ts +1 -0
  29. package/dist/components/Splitter/index.js +1 -0
  30. package/dist/components/Stat/Stat.svelte +95 -0
  31. package/dist/components/Stat/Stat.svelte.d.ts +21 -0
  32. package/dist/components/Stat/index.d.ts +1 -0
  33. package/dist/components/Stat/index.js +1 -0
  34. package/dist/components/Timeline/Timeline.svelte +98 -0
  35. package/dist/components/Timeline/Timeline.svelte.d.ts +21 -0
  36. package/dist/components/Timeline/index.d.ts +2 -0
  37. package/dist/components/Timeline/index.js +1 -0
  38. package/dist/components/Tree/Tree.svelte +178 -0
  39. package/dist/components/Tree/Tree.svelte.d.ts +26 -0
  40. package/dist/components/Tree/index.d.ts +2 -0
  41. package/dist/components/Tree/index.js +1 -0
  42. package/dist/index.d.ts +13 -0
  43. package/dist/index.js +10 -0
  44. package/docs/components.md +106 -0
  45. package/docs/usage.md +166 -0
  46. package/package.json +1 -1
@@ -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";
@@ -45,6 +50,11 @@ export { default as ThemeSelector } from "./components/ThemeSelector.svelte";
45
50
  export { default as ThemeToggle } from "./components/ThemeToggle.svelte";
46
51
  export { default as Popup } from "./components/Popup.svelte";
47
52
  export { default as Menu } from "./components/Menu.svelte";
53
+ export { ContextMenu } from "./components/ContextMenu/index.js";
54
+ export { CommandPalette } from "./components/CommandPalette/index.js";
55
+ export type { Command } from "./components/CommandPalette/index.js";
56
+ export { Tree } from "./components/Tree/index.js";
57
+ export type { TreeNode } from "./components/Tree/index.js";
48
58
  export type { MenuItem } from "./data/table/types.js";
49
59
  export { ButtonGroup } from "./components/ButtonGroup/index.js";
50
60
  export type { ButtonGroupItem, ButtonGroupSelect } from "./components/ButtonGroup/index.js";
@@ -54,6 +64,9 @@ export { default as Radio } from "./components/Radio.svelte";
54
64
  export { default as FieldGroup } from "./components/FieldGroup.svelte";
55
65
  export { default as Grid } from "./components/Grid.svelte";
56
66
  export { Divider } from "./components/Divider/index.js";
67
+ export { ScrollArea } from "./components/ScrollArea/index.js";
68
+ export { Splitter } from "./components/Splitter/index.js";
69
+ export { Carousel } from "./components/Carousel/index.js";
57
70
  export type { Columns, Gap } from "./components/field-group.js";
58
71
  export { Stepper } from "./components/Stepper/index.js";
59
72
  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";
@@ -48,6 +52,9 @@ export { default as ThemeToggle } from "./components/ThemeToggle.svelte";
48
52
  // Utility Components
49
53
  export { default as Popup } from "./components/Popup.svelte";
50
54
  export { default as Menu } from "./components/Menu.svelte";
55
+ export { ContextMenu } from "./components/ContextMenu/index.js";
56
+ export { CommandPalette } from "./components/CommandPalette/index.js";
57
+ export { Tree } from "./components/Tree/index.js";
51
58
  export { ButtonGroup } from "./components/ButtonGroup/index.js";
52
59
  export { default as Checkbox } from "./components/Checkbox.svelte";
53
60
  export { default as Toggle } from "./components/Toggle.svelte";
@@ -55,6 +62,9 @@ export { default as Radio } from "./components/Radio.svelte";
55
62
  export { default as FieldGroup } from "./components/FieldGroup.svelte";
56
63
  export { default as Grid } from "./components/Grid.svelte";
57
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";
58
68
  export { Stepper } from "./components/Stepper/index.js";
59
69
  export { default as Disclosure } from "./components/Disclosure.svelte";
60
70
  export { default as Accordion } from "./components/Accordion.svelte";
@@ -617,3 +617,109 @@ src/lib/components/ComponentName/
617
617
  ```
618
618
  src/lib/components/ComponentName.svelte
619
619
  ```
620
+
621
+ ### Kbd
622
+
623
+ Keyboard-key display (`<kbd>` keycap).
624
+
625
+ - **Location**: `src/lib/components/Kbd/Kbd.svelte`
626
+ - **Axes**: `size`
627
+ - **Props**: `keys` (`string[]` combo, joined with `+`), `text`/`children` (single key), `class`
628
+ - **Theming**: `--ui-color-surface` fill + `--ui-border-color` border with a shallow inset bottom shadow
629
+
630
+ ### EmptyState
631
+
632
+ Zero-data / empty placeholder — icon + title + description + optional action.
633
+
634
+ - **Location**: `src/lib/components/EmptyState/EmptyState.svelte`
635
+ - **Axes**: `variant` (icon-badge accent), `size`
636
+ - **Props**: `title`, `description`, `icon` (snippet; default inbox), `action` (CTA snippet),
637
+ `children`, `class`
638
+
639
+ ### Stat
640
+
641
+ Metric/KPI card — label, value, delta, icon. Built on `Card`.
642
+
643
+ - **Location**: `src/lib/components/Stat/Stat.svelte`
644
+ - **Axes**: `variant` (icon-badge accent), `size` (value scale)
645
+ - **Props**: `label`, `value`, `delta` (number → arrow; string → as-is), `deltaLabel`, `invertDelta`
646
+ (negative is good), `icon` (snippet), `class`
647
+ - **Theming**: rising delta success / falling danger / flat muted; icon badge tints from the variant
648
+
649
+ ### Timeline
650
+
651
+ Vertical event sequence — marker + connecting line + title/time/description.
652
+
653
+ - **Location**: `src/lib/components/Timeline/Timeline.svelte`
654
+ - **Axes**: `variant` (default marker accent; per-item override), `size`
655
+ - **Props**: `items` (`TimelineItem[]` = `{ title, time?, description?, icon?, variant?, content? }`), `class`
656
+ - **Theming**: icon marker = variant token @16% badge; dot = solid token; connector `--ui-border-color`
657
+
658
+ ### ContextMenu
659
+
660
+ Right-click menu over any content; opens `Menu` at the pointer.
661
+
662
+ - **Location**: `src/lib/components/ContextMenu/ContextMenu.svelte`
663
+ - **Props**: `items` (`MenuItem[]`), `size`, `variant`, `disabled` (leaves the native menu), `class`,
664
+ `children` (the right-clickable content)
665
+ - **Composition**: `Menu` anchored to a 0-size point at the pointer; flips/shifts at edges
666
+
667
+ ### Tree
668
+
669
+ Hierarchical, expandable tree view (ARIA `tree`).
670
+
671
+ - **Location**: `src/lib/components/Tree/Tree.svelte`
672
+ - **Axes**: `variant` (selected-row tint), `size`
673
+ - **Props**: `items` (`TreeNode[]` = `{ label, id?, icon?, children?, disabled? }`), `expanded`
674
+ (bindable `string[]`), `selected` (bindable key), `onselect`, `class`
675
+ - **Keyboard**: roving tabindex — ↑/↓ move, → expand/into child, ← collapse/to parent, Enter/Space
676
+ select, Home/End
677
+ - **Accessibility**: `role="tree"`/`treeitem` with `aria-level`/`-expanded`/`-selected`/`-disabled`
678
+
679
+ ### ScrollArea
680
+
681
+ Scroll container with a themed thin scrollbar.
682
+
683
+ - **Location**: `src/lib/components/ScrollArea/ScrollArea.svelte`
684
+ - **Props**: `orientation` (`vertical`/`horizontal`/`both`), `maxHeight`/`height` (CSS length), `variant`
685
+ (thumb accent; default neutral text-tint), `class`, `children`
686
+ - **Theming**: thumb = accent @28% (hover @45%), transparent track, rounded; Firefox `scrollbar-color`/
687
+ `-width:thin` + WebKit `::-webkit-scrollbar` both styled
688
+
689
+ ### Splitter
690
+
691
+ Two resizable panes with a draggable divider.
692
+
693
+ - **Location**: `src/lib/components/Splitter/Splitter.svelte`
694
+ - **Props**: `start`/`end` (snippets), `orientation` (`horizontal`/`vertical`), `size` (bindable %),
695
+ `min`/`max`/`step`, `variant` (handle accent), `disabled`, `class`
696
+ - **Interaction**: drag the handle or focus it and use ←/→ (or ↑/↓) + Home/End
697
+ - **Accessibility**: WAI-ARIA window-splitter — `role="separator"`, `aria-orientation`,
698
+ `aria-valuenow`/`min`/`max`, focusable
699
+
700
+ ### Carousel
701
+
702
+ Sliding, one-at-a-time viewport with controls + dot indicators.
703
+
704
+ - **Location**: `src/lib/components/Carousel/Carousel.svelte`
705
+ - **Props**: `items` (`T[]`) + `slide` (`Snippet<[item, index]>`), `index` (bindable), `loop`,
706
+ `autoplay` (ms; pauses on hover/focus), `controls`, `indicators`, `variant`, `ariaLabel`, `class`
707
+ - **Interaction**: prev/next `Button`s, clickable dots, ←/→ keys; `loop` wraps or clamps at ends
708
+ - **Accessibility**: `role="region"` carousel + per-slide `role="group"` (`aria-label="N of M"`, hidden
709
+ when off); dots are `<button>`s with `aria-current`
710
+
711
+ ### CommandPalette
712
+
713
+ ⌘K/Ctrl+K fuzzy command launcher — a top-centered modal overlay with a search field + filtered,
714
+ grouped, keyboard-navigable command list.
715
+
716
+ - **Location**: `src/lib/components/CommandPalette/CommandPalette.svelte`
717
+ - **Props**: `open` (bindable), `commands` (`Command[]` = `{ label, keywords?, group?, icon?,
718
+ shortcut?, disabled?, onrun }`), `hotkey` (default `"mod+k"`; `mod` = ⌘ on macOS / Ctrl elsewhere;
719
+ `""` disables), `placeholder`, `empty` (no-match text), `onopen`
720
+ - **Interaction**: hotkey toggles open; typing filters (label + keywords); ↑/↓ move (wrapping), Enter
721
+ runs the highlighted command, Esc / backdrop close; shortcuts render as `Kbd`
722
+ - **Behavior**: portals to `<body>`; on open resets query/highlight, locks scroll, focuses input; on
723
+ close restores focus. Disabled commands are inert.
724
+ - **Composition**: `Input` (search) + `Kbd` (shortcuts); `role="dialog"`/`combobox`/`listbox` wiring
725
+ - **Exports**: `CommandPalette` + the `Command` type
package/docs/usage.md CHANGED
@@ -776,6 +776,172 @@ A navigation trail; the last item is the current page. `maxItems` collapses the
776
776
  <Breadcrumbs items={trail} maxItems={4} separator="/" />
777
777
  ```
778
778
 
779
+ ### Tree
780
+
781
+ A hierarchical, expandable tree.
782
+
783
+ ```svelte
784
+ <script>
785
+ import { Tree } from "@spaethtech/svelte-ui";
786
+ const items = [
787
+ { label: "src", children: [{ label: "index.ts" }, { label: "app.css" }] },
788
+ { label: "package.json" },
789
+ ];
790
+ let expanded = $state(["0"]); // key = id or index path
791
+ let selected = $state("");
792
+ </script>
793
+
794
+ <Tree {items} bind:expanded bind:selected onselect={(n) => console.log(n.label)} />
795
+ ```
796
+
797
+ ### ContextMenu
798
+
799
+ A right-click menu over any content.
800
+
801
+ ```svelte
802
+ <script>
803
+ import { ContextMenu } from "@spaethtech/svelte-ui";
804
+ const items = [
805
+ { label: "Open", onclick: () => {} },
806
+ { label: "Delete", danger: true, onclick: () => {} },
807
+ ];
808
+ </script>
809
+
810
+ <ContextMenu {items}>
811
+ <div>Right-click me</div>
812
+ </ContextMenu>
813
+ ```
814
+
815
+ ### Timeline
816
+
817
+ A vertical event sequence.
818
+
819
+ ```svelte
820
+ <script>
821
+ import { Timeline } from "@spaethtech/svelte-ui";
822
+ const items = [
823
+ { title: "Order placed", time: "Mon 9:14 AM", variant: "success" },
824
+ { title: "Shipped", time: "Tue 8:05 AM", variant: "primary" },
825
+ { title: "Out for delivery", time: "Wed" },
826
+ ];
827
+ </script>
828
+
829
+ <Timeline {items} />
830
+ ```
831
+
832
+ ### Carousel
833
+
834
+ A sliding viewport with controls + dots.
835
+
836
+ ```svelte
837
+ <script>
838
+ import { Carousel } from "@spaethtech/svelte-ui";
839
+ const photos = [{ url: "/1.jpg" }, { url: "/2.jpg" }];
840
+ let index = $state(0);
841
+ </script>
842
+
843
+ <Carousel items={photos} bind:index autoplay={4000}>
844
+ {#snippet slide(photo, i)}
845
+ <img src={photo.url} class="w-full h-64 object-cover" />
846
+ {/snippet}
847
+ </Carousel>
848
+ ```
849
+
850
+ ### CommandPalette
851
+
852
+ A ⌘K/Ctrl+K fuzzy command launcher.
853
+
854
+ ```svelte
855
+ <script>
856
+ import { CommandPalette, type Command } from "@spaethtech/svelte-ui";
857
+ let open = $state(false);
858
+ const commands: Command[] = [
859
+ { label: "New file", group: "Actions", shortcut: ["⌘", "N"], onrun: () => create() },
860
+ { label: "Open settings", group: "Actions", onrun: () => goto("/settings") },
861
+ { label: "Toggle theme", keywords: ["dark", "light"], onrun: () => toggleTheme() },
862
+ ];
863
+ </script>
864
+
865
+ <!-- ⌘K / Ctrl+K opens it anywhere; or bind:open from your own trigger -->
866
+ <CommandPalette bind:open {commands} />
867
+ ```
868
+
869
+ ### Splitter
870
+
871
+ Two resizable panes with a draggable divider.
872
+
873
+ ```svelte
874
+ <script>
875
+ import { Splitter } from "@spaethtech/svelte-ui";
876
+ let size = $state(35);
877
+ </script>
878
+
879
+ <Splitter bind:size class="h-64">
880
+ {#snippet start()}<aside>Sidebar</aside>{/snippet}
881
+ {#snippet end()}<main>Content</main>{/snippet}
882
+ </Splitter>
883
+ ```
884
+
885
+ ### ScrollArea
886
+
887
+ A themed scroll container.
888
+
889
+ ```svelte
890
+ <script>
891
+ import { ScrollArea } from "@spaethtech/svelte-ui";
892
+ </script>
893
+
894
+ <ScrollArea maxHeight="16rem" variant="primary">
895
+ <!-- long content scrolls with a themed thin scrollbar -->
896
+ </ScrollArea>
897
+
898
+ <ScrollArea orientation="horizontal">…</ScrollArea>
899
+ ```
900
+
901
+ ### Stat
902
+
903
+ Metric/KPI cards; lay several in a `Grid`.
904
+
905
+ ```svelte
906
+ <script>
907
+ import { Stat, Grid } from "@spaethtech/svelte-ui";
908
+ </script>
909
+
910
+ <Grid columns={{ base: 1, sm: 3 }} gap={4}>
911
+ <Stat label="Revenue" value="$48,120" delta="+12.4%" deltaLabel="vs last month" variant="success" />
912
+ <Stat label="Active users" value="8,241" delta={-3.1} deltaLabel="vs last week" />
913
+ <Stat label="Error rate" value="0.8%" delta={-0.2} invertDelta />
914
+ </Grid>
915
+ ```
916
+
917
+ ### EmptyState
918
+
919
+ A zero-data placeholder with an optional action.
920
+
921
+ ```svelte
922
+ <script>
923
+ import { EmptyState, Button } from "@spaethtech/svelte-ui";
924
+ </script>
925
+
926
+ <EmptyState title="No projects yet" description="Create your first project to get started.">
927
+ {#snippet action()}<Button text="New project" variant="primary" />{/snippet}
928
+ </EmptyState>
929
+ ```
930
+
931
+ ### Kbd
932
+
933
+ Keyboard-key display.
934
+
935
+ ```svelte
936
+ <script>
937
+ import { Kbd } from "@spaethtech/svelte-ui";
938
+ </script>
939
+
940
+ <Kbd text="Esc" />
941
+ <Kbd keys={["Ctrl", "K"]} />
942
+ <Kbd keys={["⌘", "⇧", "P"]} size="lg" />
943
+ ```
944
+
779
945
  ### Chip
780
946
 
781
947
  An interactive pill — filter/choice tokens, removable tags, clickable labels.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spaethtech/svelte-ui",
3
- "version": "0.14.1-dev.65.3b37f4b",
3
+ "version": "0.15.1-dev.67.fbf3875",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/spaethtech/svelte-ui.git"