@happyvertical/smrt-ui 0.38.26 → 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
@@ -28,8 +28,8 @@ export interface Props {
28
28
  title?: string;
29
29
  /** Modal size variant */
30
30
  size?: 'sm' | 'md' | 'lg' | 'xl' | 'full';
31
- /** Placement: center (default) or end for a right-anchored full-height drawer */
32
- placement?: 'center' | 'end';
31
+ /** Placement: center (default), start, or end for full-height drawers */
32
+ placement?: 'center' | 'start' | 'end';
33
33
  /** Whether clicking backdrop closes modal */
34
34
  closeOnBackdrop?: boolean;
35
35
  /** Whether pressing Escape closes modal */
@@ -114,9 +114,7 @@ const sizeClasses = {
114
114
  full: 'modal--full',
115
115
  };
116
116
 
117
- const placementClass = $derived(
118
- placement === 'end' ? 'modal--end' : 'modal--center',
119
- );
117
+ const placementClass = $derived(`modal--${placement}`);
120
118
  </script>
121
119
 
122
120
  <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
@@ -269,11 +267,17 @@ const placementClass = $derived(
269
267
 
270
268
  /* Placement: end = right-anchored, full-height drawer (e.g. detail panels).
271
269
  The size variant still sets the panel width. */
270
+ .modal--start,
272
271
  .modal--end {
273
272
  justify-content: flex-end;
274
273
  align-items: stretch;
275
274
  }
276
275
 
276
+ .modal--start {
277
+ justify-content: flex-start;
278
+ }
279
+
280
+ .modal--start .modal__container,
277
281
  .modal--end .modal__container {
278
282
  height: 100%;
279
283
  max-height: 100%;
@@ -21,8 +21,8 @@ export interface Props {
21
21
  title?: string;
22
22
  /** Modal size variant */
23
23
  size?: 'sm' | 'md' | 'lg' | 'xl' | 'full';
24
- /** Placement: center (default) or end for a right-anchored full-height drawer */
25
- placement?: 'center' | 'end';
24
+ /** Placement: center (default), start, or end for full-height drawers */
25
+ placement?: 'center' | 'start' | 'end';
26
26
  /** Whether clicking backdrop closes modal */
27
27
  closeOnBackdrop?: boolean;
28
28
  /** Whether pressing Escape closes modal */
@@ -1 +1 @@
1
- {"version":3,"file":"Modal.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/feedback/Modal.svelte.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAKtC,gCAAgC;AAChC,MAAM,WAAW,KAAK;IACpB,gCAAgC;IAChC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,4CAA4C;IAC5C,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,kBAAkB;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yBAAyB;IACzB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;IAC1C,iFAAiF;IACjF,SAAS,CAAC,EAAE,QAAQ,GAAG,KAAK,CAAC;IAC7B,6CAA6C;IAC7C,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,2CAA2C;IAC3C,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,wBAAwB;IACxB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,4BAA4B;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,4BAA4B;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,mBAAmB;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gCAAgC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2BAA2B;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AA6HD,QAAA,MAAM,KAAK,+CAAwC,CAAC;AACpD,KAAK,KAAK,GAAG,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC;AACtC,eAAe,KAAK,CAAC"}
1
+ {"version":3,"file":"Modal.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/feedback/Modal.svelte.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAKtC,gCAAgC;AAChC,MAAM,WAAW,KAAK;IACpB,gCAAgC;IAChC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,4CAA4C;IAC5C,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,kBAAkB;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yBAAyB;IACzB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;IAC1C,yEAAyE;IACzE,SAAS,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,KAAK,CAAC;IACvC,6CAA6C;IAC7C,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,2CAA2C;IAC3C,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,wBAAwB;IACxB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,4BAA4B;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,4BAA4B;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,mBAAmB;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gCAAgC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2BAA2B;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AA2HD,QAAA,MAAM,KAAK,+CAAwC,CAAC;AACpD,KAAK,KAAK,GAAG,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC;AACtC,eAAe,KAAK,CAAC"}
@@ -0,0 +1,61 @@
1
+ <script lang="ts">
2
+ export interface Props {
3
+ value?: number;
4
+ min?: number;
5
+ max?: number;
6
+ label?: string;
7
+ variant?: 'linear' | 'circular';
8
+ size?: 'sm' | 'md' | 'lg';
9
+ showValue?: boolean;
10
+ class?: string;
11
+ }
12
+ let {
13
+ value,
14
+ min = 0,
15
+ max = 100,
16
+ label = 'Progress',
17
+ variant = 'linear',
18
+ size = 'md',
19
+ showValue = false,
20
+ class: className = '',
21
+ }: Props = $props();
22
+ const indeterminate = $derived(value === undefined);
23
+ const normalized = $derived(
24
+ indeterminate || max <= min
25
+ ? 0
26
+ : Math.min(100, Math.max(0, (((value ?? min) - min) / (max - min)) * 100)),
27
+ );
28
+ const displayValue = $derived(
29
+ value === undefined ? '' : `${Math.round(normalized)}%`,
30
+ );
31
+ </script>
32
+ {#if variant === 'circular'}
33
+ <div class="progress progress--circular progress--{size} {className}" class:indeterminate role="progressbar" aria-label={label}
34
+ aria-valuemin={indeterminate ? undefined : min} aria-valuemax={indeterminate ? undefined : max} aria-valuenow={value}>
35
+ <svg viewBox="0 0 44 44" aria-hidden="true"><circle class="track" cx="22" cy="22" r="18"></circle><circle class="indicator" cx="22" cy="22" r="18" style={`--progress: ${normalized}`}></circle></svg>
36
+ {#if showValue && !indeterminate}<span>{displayValue}</span>{/if}
37
+ </div>
38
+ {:else}
39
+ <div class="progress progress--linear progress--{size} {className}" class:indeterminate role="progressbar" aria-label={label}
40
+ aria-valuemin={indeterminate ? undefined : min} aria-valuemax={indeterminate ? undefined : max} aria-valuenow={value}>
41
+ <span class="track"><span class="indicator" style:width={indeterminate ? undefined : `${normalized}%`}></span></span>
42
+ {#if showValue && !indeterminate}<span class="value">{displayValue}</span>{/if}
43
+ </div>
44
+ {/if}
45
+ <style>
46
+ .progress--linear { display: flex; align-items: center; gap: var(--smrt-spacing-2); width: 100%; }
47
+ .progress--linear .track { position: relative; display: block; flex: 1; height: 4px; overflow: hidden; border-radius: var(--smrt-radius-full); background: var(--smrt-color-surface-container-highest); }
48
+ .progress--linear.progress--sm .track { height: 2px; } .progress--linear.progress--lg .track { height: 8px; }
49
+ .progress--linear .indicator { display: block; height: 100%; border-radius: inherit; background: var(--smrt-color-primary); transition: width var(--smrt-duration-short4) var(--smrt-easing-standard); }
50
+ .progress--linear.indeterminate .indicator { position: absolute; width: 35%; animation: linear-progress 1.25s ease-in-out infinite; }
51
+ .value { min-width: 3ch; font: var(--smrt-typography-label-small-font); color: var(--smrt-color-on-surface-variant); }
52
+ .progress--circular { position: relative; display: inline-grid; place-items: center; width: 2.75rem; height: 2.75rem; }
53
+ .progress--circular.progress--sm { width: 1.75rem; height: 1.75rem; } .progress--circular.progress--lg { width: 4rem; height: 4rem; }
54
+ .progress--circular svg { width: 100%; height: 100%; transform: rotate(-90deg); }
55
+ .progress--circular circle { fill: none; stroke-width: 4; } .progress--circular .track { stroke: var(--smrt-color-surface-container-highest); }
56
+ .progress--circular .indicator { stroke: var(--smrt-color-primary); stroke-linecap: round; stroke-dasharray: 113.1; stroke-dashoffset: calc(113.1 - (113.1 * var(--progress)) / 100); transition: stroke-dashoffset var(--smrt-duration-short4); }
57
+ .progress--circular.indeterminate svg { animation: spin 1s linear infinite; } .progress--circular.indeterminate .indicator { stroke-dasharray: 70 113.1; }
58
+ .progress--circular > span { position: absolute; font: var(--smrt-typography-label-small-font); }
59
+ @keyframes linear-progress { from { left: -40%; } to { left: 105%; } } @keyframes spin { to { transform: rotate(270deg); } }
60
+ @media (prefers-reduced-motion: reduce) { .indicator { transition: none !important; animation-duration: 2.5s !important; } }
61
+ </style>
@@ -0,0 +1,14 @@
1
+ export interface Props {
2
+ value?: number;
3
+ min?: number;
4
+ max?: number;
5
+ label?: string;
6
+ variant?: 'linear' | 'circular';
7
+ size?: 'sm' | 'md' | 'lg';
8
+ showValue?: boolean;
9
+ class?: string;
10
+ }
11
+ declare const Progress: import("svelte").Component<Props, {}, "">;
12
+ type Progress = ReturnType<typeof Progress>;
13
+ export default Progress;
14
+ //# sourceMappingURL=Progress.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Progress.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/feedback/Progress.svelte.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,KAAK;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAC;IAChC,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAsCD,QAAA,MAAM,QAAQ,2CAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
@@ -0,0 +1,17 @@
1
+ <script lang="ts">
2
+ export interface Props {
3
+ label?: string;
4
+ size?: 'sm' | 'md' | 'lg';
5
+ class?: string;
6
+ }
7
+ let { label = 'Loading', size = 'md', class: className = '' }: Props = $props();
8
+ </script>
9
+ <span class="spinner spinner--{size} {className}" role="status"><span class="spinner__ring" aria-hidden="true"></span><span class="visually-hidden">{label}</span></span>
10
+ <style>
11
+ .spinner { display: inline-flex; }
12
+ .spinner__ring { width: 1.5rem; height: 1.5rem; border: 2px solid var(--smrt-color-surface-container-highest); border-top-color: var(--smrt-color-primary); border-radius: 50%; animation: spin .75s linear infinite; }
13
+ .spinner--sm .spinner__ring { width: 1rem; height: 1rem; } .spinner--lg .spinner__ring { width: 2.5rem; height: 2.5rem; border-width: 3px; }
14
+ .visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
15
+ @keyframes spin { to { transform: rotate(360deg); } }
16
+ @media (prefers-reduced-motion: reduce) { .spinner__ring { animation-duration: 1.5s; } }
17
+ </style>
@@ -0,0 +1,9 @@
1
+ export interface Props {
2
+ label?: string;
3
+ size?: 'sm' | 'md' | 'lg';
4
+ class?: string;
5
+ }
6
+ declare const Spinner: import("svelte").Component<Props, {}, "">;
7
+ type Spinner = ReturnType<typeof Spinner>;
8
+ export default Spinner;
9
+ //# sourceMappingURL=Spinner.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Spinner.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/feedback/Spinner.svelte.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,KAAK;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAUD,QAAA,MAAM,OAAO,2CAAwC,CAAC;AACtD,KAAK,OAAO,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC;AAC1C,eAAe,OAAO,CAAC"}
@@ -0,0 +1,48 @@
1
+ <script lang="ts">
2
+ import Alert from './Alert.svelte';
3
+ import {
4
+ toaster as defaultToaster,
5
+ type Toast,
6
+ type Toaster,
7
+ } from './toast.js';
8
+
9
+ export interface Props {
10
+ toaster?: Toaster;
11
+ position?: 'top-start' | 'top-end' | 'bottom-start' | 'bottom-end';
12
+ class?: string;
13
+ }
14
+ let {
15
+ toaster = defaultToaster,
16
+ position = 'bottom-end',
17
+ class: className = '',
18
+ }: Props = $props();
19
+ let toasts = $state<Toast[]>([]);
20
+
21
+ $effect(() => toaster.subscribe((next) => (toasts = next)));
22
+
23
+ async function runAction(toast: Toast) {
24
+ await toast.action?.run();
25
+ toaster.dismiss(toast.id);
26
+ }
27
+ </script>
28
+
29
+ <section class="viewport viewport--{position} {className}" aria-label="Notifications" aria-live="polite">
30
+ {#each toasts as toast (toast.id)}
31
+ <Alert variant={toast.variant} title={toast.title} dismissible ondismiss={() => toaster.dismiss(toast.id)}>
32
+ {toast.message}
33
+ {#snippet action()}
34
+ {#if toast.action}<button type="button" onclick={() => runAction(toast)}>{toast.action.label}</button>{/if}
35
+ {/snippet}
36
+ </Alert>
37
+ {/each}
38
+ </section>
39
+
40
+ <style>
41
+ .viewport { position: fixed; z-index: var(--smrt-z-index-toast, 1200); display: grid; width: min(24rem, calc(100vw - 2 * var(--smrt-spacing-4))); gap: var(--smrt-spacing-2); pointer-events: none; }
42
+ .viewport :global(.alert) { pointer-events: auto; box-shadow: var(--smrt-elevation-3); }
43
+ .viewport--top-start { top: var(--smrt-spacing-4); left: var(--smrt-spacing-4); }
44
+ .viewport--top-end { top: var(--smrt-spacing-4); right: var(--smrt-spacing-4); }
45
+ .viewport--bottom-start { bottom: var(--smrt-spacing-4); left: var(--smrt-spacing-4); }
46
+ .viewport--bottom-end { right: var(--smrt-spacing-4); bottom: var(--smrt-spacing-4); }
47
+ button { padding: var(--smrt-spacing-1) var(--smrt-spacing-2); border: 1px solid currentColor; border-radius: var(--smrt-radius-small); background: transparent; color: inherit; font: inherit; cursor: pointer; }
48
+ </style>
@@ -0,0 +1,10 @@
1
+ import { type Toaster } from './toast.js';
2
+ export interface Props {
3
+ toaster?: Toaster;
4
+ position?: 'top-start' | 'top-end' | 'bottom-start' | 'bottom-end';
5
+ class?: string;
6
+ }
7
+ declare const ToastViewport: import("svelte").Component<Props, {}, "">;
8
+ type ToastViewport = ReturnType<typeof ToastViewport>;
9
+ export default ToastViewport;
10
+ //# sourceMappingURL=ToastViewport.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ToastViewport.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/feedback/ToastViewport.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,OAAO,EACb,MAAM,YAAY,CAAC;AAGpB,MAAM,WAAW,KAAK;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,WAAW,GAAG,SAAS,GAAG,cAAc,GAAG,YAAY,CAAC;IACnE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAmCD,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
@@ -0,0 +1,49 @@
1
+ import { render, screen } from '@testing-library/svelte';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { describe, expect, it, vi } from 'vitest';
4
+ import ToastViewport from '../ToastViewport.svelte';
5
+ import { createToaster } from '../toast.js';
6
+ describe('toast', () => {
7
+ it('publishes, acts on, and dismisses notifications', async () => {
8
+ const toaster = createToaster();
9
+ const action = vi.fn();
10
+ render(ToastViewport, { props: { toaster } });
11
+ toaster.show({
12
+ message: 'Draft restored',
13
+ duration: 0,
14
+ action: { label: 'Undo', run: action },
15
+ });
16
+ expect(await screen.findByText('Draft restored')).toBeInTheDocument();
17
+ await userEvent.click(screen.getByRole('button', { name: 'Undo' }));
18
+ expect(action).toHaveBeenCalledTimes(1);
19
+ expect(screen.queryByText('Draft restored')).not.toBeInTheDocument();
20
+ });
21
+ it('provides success and error conveniences', () => {
22
+ const toaster = createToaster();
23
+ const seen = [];
24
+ toaster.subscribe((toasts) => seen.push(...toasts.map((toast) => toast.variant)));
25
+ toaster.success('Saved', { duration: 0 });
26
+ toaster.error('Failed', { duration: 0 });
27
+ expect(seen).toEqual(expect.arrayContaining(['success', 'error']));
28
+ });
29
+ it('restarts dismissal timing when a toast id is reused', () => {
30
+ vi.useFakeTimers();
31
+ try {
32
+ const toaster = createToaster();
33
+ let visibleIds = [];
34
+ toaster.subscribe((toasts) => {
35
+ visibleIds = toasts.map((toast) => toast.id);
36
+ });
37
+ toaster.show({ id: 'saving', message: 'Saving', duration: 1000 });
38
+ vi.advanceTimersByTime(500);
39
+ toaster.show({ id: 'saving', message: 'Still saving', duration: 1000 });
40
+ vi.advanceTimersByTime(500);
41
+ expect(visibleIds).toEqual(['saving']);
42
+ vi.advanceTimersByTime(500);
43
+ expect(visibleIds).toEqual([]);
44
+ }
45
+ finally {
46
+ vi.useRealTimers();
47
+ }
48
+ });
49
+ });
@@ -1,8 +1,16 @@
1
1
  /**
2
2
  * Feedback components - User feedback and status indicators
3
3
  */
4
+ export { default as Alert } from './Alert.svelte';
4
5
  export { default as ConfirmDialog } from './ConfirmDialog.svelte';
6
+ export { default as Drawer, default as Sheet } from './Drawer.svelte';
5
7
  export { default as LoadingOverlay } from './LoadingOverlay.svelte';
8
+ export { default as Meter } from './Meter.svelte';
6
9
  export { default as Modal } from './Modal.svelte';
10
+ export { default as Progress } from './Progress.svelte';
7
11
  export { default as ProgressBar } from './ProgressBar.svelte';
12
+ export { default as Spinner } from './Spinner.svelte';
13
+ export { default as ToastViewport } from './ToastViewport.svelte';
14
+ export type { Toast, ToastAction, Toaster, ToastInput, ToastVariant, } from './toast.js';
15
+ export { createToaster, toaster } from './toast.js';
8
16
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/feedback/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/feedback/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,iBAAiB,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAClE,YAAY,EACV,KAAK,EACL,WAAW,EACX,OAAO,EACP,UAAU,EACV,YAAY,GACb,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC"}
@@ -1,10 +1,17 @@
1
1
  /**
2
2
  * Feedback components - User feedback and status indicators
3
3
  */
4
+ // Note: Component prop types (ConfirmDialogProps, etc.) are available
5
+ // via svelte-package build output but cannot be re-exported here because
6
+ // tsc --noEmit cannot resolve type exports from .svelte files.
7
+ export { default as Alert } from './Alert.svelte';
4
8
  export { default as ConfirmDialog } from './ConfirmDialog.svelte';
9
+ export { default as Drawer, default as Sheet } from './Drawer.svelte';
5
10
  export { default as LoadingOverlay } from './LoadingOverlay.svelte';
11
+ export { default as Meter } from './Meter.svelte';
6
12
  export { default as Modal } from './Modal.svelte';
13
+ export { default as Progress } from './Progress.svelte';
7
14
  export { default as ProgressBar } from './ProgressBar.svelte';
8
- // Note: Component prop types (ConfirmDialogProps, etc.) are available
9
- // via svelte-package build output but cannot be re-exported here because
10
- // tsc --noEmit cannot resolve type exports from .svelte files.
15
+ export { default as Spinner } from './Spinner.svelte';
16
+ export { default as ToastViewport } from './ToastViewport.svelte';
17
+ export { createToaster, toaster } from './toast.js';
@@ -0,0 +1,28 @@
1
+ export type ToastVariant = 'info' | 'success' | 'warning' | 'error';
2
+ export interface ToastAction {
3
+ label: string;
4
+ run(): void | Promise<void>;
5
+ }
6
+ export interface Toast {
7
+ id: string;
8
+ title?: string;
9
+ message: string;
10
+ variant: ToastVariant;
11
+ duration: number;
12
+ action?: ToastAction;
13
+ }
14
+ export interface ToastInput extends Partial<Omit<Toast, 'id' | 'message'>> {
15
+ id?: string;
16
+ message: string;
17
+ }
18
+ export interface Toaster {
19
+ show(input: string | ToastInput): string;
20
+ success(message: string, input?: Omit<ToastInput, 'message' | 'variant'>): string;
21
+ error(message: string, input?: Omit<ToastInput, 'message' | 'variant'>): string;
22
+ dismiss(id: string): void;
23
+ clear(): void;
24
+ subscribe(listener: (toasts: Toast[]) => void): () => void;
25
+ }
26
+ export declare function createToaster(): Toaster;
27
+ export declare const toaster: Toaster;
28
+ //# sourceMappingURL=toast.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toast.d.ts","sourceRoot":"","sources":["../../../src/components/feedback/toast.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAEpE,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B;AAED,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,YAAY,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,UAAW,SAAQ,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,GAAG,SAAS,CAAC,CAAC;IACxE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC;IACzC,OAAO,CACL,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,SAAS,GAAG,SAAS,CAAC,GAC9C,MAAM,CAAC;IACV,KAAK,CACH,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,SAAS,GAAG,SAAS,CAAC,GAC9C,MAAM,CAAC;IACV,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,IAAI,IAAI,CAAC;IACd,SAAS,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CAC5D;AAID,wBAAgB,aAAa,IAAI,OAAO,CA0DvC;AAED,eAAO,MAAM,OAAO,SAAkB,CAAC"}
@@ -0,0 +1,58 @@
1
+ let nextToastId = 0;
2
+ export function createToaster() {
3
+ let toasts = [];
4
+ const listeners = new Set();
5
+ const timers = new Map();
6
+ const publish = () => {
7
+ for (const listener of listeners)
8
+ listener([...toasts]);
9
+ };
10
+ function dismiss(id) {
11
+ const timer = timers.get(id);
12
+ if (timer)
13
+ clearTimeout(timer);
14
+ timers.delete(id);
15
+ toasts = toasts.filter((toast) => toast.id !== id);
16
+ publish();
17
+ }
18
+ function show(input) {
19
+ const normalized = typeof input === 'string' ? { message: input } : input;
20
+ const id = normalized.id ?? `toast-${++nextToastId}`;
21
+ const existingTimer = timers.get(id);
22
+ if (existingTimer)
23
+ clearTimeout(existingTimer);
24
+ timers.delete(id);
25
+ const toast = {
26
+ id,
27
+ message: normalized.message,
28
+ title: normalized.title,
29
+ variant: normalized.variant ?? 'info',
30
+ duration: normalized.duration ?? 5000,
31
+ action: normalized.action,
32
+ };
33
+ toasts = [...toasts.filter((item) => item.id !== id), toast];
34
+ publish();
35
+ if (toast.duration > 0)
36
+ timers.set(id, setTimeout(() => dismiss(id), toast.duration));
37
+ return id;
38
+ }
39
+ return {
40
+ show,
41
+ success: (message, input) => show({ ...input, message, variant: 'success' }),
42
+ error: (message, input) => show({ ...input, message, variant: 'error' }),
43
+ dismiss,
44
+ clear() {
45
+ for (const timer of timers.values())
46
+ clearTimeout(timer);
47
+ timers.clear();
48
+ toasts = [];
49
+ publish();
50
+ },
51
+ subscribe(listener) {
52
+ listeners.add(listener);
53
+ listener([...toasts]);
54
+ return () => listeners.delete(listener);
55
+ },
56
+ };
57
+ }
58
+ export const toaster = createToaster();
@@ -0,0 +1,149 @@
1
+ <script lang="ts">
2
+ import type { HTMLInputAttributes } from 'svelte/elements';
3
+ import {
4
+ emitControlChange,
5
+ highlightControl,
6
+ revealControl,
7
+ } from './control-dom.js';
8
+ import type { ControlInteractionOptions } from './control-interaction.js';
9
+ import { tryGetControlInteractionContext } from './control-interaction-context.js';
10
+ import { tryGetFormGroupContext } from './form-group-context.js';
11
+ import { useControlRegistration } from './use-control-registration.svelte.js';
12
+
13
+ export interface Props
14
+ extends Omit<HTMLInputAttributes, 'type' | 'class' | 'checked'> {
15
+ checked?: boolean;
16
+ indeterminate?: boolean;
17
+ label?: string;
18
+ class?: string;
19
+ interaction?: ControlInteractionOptions | false;
20
+ }
21
+
22
+ let {
23
+ checked = $bindable(false),
24
+ indeterminate = false,
25
+ label,
26
+ id,
27
+ name,
28
+ value,
29
+ disabled = false,
30
+ required = false,
31
+ class: className = '',
32
+ interaction,
33
+ onchange,
34
+ 'aria-label': ariaLabel,
35
+ 'aria-describedby': ariaDescribedby,
36
+ ...rest
37
+ }: Props = $props();
38
+
39
+ const instanceId = $props.id();
40
+ const formGroup = tryGetFormGroupContext();
41
+ const interactionContext = tryGetControlInteractionContext();
42
+ let inputEl = $state<HTMLInputElement | null>(null);
43
+ const resolvedId = $derived(
44
+ id ?? formGroup?.().inputId ?? `smrt-checkbox-${instanceId}`,
45
+ );
46
+ const resolvedDescription = $derived(
47
+ ariaDescribedby ?? formGroup?.().describedBy,
48
+ );
49
+ const resolvedInteraction = $derived.by(() => {
50
+ const inherited = formGroup?.().interaction;
51
+ if (interaction === false || inherited === false) return false;
52
+ return { ...(inherited ?? {}), ...(interaction ?? {}) };
53
+ });
54
+ const controlId = $derived(
55
+ resolvedInteraction === false
56
+ ? undefined
57
+ : (resolvedInteraction.id ?? name ?? resolvedId),
58
+ );
59
+
60
+ $effect(() => {
61
+ if (inputEl) inputEl.indeterminate = indeterminate;
62
+ });
63
+
64
+ function setChecked(next: unknown) {
65
+ checked =
66
+ typeof next === 'string'
67
+ ? ['true', '1', 'yes', 'on'].includes(next.toLowerCase())
68
+ : Boolean(next);
69
+ if (inputEl) emitControlChange(inputEl);
70
+ }
71
+
72
+ function handleChange(event: Event & { currentTarget: HTMLInputElement }) {
73
+ checked = event.currentTarget.checked;
74
+ onchange?.(event);
75
+ }
76
+
77
+ useControlRegistration(() => {
78
+ const element = inputEl;
79
+ const options = resolvedInteraction;
80
+ if (!element || options === false) return false;
81
+ return {
82
+ controlId,
83
+ subject: options.subject,
84
+ metadata: {
85
+ kind: 'checkbox',
86
+ label: formGroup?.().label ?? label ?? ariaLabel ?? undefined,
87
+ description: options.description ?? formGroup?.().description,
88
+ sensitivity: options.sensitivity ?? 'public',
89
+ readable: options.readable,
90
+ writable: options.writable,
91
+ constraints: { required: required === true },
92
+ },
93
+ getValue: () => checked,
94
+ setValue: setChecked,
95
+ clear: () => setChecked(false),
96
+ focus: () => element.focus(),
97
+ reveal: () => revealControl(element),
98
+ highlight: (durationMs) =>
99
+ highlightControl(element.closest('label') ?? element, durationMs),
100
+ validate: () => element.reportValidity(),
101
+ getState: () => ({
102
+ disabled: element.disabled,
103
+ readonly: false,
104
+ valid: element.validity.valid,
105
+ validationMessage: element.validationMessage || undefined,
106
+ }),
107
+ };
108
+ });
109
+ </script>
110
+
111
+ <label
112
+ class="checkbox {className}"
113
+ class:checkbox--disabled={disabled}
114
+ data-smrt-control={controlId}
115
+ data-smrt-form={interactionContext?.formId}
116
+ >
117
+ <input
118
+ bind:this={inputEl}
119
+ id={resolvedId}
120
+ type="checkbox"
121
+ {name}
122
+ {value}
123
+ {disabled}
124
+ {required}
125
+ checked={checked}
126
+ aria-label={ariaLabel ?? (!label && !formGroup ? 'Checkbox' : undefined)}
127
+ aria-describedby={resolvedDescription}
128
+ aria-invalid={formGroup?.().invalid ? 'true' : undefined}
129
+ onchange={handleChange}
130
+ {...rest}
131
+ />
132
+ <span class="checkbox__box" aria-hidden="true">
133
+ {#if indeterminate}<span class="checkbox__dash"></span>{:else if checked}<span class="checkbox__check">✓</span>{/if}
134
+ </span>
135
+ {#if label}<span class="checkbox__label">{label}</span>{/if}
136
+ </label>
137
+
138
+ <style>
139
+ .checkbox { display: inline-flex; align-items: center; gap: var(--smrt-spacing-2, .5rem); cursor: pointer; color: var(--smrt-color-on-surface); }
140
+ .checkbox--disabled { opacity: .5; cursor: not-allowed; }
141
+ input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
142
+ .checkbox__box { width: 1.125rem; height: 1.125rem; display: grid; place-items: center; border: 2px solid var(--smrt-color-outline); border-radius: var(--smrt-radius-extra-small, 3px); background: var(--smrt-color-surface); transition: background var(--smrt-duration-short2), border-color var(--smrt-duration-short2); }
143
+ input:checked + .checkbox__box, input:indeterminate + .checkbox__box { background: var(--smrt-color-primary); border-color: var(--smrt-color-primary); color: var(--smrt-color-on-primary); }
144
+ input:focus-visible + .checkbox__box { outline: 2px solid var(--smrt-color-primary); outline-offset: 3px; }
145
+ .checkbox__check { font-size: .8rem; font-weight: 800; line-height: 1; }
146
+ .checkbox__dash { width: .6rem; height: 2px; background: currentColor; }
147
+ .checkbox[data-smrt-highlighted='true'] { outline: 3px solid var(--smrt-color-tertiary); outline-offset: 4px; border-radius: var(--smrt-radius-small); }
148
+ @media (prefers-reduced-motion: reduce) { .checkbox__box { transition: none; } }
149
+ </style>
@@ -0,0 +1,13 @@
1
+ import type { HTMLInputAttributes } from 'svelte/elements';
2
+ import type { ControlInteractionOptions } from './control-interaction.js';
3
+ export interface Props extends Omit<HTMLInputAttributes, 'type' | 'class' | 'checked'> {
4
+ checked?: boolean;
5
+ indeterminate?: boolean;
6
+ label?: string;
7
+ class?: string;
8
+ interaction?: ControlInteractionOptions | false;
9
+ }
10
+ declare const Checkbox: import("svelte").Component<Props, {}, "checked">;
11
+ type Checkbox = ReturnType<typeof Checkbox>;
12
+ export default Checkbox;
13
+ //# sourceMappingURL=Checkbox.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Checkbox.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/Checkbox.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAM3D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAM1E,MAAM,WAAW,KACf,SAAQ,IAAI,CAAC,mBAAmB,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;IAC/D,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,yBAAyB,GAAG,KAAK,CAAC;CACjD;AA+GD,QAAA,MAAM,QAAQ,kDAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}