@happyvertical/smrt-ui 0.38.25 → 0.39.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 (205) hide show
  1. package/AGENTS.md +23 -7
  2. package/README.md +132 -0
  3. package/dist/components/data/CollectionList.svelte +106 -0
  4. package/dist/components/data/CollectionList.svelte.d.ts +49 -0
  5. package/dist/components/data/CollectionList.svelte.d.ts.map +1 -0
  6. package/dist/components/data/CollectionToolbar.svelte +72 -0
  7. package/dist/components/data/CollectionToolbar.svelte.d.ts +20 -0
  8. package/dist/components/data/CollectionToolbar.svelte.d.ts.map +1 -0
  9. package/dist/components/data/DataTable.svelte +107 -17
  10. package/dist/components/data/DataTable.svelte.d.ts +2 -2
  11. package/dist/components/data/DataTable.svelte.d.ts.map +1 -1
  12. package/dist/components/data/__tests__/DataTable.test.js +61 -1
  13. package/dist/components/data/index.d.ts +2 -0
  14. package/dist/components/data/index.d.ts.map +1 -1
  15. package/dist/components/data/index.js +2 -0
  16. package/dist/components/data/types.d.ts +33 -0
  17. package/dist/components/data/types.d.ts.map +1 -1
  18. package/dist/components/feedback/Alert.svelte +53 -0
  19. package/dist/components/feedback/Alert.svelte.d.ts +14 -0
  20. package/dist/components/feedback/Alert.svelte.d.ts.map +1 -0
  21. package/dist/components/feedback/Drawer.svelte +34 -0
  22. package/dist/components/feedback/Drawer.svelte.d.ts +18 -0
  23. package/dist/components/feedback/Drawer.svelte.d.ts.map +1 -0
  24. package/dist/components/feedback/Meter.svelte +33 -0
  25. package/dist/components/feedback/Meter.svelte.d.ts +16 -0
  26. package/dist/components/feedback/Meter.svelte.d.ts.map +1 -0
  27. package/dist/components/feedback/Modal.svelte +9 -5
  28. package/dist/components/feedback/Modal.svelte.d.ts +2 -2
  29. package/dist/components/feedback/Modal.svelte.d.ts.map +1 -1
  30. package/dist/components/feedback/Progress.svelte +61 -0
  31. package/dist/components/feedback/Progress.svelte.d.ts +14 -0
  32. package/dist/components/feedback/Progress.svelte.d.ts.map +1 -0
  33. package/dist/components/feedback/Spinner.svelte +17 -0
  34. package/dist/components/feedback/Spinner.svelte.d.ts +9 -0
  35. package/dist/components/feedback/Spinner.svelte.d.ts.map +1 -0
  36. package/dist/components/feedback/ToastViewport.svelte +48 -0
  37. package/dist/components/feedback/ToastViewport.svelte.d.ts +10 -0
  38. package/dist/components/feedback/ToastViewport.svelte.d.ts.map +1 -0
  39. package/dist/components/feedback/__tests__/toast.test.js +49 -0
  40. package/dist/components/feedback/index.d.ts +8 -0
  41. package/dist/components/feedback/index.d.ts.map +1 -1
  42. package/dist/components/feedback/index.js +10 -3
  43. package/dist/components/feedback/toast.d.ts +28 -0
  44. package/dist/components/feedback/toast.d.ts.map +1 -0
  45. package/dist/components/feedback/toast.js +58 -0
  46. package/dist/components/forms/Checkbox.svelte +149 -0
  47. package/dist/components/forms/Checkbox.svelte.d.ts +13 -0
  48. package/dist/components/forms/Checkbox.svelte.d.ts.map +1 -0
  49. package/dist/components/forms/Combobox.svelte +172 -0
  50. package/dist/components/forms/Combobox.svelte.d.ts +18 -0
  51. package/dist/components/forms/Combobox.svelte.d.ts.map +1 -0
  52. package/dist/components/forms/DatePicker.svelte +25 -0
  53. package/dist/components/forms/DatePicker.svelte.d.ts +12 -0
  54. package/dist/components/forms/DatePicker.svelte.d.ts.map +1 -0
  55. package/dist/components/forms/ErrorSummary.svelte +32 -0
  56. package/dist/components/forms/ErrorSummary.svelte.d.ts +11 -0
  57. package/dist/components/forms/ErrorSummary.svelte.d.ts.map +1 -0
  58. package/dist/components/forms/Fieldset.svelte +32 -0
  59. package/dist/components/forms/Fieldset.svelte.d.ts +13 -0
  60. package/dist/components/forms/Fieldset.svelte.d.ts.map +1 -0
  61. package/dist/components/forms/FilePicker.svelte +116 -0
  62. package/dist/components/forms/FilePicker.svelte.d.ts +19 -0
  63. package/dist/components/forms/FilePicker.svelte.d.ts.map +1 -0
  64. package/dist/components/forms/Form.svelte +55 -2
  65. package/dist/components/forms/Form.svelte.d.ts +11 -1
  66. package/dist/components/forms/Form.svelte.d.ts.map +1 -1
  67. package/dist/components/forms/FormGroup.svelte +19 -2
  68. package/dist/components/forms/FormGroup.svelte.d.ts +3 -0
  69. package/dist/components/forms/FormGroup.svelte.d.ts.map +1 -1
  70. package/dist/components/forms/Input.svelte +127 -0
  71. package/dist/components/forms/Input.svelte.d.ts +9 -1
  72. package/dist/components/forms/Input.svelte.d.ts.map +1 -1
  73. package/dist/components/forms/InputGroup.svelte +17 -0
  74. package/dist/components/forms/InputGroup.svelte.d.ts +11 -0
  75. package/dist/components/forms/InputGroup.svelte.d.ts.map +1 -0
  76. package/dist/components/forms/Listbox.svelte +97 -0
  77. package/dist/components/forms/Listbox.svelte.d.ts +15 -0
  78. package/dist/components/forms/Listbox.svelte.d.ts.map +1 -0
  79. package/dist/components/forms/MultiSelect.svelte +159 -0
  80. package/dist/components/forms/MultiSelect.svelte.d.ts +16 -0
  81. package/dist/components/forms/MultiSelect.svelte.d.ts.map +1 -0
  82. package/dist/components/forms/Radio.svelte +34 -0
  83. package/dist/components/forms/Radio.svelte.d.ts +10 -0
  84. package/dist/components/forms/Radio.svelte.d.ts.map +1 -0
  85. package/dist/components/forms/RadioGroup.svelte +130 -0
  86. package/dist/components/forms/RadioGroup.svelte.d.ts +19 -0
  87. package/dist/components/forms/RadioGroup.svelte.d.ts.map +1 -0
  88. package/dist/components/forms/RangeSlider.svelte +137 -0
  89. package/dist/components/forms/RangeSlider.svelte.d.ts +21 -0
  90. package/dist/components/forms/RangeSlider.svelte.d.ts.map +1 -0
  91. package/dist/components/forms/SegmentedControl.svelte +87 -0
  92. package/dist/components/forms/SegmentedControl.svelte.d.ts +17 -0
  93. package/dist/components/forms/SegmentedControl.svelte.d.ts.map +1 -0
  94. package/dist/components/forms/Select.svelte +84 -3
  95. package/dist/components/forms/Select.svelte.d.ts +6 -1
  96. package/dist/components/forms/Select.svelte.d.ts.map +1 -1
  97. package/dist/components/forms/Slider.svelte +131 -0
  98. package/dist/components/forms/Slider.svelte.d.ts +18 -0
  99. package/dist/components/forms/Slider.svelte.d.ts.map +1 -0
  100. package/dist/components/forms/Switch.svelte +128 -0
  101. package/dist/components/forms/Switch.svelte.d.ts +14 -0
  102. package/dist/components/forms/Switch.svelte.d.ts.map +1 -0
  103. package/dist/components/forms/TagsInput.svelte +106 -0
  104. package/dist/components/forms/TagsInput.svelte.d.ts +17 -0
  105. package/dist/components/forms/TagsInput.svelte.d.ts.map +1 -0
  106. package/dist/components/forms/Textarea.svelte +84 -3
  107. package/dist/components/forms/Textarea.svelte.d.ts +6 -1
  108. package/dist/components/forms/Textarea.svelte.d.ts.map +1 -1
  109. package/dist/components/forms/TimePicker.svelte +25 -0
  110. package/dist/components/forms/TimePicker.svelte.d.ts +12 -0
  111. package/dist/components/forms/TimePicker.svelte.d.ts.map +1 -0
  112. package/dist/components/forms/ToggleButton.svelte +79 -0
  113. package/dist/components/forms/ToggleButton.svelte.d.ts +13 -0
  114. package/dist/components/forms/ToggleButton.svelte.d.ts.map +1 -0
  115. package/dist/components/forms/__tests__/composite-controls.fixture.svelte +29 -0
  116. package/dist/components/forms/__tests__/composite-controls.fixture.svelte.d.ts +8 -0
  117. package/dist/components/forms/__tests__/composite-controls.fixture.svelte.d.ts.map +1 -0
  118. package/dist/components/forms/__tests__/composite-controls.test.js +33 -0
  119. package/dist/components/forms/__tests__/control-interaction.test.js +95 -0
  120. package/dist/components/forms/__tests__/core-controls.fixture.svelte +39 -0
  121. package/dist/components/forms/__tests__/core-controls.fixture.svelte.d.ts +8 -0
  122. package/dist/components/forms/__tests__/core-controls.fixture.svelte.d.ts.map +1 -0
  123. package/dist/components/forms/__tests__/core-controls.test.js +90 -0
  124. package/dist/components/forms/control-dom.d.ts +6 -0
  125. package/dist/components/forms/control-dom.d.ts.map +1 -0
  126. package/dist/components/forms/control-dom.js +38 -0
  127. package/dist/components/forms/control-interaction-context.d.ts +9 -0
  128. package/dist/components/forms/control-interaction-context.d.ts.map +1 -0
  129. package/dist/components/forms/control-interaction-context.js +15 -0
  130. package/dist/components/forms/control-interaction.d.ts +147 -0
  131. package/dist/components/forms/control-interaction.d.ts.map +1 -0
  132. package/dist/components/forms/control-interaction.js +265 -0
  133. package/dist/components/forms/form-group-context.d.ts +7 -0
  134. package/dist/components/forms/form-group-context.d.ts.map +1 -1
  135. package/dist/components/forms/index.d.ts +22 -1
  136. package/dist/components/forms/index.d.ts.map +1 -1
  137. package/dist/components/forms/index.js +21 -1
  138. package/dist/components/forms/radio-group-context.d.ts +12 -0
  139. package/dist/components/forms/radio-group-context.d.ts.map +1 -0
  140. package/dist/components/forms/radio-group-context.js +11 -0
  141. package/dist/components/forms/types.d.ts +14 -0
  142. package/dist/components/forms/types.d.ts.map +1 -0
  143. package/dist/components/forms/types.js +1 -0
  144. package/dist/components/forms/use-control-registration.svelte.d.ts +8 -0
  145. package/dist/components/forms/use-control-registration.svelte.d.ts.map +1 -0
  146. package/dist/components/forms/use-control-registration.svelte.js +15 -0
  147. package/dist/components/ui/Accordion.svelte +44 -0
  148. package/dist/components/ui/Accordion.svelte.d.ts +12 -0
  149. package/dist/components/ui/Accordion.svelte.d.ts.map +1 -0
  150. package/dist/components/ui/AccordionItem.svelte +42 -0
  151. package/dist/components/ui/AccordionItem.svelte.d.ts +11 -0
  152. package/dist/components/ui/AccordionItem.svelte.d.ts.map +1 -0
  153. package/dist/components/ui/Disclosure.svelte +50 -0
  154. package/dist/components/ui/Disclosure.svelte.d.ts +13 -0
  155. package/dist/components/ui/Disclosure.svelte.d.ts.map +1 -0
  156. package/dist/components/ui/Dropdown.svelte +5 -7
  157. package/dist/components/ui/Dropdown.svelte.d.ts +9 -0
  158. package/dist/components/ui/Dropdown.svelte.d.ts.map +1 -1
  159. package/dist/components/ui/Popover.svelte +102 -0
  160. package/dist/components/ui/Popover.svelte.d.ts +16 -0
  161. package/dist/components/ui/Popover.svelte.d.ts.map +1 -0
  162. package/dist/components/ui/__tests__/interaction-primitives.fixture.svelte +13 -0
  163. package/dist/components/ui/__tests__/interaction-primitives.fixture.svelte.d.ts +19 -0
  164. package/dist/components/ui/__tests__/interaction-primitives.fixture.svelte.d.ts.map +1 -0
  165. package/dist/components/ui/__tests__/interaction-primitives.test.js +31 -0
  166. package/dist/components/ui/accordion-context.d.ts +7 -0
  167. package/dist/components/ui/accordion-context.d.ts.map +1 -0
  168. package/dist/components/ui/accordion-context.js +11 -0
  169. package/dist/components/ui/index.d.ts +5 -1
  170. package/dist/components/ui/index.d.ts.map +1 -1
  171. package/dist/components/ui/index.js +5 -1
  172. package/dist/index.d.ts +1 -1
  173. package/dist/index.d.ts.map +1 -1
  174. package/dist/playground.d.ts +2 -0
  175. package/dist/playground.d.ts.map +1 -0
  176. package/dist/playground.js +1 -0
  177. package/dist/svelte/__tests__/playground.test.js +18 -0
  178. package/dist/svelte/playground/BaseControlsPreview.svelte +233 -0
  179. package/dist/svelte/playground/BaseControlsPreview.svelte.d.ts +4 -0
  180. package/dist/svelte/playground/BaseControlsPreview.svelte.d.ts.map +1 -0
  181. package/dist/svelte/playground/CollectionsPreview.svelte +59 -0
  182. package/dist/svelte/playground/CollectionsPreview.svelte.d.ts +4 -0
  183. package/dist/svelte/playground/CollectionsPreview.svelte.d.ts.map +1 -0
  184. package/dist/svelte/playground/DataTablePreview.svelte +247 -0
  185. package/dist/svelte/playground/DataTablePreview.svelte.d.ts +4 -0
  186. package/dist/svelte/playground/DataTablePreview.svelte.d.ts.map +1 -0
  187. package/dist/svelte/playground/FeedbackPreview.svelte +51 -0
  188. package/dist/svelte/playground/FeedbackPreview.svelte.d.ts +4 -0
  189. package/dist/svelte/playground/FeedbackPreview.svelte.d.ts.map +1 -0
  190. package/dist/svelte/playground/InteractiveControlsPreview.svelte +109 -0
  191. package/dist/svelte/playground/InteractiveControlsPreview.svelte.d.ts +4 -0
  192. package/dist/svelte/playground/InteractiveControlsPreview.svelte.d.ts.map +1 -0
  193. package/dist/svelte/playground.d.ts +20 -0
  194. package/dist/svelte/playground.d.ts.map +1 -0
  195. package/dist/svelte/playground.js +65 -0
  196. package/dist/theme/index.d.ts +6 -4
  197. package/dist/theme/index.d.ts.map +1 -1
  198. package/dist/theme/index.js +6 -7
  199. package/dist/themes/context.svelte.js +1 -1
  200. package/dist/themes/index.d.ts +1 -1
  201. package/dist/themes/index.js +1 -1
  202. package/dist/themes/smrt/index.js +1 -1
  203. package/dist/types-generic.d.ts +7 -1
  204. package/dist/types-generic.d.ts.map +1 -1
  205. package/package.json +23 -12
package/AGENTS.md CHANGED
@@ -14,9 +14,11 @@ SMRT has one shared set of UI primitives, split across two packages by concern:
14
14
 
15
15
  - **`smrt-ui` (here) owns the domain-agnostic VISUAL primitives** — `Button`,
16
16
  `Card`, `Modal`/`ConfirmDialog`, `Badge`, `Avatar`, `Chip`, `Dropdown`,
17
- `Tooltip`, `Skeleton`, `Tree`, `Pagination`, `DataTable`, … — **plus the
17
+ `Tooltip`, `Skeleton`, `Tree`, `Pagination`, `DataTable`, `ContentList`, … — **plus the
18
18
  Provider-free base FORM primitives** under `./forms` (`Form`, `Input`,
19
- `Select`, `Textarea`, `Toggle`, `FormGroup`), relocated here in #1589's
19
+ `Select`, `Textarea`, `Checkbox`, `Switch`, `RadioGroup`, `Slider`,
20
+ `RangeSlider`, `Combobox`, `MultiSelect`, `TagsInput`, `FilePicker`,
21
+ `FormGroup`), relocated here in #1589's
20
22
  deferred-forms phase so domain packages can adopt them without pulling in the
21
23
  smrt-svelte Provider or closing a build-graph cycle. These are dependency-free:
22
24
  no Provider, no i18n, no spoken-input logic.
@@ -42,16 +44,18 @@ components are exempt — they *are* the primitives.
42
44
  | Subpath | Contents |
43
45
  |---------|----------|
44
46
  | `.` | barrel re-exporting the primitives, registry, and theme system |
45
- | `./ui` | `Button`, `Card`, `Badge`, `Avatar`, `Chip`, `Skeleton`, `Tooltip`, `Dropdown`, `Tree`, `Pagination`, |
46
- | `./feedback` | `Modal`, `ConfirmDialog`, `LoadingOverlay`, `ProgressBar` |
47
+ | `./playground` | shared-host previews for the complete foundation catalog; rendered inside the active preset and color scheme |
48
+ | `./ui` | `Button`, `Card`, `Badge`, `Avatar`, `Chip`, `Skeleton`, `Tooltip`, `Dropdown`/`Menu`, `Popover`, `Disclosure`, `Accordion`, `Tree`, `Pagination`, … |
49
+ | `./feedback` | `Alert`, `ToastViewport`, `Modal`, `Drawer`/`Sheet`, `ConfirmDialog`, `LoadingOverlay`, `Progress`, `Meter`, `Spinner` |
50
+ | `./data` | `CollectionToolbar`, `CollectionList`/`ContentList`, `DataTable` and their types |
47
51
  | `./layout` | `Container`, `Grid`, `Header`, `Footer`, `PageHeader`, `EmptyState`, … |
48
52
  | `./calendar` | `Calendar`, `DayView` |
49
53
  | `./chat` | `MessageBubble`, `ReactionPicker`, `TypingIndicator` |
50
- | `./forms` | Provider-free base form primitives: `Form`, `Input`, `Select`, `Textarea`, `Toggle`, `FormGroup` (+ the FormGroup a11y context helpers) |
54
+ | `./forms` | Provider-free fields, choice controls, sliders/ranges, combobox/listbox/multiselect/tags, date/time/file controls, plus the transport-neutral control interaction registry |
51
55
  | `./i18n` | i18n **client**: `useI18n`, `<Trans>`, `defineMessages`, `renderTemplate` (no `smrt-languages` import — the server resolver stays in `smrt-svelte/i18n/server`) |
52
56
  | `./registry` | `ModuleUIRegistry` for cross-package component discovery |
53
- | `./theme` | simple `ThemeProvider` + context (`useTheme` consumes this from `smrt-svelte`) |
54
- | `./themes` | canonical preset token system (material/glass/studio/smrt), CSS generation |
57
+ | `./theme` | deprecated compatibility path forwarding to the canonical theme system |
58
+ | `./themes` | canonical `ThemeProvider`, context, preset token system (material/glass/studio/smrt), and CSS generation |
55
59
  | `./themes/styles/smrt.css` | static SMRT theme CSS (dark-first amber instrument-panel look) + signature `.smrt-*` flourish utilities |
56
60
  | `./themes/styles/fonts.css` | optional self-hosted `@font-face` for the SMRT type stack (Space Grotesk / Inter / JetBrains Mono) |
57
61
  | `./styles/tokens.css` | base token stylesheet |
@@ -59,6 +63,14 @@ components are exempt — they *are* the primitives.
59
63
  Also internal: `actions/` (`ripple`, `permission`), `display/`, `data/`,
60
64
  `nav/`, `permissions/`, `utils/`, `test-support/`.
61
65
 
66
+ ## Playground
67
+
68
+ The package publishes `./playground` and keeps its workspace source at
69
+ `src/svelte/playground.ts`. Previews inherit the shared host's active
70
+ Material/Glass/Studio/SMRT preset and light/dark color scheme; preview
71
+ components must consume `--smrt-*` tokens rather than installing their own
72
+ theme provider or static preset stylesheet.
73
+
62
74
  ## Rules
63
75
 
64
76
  - **No smrt domain imports.** A `@happyvertical/smrt-*` import other than
@@ -73,6 +85,10 @@ Also internal: `actions/` (`ripple`, `permission`), `display/`, `data/`,
73
85
  hooks / browser-ai — live in `@happyvertical/smrt-svelte`, which depends on
74
86
  this package.
75
87
  - Built with `svelte-package`; consumed via the `svelte` export condition.
88
+ - Form controls expose stable `formId` + `controlId` identity through the
89
+ interaction registry. Keep chat/voice transports outside this package;
90
+ mutations must retain sensitivity checks and the stage → confirmed apply
91
+ policy.
76
92
 
77
93
  ## Gotchas
78
94
 
package/README.md ADDED
@@ -0,0 +1,132 @@
1
+ # @happyvertical/smrt-ui
2
+
3
+ The domain-agnostic Svelte 5 component foundation for SMRT. Components use the
4
+ shared `--smrt-*` design tokens, render without a SMRT Provider, and can be used
5
+ by any package or application without pulling domain dependencies into the UI
6
+ layer.
7
+
8
+ ```bash
9
+ pnpm add @happyvertical/smrt-ui
10
+ ```
11
+
12
+ ```svelte
13
+ <script lang="ts">
14
+ import { ThemeProvider } from '@happyvertical/smrt-ui/themes';
15
+ import { Button } from '@happyvertical/smrt-ui/ui';
16
+ import { Form, FormGroup, Input, Switch } from '@happyvertical/smrt-ui/forms';
17
+ </script>
18
+
19
+ <ThemeProvider preset="material" colorScheme="system">
20
+ <Form formId="profile">
21
+ <FormGroup label="Display name"><Input name="displayName" /></FormGroup>
22
+ <Switch name="updates" label="Product updates" />
23
+ <Button type="submit">Save</Button>
24
+ </Form>
25
+ </ThemeProvider>
26
+ ```
27
+
28
+ ## Foundation catalog
29
+
30
+ | Area | Components |
31
+ | --- | --- |
32
+ | Fields | `Form`, `Field`/`FormGroup`, `Fieldset`, `InputGroup`, `ErrorSummary` |
33
+ | Text and structured input | `Input`, `Textarea`, `Select`, `Combobox`, `Listbox`, `MultiSelect`, `TagsInput` |
34
+ | Choices | `Checkbox`, `RadioGroup`/`Radio`, `Switch`, `Toggle`, `ToggleButton`, `SegmentedControl` |
35
+ | Values and files | `Slider`, `RangeSlider`, `DatePicker`, `TimePicker`, `FilePicker` |
36
+ | Actions and display | `Button`, `Dropdown`/`Menu`, `Badge`, `Chip`, `Avatar`, `Card`, `Skeleton`, `Tooltip`, `Tree` |
37
+ | Disclosure and overlays | `Popover`, `Disclosure`, `Accordion`/`AccordionItem`, `Modal`, `Drawer`/`Sheet`, `ConfirmDialog` |
38
+ | Feedback | `Alert`, `ToastViewport`, `Progress`, `Meter`, `Spinner`, `LoadingOverlay` |
39
+ | Collections | `CollectionToolbar`, `CollectionList`/`ContentList`, `DataTable`, `Pagination` |
40
+ | Layout and navigation | `Container`, `Grid`, `Header`, `Footer`, `PageHeader`, `EmptyState`, `Tabs`, `FilterChips` |
41
+
42
+ Use the focused subpaths (`/forms`, `/ui`, `/feedback`, `/data`, `/layout`,
43
+ `/themes`) to keep imports explicit. The package root remains a compatibility
44
+ barrel.
45
+
46
+ ## Component standard
47
+
48
+ Foundation components follow one contract:
49
+
50
+ - Native HTML semantics first, with labelled controls, keyboard interaction,
51
+ focus-visible states, disabled/read-only handling, and reduced-motion rules.
52
+ - Svelte 5 bindable state plus explicit change callbacks for controlled use.
53
+ - SSR-safe IDs from `$props.id()` and stable `name`-based interaction identity.
54
+ - Styling only through semantic `--smrt-*` tokens; no component owns a theme.
55
+ - Loading, empty, invalid, indeterminate, and disabled states are visible and
56
+ announced where applicable.
57
+ - Provider-free implementation and no domain imports.
58
+ - Focused interaction tests and axe checks for composed controls.
59
+
60
+ Application-specific editors, maps, charts, media workbenches, and domain
61
+ records remain composites built from this foundation rather than generic base
62
+ components.
63
+
64
+ ## Agent-addressable forms
65
+
66
+ `Form` can expose its controls to a chat, voice, tutorial, or test adapter
67
+ without coupling controls to a transport:
68
+
69
+ ```svelte
70
+ <script lang="ts">
71
+ import {
72
+ Form,
73
+ FormGroup,
74
+ Input,
75
+ createControlInteractionRegistry,
76
+ } from '@happyvertical/smrt-ui/forms';
77
+
78
+ const registry = createControlInteractionRegistry();
79
+
80
+ async function proposeName() {
81
+ await registry.execute(
82
+ {
83
+ action: 'stage',
84
+ identity: { formId: 'profile', controlId: 'displayName' },
85
+ value: 'Ada Lovelace',
86
+ },
87
+ { source: 'agent' },
88
+ );
89
+ }
90
+ </script>
91
+
92
+ <Form formId="profile" interactionRegistry={registry}>
93
+ <FormGroup label="Display name">
94
+ <Input name="displayName" />
95
+ </FormGroup>
96
+ </Form>
97
+ ```
98
+
99
+ Controls publish serializable metadata, constraints, options, sensitivity, and
100
+ capabilities. Adapters can focus, reveal, highlight, explain, validate, stage,
101
+ apply, clear, or undo. Agent mutations are denied for secret/read-only controls
102
+ and require explicit confirmation before apply, clear, or undo. Staging is
103
+ separate so the UI can show a proposal before it changes user state.
104
+
105
+ ## Themes
106
+
107
+ `@happyvertical/smrt-ui/themes` is the canonical theme API and includes the
108
+ Material, Glass, Studio, and SMRT presets. The old `/theme` path forwards to the
109
+ same provider and context for compatibility.
110
+
111
+ ```svelte
112
+ <script>
113
+ import { ThemeProvider } from '@happyvertical/smrt-ui/themes';
114
+ import '@happyvertical/smrt-ui/themes/styles/fonts.css';
115
+ </script>
116
+
117
+ <ThemeProvider preset="smrt" colorScheme="dark">
118
+ {@render children()}
119
+ </ThemeProvider>
120
+ ```
121
+
122
+ Run the shared playground to inspect the full catalog under every preset and
123
+ light/dark scheme.
124
+
125
+ ## Development
126
+
127
+ ```bash
128
+ pnpm check
129
+ pnpm test
130
+ pnpm build
131
+ pnpm verify:pack
132
+ ```
@@ -0,0 +1,106 @@
1
+ <script lang="ts" generics="T">
2
+ import type { Snippet } from 'svelte';
3
+
4
+ export interface Props<T> {
5
+ items: T[];
6
+ itemKey?: keyof T | ((item: T) => string | number);
7
+ title?: keyof T | ((item: T) => string);
8
+ description?: keyof T | ((item: T) => string | undefined);
9
+ layout?: 'list' | 'grid';
10
+ selectable?: boolean;
11
+ selected?: Set<string | number>;
12
+ loading?: boolean;
13
+ item?: Snippet<[{ item: T; index: number; selected: boolean }]>;
14
+ actions?: Snippet<[{ item: T; index: number }]>;
15
+ empty?: Snippet;
16
+ onselectionchange?: (selected: Set<string | number>) => void;
17
+ onitemclick?: (item: T, index: number) => void;
18
+ class?: string;
19
+ }
20
+
21
+ let {
22
+ items,
23
+ itemKey,
24
+ title,
25
+ description,
26
+ layout = 'list',
27
+ selectable = false,
28
+ selected = $bindable(new Set<string | number>()),
29
+ loading = false,
30
+ item,
31
+ actions,
32
+ empty,
33
+ onselectionchange,
34
+ onitemclick,
35
+ class: className = '',
36
+ }: Props<T> = $props();
37
+
38
+ function keyOf(entry: T, index: number): string | number {
39
+ if (!itemKey) return index;
40
+ return typeof itemKey === 'function'
41
+ ? itemKey(entry)
42
+ : (entry[itemKey] as string | number);
43
+ }
44
+
45
+ function textOf(
46
+ entry: T,
47
+ accessor: keyof T | ((item: T) => string | undefined) | undefined,
48
+ ): string | undefined {
49
+ if (!accessor) return undefined;
50
+ return typeof accessor === 'function'
51
+ ? accessor(entry)
52
+ : String(entry[accessor] ?? '');
53
+ }
54
+
55
+ function toggle(key: string | number) {
56
+ const next = new Set(selected);
57
+ next.has(key) ? next.delete(key) : next.add(key);
58
+ selected = next;
59
+ onselectionchange?.(next);
60
+ }
61
+ </script>
62
+
63
+ {#if loading}
64
+ <div class="loading" role="status"><span class="spinner" aria-hidden="true"></span>Loading…</div>
65
+ {:else if items.length === 0}
66
+ <div class="empty">{#if empty}{@render empty()}{:else}No items available{/if}</div>
67
+ {:else}
68
+ <ul class="collection collection--{layout} {className}">
69
+ {#each items as entry, index (keyOf(entry, index))}
70
+ {@const key = keyOf(entry, index)}
71
+ {@const isSelected = selected.has(key)}
72
+ {@const itemTitle = textOf(entry, title) ?? `Item ${index + 1}`}
73
+ <li class:selected={isSelected}>
74
+ {#if selectable}<input type="checkbox" checked={isSelected} aria-label={`Select ${itemTitle}`} onchange={() => toggle(key)} />{/if}
75
+ {#if onitemclick}
76
+ <button type="button" class="main" onclick={() => onitemclick?.(entry, index)}>
77
+ {#if item}{@render item({ item: entry, index, selected: isSelected })}{:else}<strong>{itemTitle}</strong>{#if description}<span>{textOf(entry, description)}</span>{/if}{/if}
78
+ </button>
79
+ {:else}
80
+ <div class="main">
81
+ {#if item}{@render item({ item: entry, index, selected: isSelected })}{:else}<strong>{itemTitle}</strong>{#if description}<span>{textOf(entry, description)}</span>{/if}{/if}
82
+ </div>
83
+ {/if}
84
+ {#if actions}<div class="actions">{@render actions({ item: entry, index })}</div>{/if}
85
+ </li>
86
+ {/each}
87
+ </ul>
88
+ {/if}
89
+
90
+ <style>
91
+ .collection { display: grid; gap: var(--smrt-spacing-2); margin: 0; padding: 0; list-style: none; }
92
+ .collection--grid { grid-template-columns: repeat(auto-fill, minmax(min(18rem, 100%), 1fr)); }
93
+ li { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: var(--smrt-spacing-3); min-width: 0; padding: var(--smrt-spacing-3); border: 1px solid var(--smrt-color-outline-variant); border-radius: var(--smrt-radius-medium); background: var(--smrt-color-surface); color: var(--smrt-color-on-surface); }
94
+ li.selected { border-color: var(--smrt-color-primary); background: var(--smrt-color-primary-container); }
95
+ input { width: 1.125rem; height: 1.125rem; accent-color: var(--smrt-color-primary); }
96
+ .main { display: grid; min-width: 0; gap: var(--smrt-spacing-1); padding: 0; border: 0; background: transparent; color: inherit; font: inherit; text-align: left; }
97
+ button.main { width: 100%; cursor: pointer; }
98
+ button.main:focus-visible { outline: 2px solid var(--smrt-color-primary); outline-offset: 3px; }
99
+ .main strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font: var(--smrt-typography-title-small-font); }
100
+ .main span { color: var(--smrt-color-on-surface-variant); }
101
+ .actions { display: flex; gap: var(--smrt-spacing-1); }
102
+ .loading, .empty { display: flex; min-height: 8rem; align-items: center; justify-content: center; gap: var(--smrt-spacing-2); color: var(--smrt-color-on-surface-variant); }
103
+ .spinner { width: 1rem; height: 1rem; border: 2px solid var(--smrt-color-outline-variant); border-top-color: var(--smrt-color-primary); border-radius: 50%; animation: spin .8s linear infinite; }
104
+ @keyframes spin { to { transform: rotate(360deg); } }
105
+ @media (prefers-reduced-motion: reduce) { .spinner { animation-duration: 1.6s; } }
106
+ </style>
@@ -0,0 +1,49 @@
1
+ import type { Snippet } from 'svelte';
2
+ export interface Props<T> {
3
+ items: T[];
4
+ itemKey?: keyof T | ((item: T) => string | number);
5
+ title?: keyof T | ((item: T) => string);
6
+ description?: keyof T | ((item: T) => string | undefined);
7
+ layout?: 'list' | 'grid';
8
+ selectable?: boolean;
9
+ selected?: Set<string | number>;
10
+ loading?: boolean;
11
+ item?: Snippet<[{
12
+ item: T;
13
+ index: number;
14
+ selected: boolean;
15
+ }]>;
16
+ actions?: Snippet<[{
17
+ item: T;
18
+ index: number;
19
+ }]>;
20
+ empty?: Snippet;
21
+ onselectionchange?: (selected: Set<string | number>) => void;
22
+ onitemclick?: (item: T, index: number) => void;
23
+ class?: string;
24
+ }
25
+ declare function $$render<T>(): {
26
+ props: Props<T>;
27
+ exports: {};
28
+ bindings: "selected";
29
+ slots: {};
30
+ events: {};
31
+ };
32
+ declare class __sveltets_Render<T> {
33
+ props(): ReturnType<typeof $$render<T>>['props'];
34
+ events(): ReturnType<typeof $$render<T>>['events'];
35
+ slots(): ReturnType<typeof $$render<T>>['slots'];
36
+ bindings(): "selected";
37
+ exports(): {};
38
+ }
39
+ interface $$IsomorphicComponent {
40
+ new <T>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
41
+ $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
42
+ } & ReturnType<__sveltets_Render<T>['exports']>;
43
+ <T>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
44
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
45
+ }
46
+ declare const CollectionList: $$IsomorphicComponent;
47
+ type CollectionList<T> = InstanceType<typeof CollectionList<T>>;
48
+ export default CollectionList;
49
+ //# sourceMappingURL=CollectionList.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CollectionList.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/data/CollectionList.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAGtC,MAAM,WAAW,KAAK,CAAC,CAAC;IACtB,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,OAAO,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,GAAG,MAAM,CAAC,CAAC;IACnD,KAAK,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,CAAC,CAAC;IACxC,WAAW,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,GAAG,SAAS,CAAC,CAAC;IAC1D,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;IAChC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;QAAE,IAAI,EAAE,CAAC,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC,CAAC;IAChE,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC;QAAE,IAAI,EAAE,CAAC,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;IAChD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,KAAK,IAAI,CAAC;IAC7D,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAAC,iBAAS,QAAQ,CAAC,CAAC;WA6EQ,KAAK,CAAC,CAAC,CAAC;;;;;EAAoF;AACzH,cAAM,iBAAiB,CAAC,CAAC;IACrB,KAAK,IAAI,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAChD,MAAM,IAAI,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IAClD,KAAK,IAAI,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAChD,QAAQ;IACR,OAAO;CACV;AAED,UAAU,qBAAqB;IAC3B,KAAK,CAAC,EAAE,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAA;KAAE,GAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IAC5X,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IAC3H,YAAY,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;CACjE;AACD,QAAA,MAAM,cAAc,EAAE,qBAAmC,CAAC;AACxC,KAAK,cAAc,CAAC,CAAC,IAAI,YAAY,CAAC,OAAO,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;AAClE,eAAe,cAAc,CAAC"}
@@ -0,0 +1,72 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import Input from '../forms/Input.svelte';
4
+ import SegmentedControl from '../forms/SegmentedControl.svelte';
5
+
6
+ export interface Props {
7
+ search?: string;
8
+ searchLabel?: string;
9
+ searchPlaceholder?: string;
10
+ view?: 'list' | 'grid' | 'table';
11
+ views?: Array<'list' | 'grid' | 'table'>;
12
+ resultCount?: number;
13
+ selectedCount?: number;
14
+ filters?: Snippet;
15
+ actions?: Snippet;
16
+ bulkActions?: Snippet;
17
+ onsearchchange?: (value: string) => void;
18
+ onviewchange?: (view: 'list' | 'grid' | 'table') => void;
19
+ class?: string;
20
+ }
21
+
22
+ let {
23
+ search = $bindable(''),
24
+ searchLabel = 'Search',
25
+ searchPlaceholder = 'Search…',
26
+ view = $bindable('list'),
27
+ views = ['list', 'grid'],
28
+ resultCount,
29
+ selectedCount = 0,
30
+ filters,
31
+ actions,
32
+ bulkActions,
33
+ onsearchchange,
34
+ onviewchange,
35
+ class: className = '',
36
+ }: Props = $props();
37
+
38
+ const viewOptions = $derived(
39
+ views.map((value) => ({
40
+ value,
41
+ label: value[0].toUpperCase() + value.slice(1),
42
+ })),
43
+ );
44
+
45
+ function changeView(next: string | number) {
46
+ view = String(next) as typeof view;
47
+ onviewchange?.(view);
48
+ }
49
+ </script>
50
+
51
+ <div class="toolbar {className}" role="search">
52
+ <div class="search">
53
+ <Input type="search" name="collection-search" aria-label={searchLabel} placeholder={searchPlaceholder} bind:value={search} oninput={(event) => onsearchchange?.(event.currentTarget.value)} />
54
+ </div>
55
+ {#if filters}<div class="filters">{@render filters()}</div>{/if}
56
+ {#if resultCount !== undefined}<span class="count" aria-live="polite">{resultCount} {resultCount === 1 ? 'result' : 'results'}</span>{/if}
57
+ <span class="spacer"></span>
58
+ {#if selectedCount > 0 && bulkActions}<div class="bulk"><span>{selectedCount} selected</span>{@render bulkActions()}</div>{/if}
59
+ {#if actions}<div class="actions">{@render actions()}</div>{/if}
60
+ {#if viewOptions.length > 1}
61
+ <SegmentedControl label="View" options={viewOptions} value={view} interaction={false} onvaluechange={changeView} />
62
+ {/if}
63
+ </div>
64
+
65
+ <style>
66
+ .toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--smrt-spacing-2); padding: var(--smrt-spacing-2) 0; color: var(--smrt-color-on-surface); }
67
+ .search { flex: 1 1 14rem; max-width: 24rem; }
68
+ .filters, .actions, .bulk { display: flex; align-items: center; gap: var(--smrt-spacing-2); }
69
+ .bulk { padding: var(--smrt-spacing-1) var(--smrt-spacing-2); border-radius: var(--smrt-radius-small); background: var(--smrt-color-secondary-container); color: var(--smrt-color-on-secondary-container); }
70
+ .count { color: var(--smrt-color-on-surface-variant); font: var(--smrt-typography-label-medium-font); }
71
+ .spacer { flex: 1; }
72
+ </style>
@@ -0,0 +1,20 @@
1
+ import type { Snippet } from 'svelte';
2
+ export interface Props {
3
+ search?: string;
4
+ searchLabel?: string;
5
+ searchPlaceholder?: string;
6
+ view?: 'list' | 'grid' | 'table';
7
+ views?: Array<'list' | 'grid' | 'table'>;
8
+ resultCount?: number;
9
+ selectedCount?: number;
10
+ filters?: Snippet;
11
+ actions?: Snippet;
12
+ bulkActions?: Snippet;
13
+ onsearchchange?: (value: string) => void;
14
+ onviewchange?: (view: 'list' | 'grid' | 'table') => void;
15
+ class?: string;
16
+ }
17
+ declare const CollectionToolbar: import("svelte").Component<Props, {}, "view" | "search">;
18
+ type CollectionToolbar = ReturnType<typeof CollectionToolbar>;
19
+ export default CollectionToolbar;
20
+ //# sourceMappingURL=CollectionToolbar.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CollectionToolbar.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/data/CollectionToolbar.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAKtC,MAAM,WAAW,KAAK;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IACjC,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;IACzC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,KAAK,IAAI,CAAC;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAsDD,QAAA,MAAM,iBAAiB,0DAAwC,CAAC;AAChE,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9D,eAAe,iBAAiB,CAAC"}