@happyvertical/smrt-ui 0.52.0 → 0.53.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 (208) hide show
  1. package/AGENTS.md +11 -1
  2. package/README.md +300 -2
  3. package/dist/components/data/DataTable.svelte +60 -15
  4. package/dist/components/data/DataTable.svelte.d.ts.map +1 -1
  5. package/dist/components/data/DataTableResponsive.d.ts +4 -0
  6. package/dist/components/data/DataTableResponsive.d.ts.map +1 -0
  7. package/dist/components/data/DataTableResponsive.js +22 -0
  8. package/dist/components/data/__tests__/DataTableResponsive.test.js +80 -0
  9. package/dist/components/data/__tests__/responsive-table.fixture.svelte +29 -0
  10. package/dist/components/data/__tests__/responsive-table.fixture.svelte.d.ts +7 -0
  11. package/dist/components/data/__tests__/responsive-table.fixture.svelte.d.ts.map +1 -0
  12. package/dist/components/data/types.d.ts +8 -0
  13. package/dist/components/data/types.d.ts.map +1 -1
  14. package/dist/components/display/DateDisplay.svelte +76 -14
  15. package/dist/components/display/DateDisplay.svelte.d.ts +2 -0
  16. package/dist/components/display/DateDisplay.svelte.d.ts.map +1 -1
  17. package/dist/components/display/Icon.svelte +3 -16
  18. package/dist/components/display/Icon.svelte.d.ts +0 -9
  19. package/dist/components/display/Icon.svelte.d.ts.map +1 -1
  20. package/dist/components/display/StatusBadge.svelte +29 -2
  21. package/dist/components/display/StatusBadge.svelte.d.ts +4 -2
  22. package/dist/components/display/StatusBadge.svelte.d.ts.map +1 -1
  23. package/dist/components/display/__tests__/DateDisplay.test.js +82 -1
  24. package/dist/components/display/__tests__/Icon.test.js +132 -2
  25. package/dist/components/display/__tests__/StatusBadge.test.js +78 -0
  26. package/dist/components/display/icons.svelte.d.ts +11 -0
  27. package/dist/components/display/icons.svelte.d.ts.map +1 -0
  28. package/dist/components/display/icons.svelte.js +60 -0
  29. package/dist/components/display/index.d.ts +3 -1
  30. package/dist/components/display/index.d.ts.map +1 -1
  31. package/dist/components/display/index.js +1 -0
  32. package/dist/components/display/types.d.ts +2 -0
  33. package/dist/components/display/types.d.ts.map +1 -1
  34. package/dist/components/feedback/ConfirmDialog.svelte +59 -48
  35. package/dist/components/feedback/ConfirmDialog.svelte.d.ts +14 -2
  36. package/dist/components/feedback/ConfirmDialog.svelte.d.ts.map +1 -1
  37. package/dist/components/feedback/ToastViewport.svelte +156 -8
  38. package/dist/components/feedback/ToastViewport.svelte.d.ts +5 -1
  39. package/dist/components/feedback/ToastViewport.svelte.d.ts.map +1 -1
  40. package/dist/components/feedback/__tests__/ConfirmDialog.test.js +38 -20
  41. package/dist/components/feedback/__tests__/confirm-message.fixture.svelte +9 -0
  42. package/dist/components/feedback/__tests__/confirm-message.fixture.svelte.d.ts +19 -0
  43. package/dist/components/feedback/__tests__/confirm-message.fixture.svelte.d.ts.map +1 -0
  44. package/dist/components/feedback/__tests__/toast.test.js +142 -1
  45. package/dist/components/feedback/index.d.ts +1 -1
  46. package/dist/components/feedback/index.d.ts.map +1 -1
  47. package/dist/components/feedback/toast.d.ts +13 -1
  48. package/dist/components/feedback/toast.d.ts.map +1 -1
  49. package/dist/components/feedback/toast.js +51 -10
  50. package/dist/components/forms/CameraCapture.svelte +619 -0
  51. package/dist/components/forms/CameraCapture.svelte.d.ts +42 -0
  52. package/dist/components/forms/CameraCapture.svelte.d.ts.map +1 -0
  53. package/dist/components/forms/Checkbox.svelte +8 -1
  54. package/dist/components/forms/Checkbox.svelte.d.ts +2 -0
  55. package/dist/components/forms/Checkbox.svelte.d.ts.map +1 -1
  56. package/dist/components/forms/Combobox.svelte +40 -0
  57. package/dist/components/forms/Combobox.svelte.d.ts.map +1 -1
  58. package/dist/components/forms/Form.svelte +5 -0
  59. package/dist/components/forms/Form.svelte.d.ts +1 -0
  60. package/dist/components/forms/Form.svelte.d.ts.map +1 -1
  61. package/dist/components/forms/Input.svelte +8 -1
  62. package/dist/components/forms/Input.svelte.d.ts +2 -0
  63. package/dist/components/forms/Input.svelte.d.ts.map +1 -1
  64. package/dist/components/forms/Listbox.svelte +39 -1
  65. package/dist/components/forms/Listbox.svelte.d.ts.map +1 -1
  66. package/dist/components/forms/MultiSelect.svelte +41 -1
  67. package/dist/components/forms/MultiSelect.svelte.d.ts.map +1 -1
  68. package/dist/components/forms/Radio.svelte +1 -0
  69. package/dist/components/forms/RadioGroup.svelte +7 -1
  70. package/dist/components/forms/RadioGroup.svelte.d.ts +2 -0
  71. package/dist/components/forms/RadioGroup.svelte.d.ts.map +1 -1
  72. package/dist/components/forms/SegmentedControl.svelte +152 -11
  73. package/dist/components/forms/SegmentedControl.svelte.d.ts +4 -0
  74. package/dist/components/forms/SegmentedControl.svelte.d.ts.map +1 -1
  75. package/dist/components/forms/Select.svelte +8 -1
  76. package/dist/components/forms/Select.svelte.d.ts +2 -0
  77. package/dist/components/forms/Select.svelte.d.ts.map +1 -1
  78. package/dist/components/forms/SignaturePad.svelte +436 -0
  79. package/dist/components/forms/SignaturePad.svelte.d.ts +32 -0
  80. package/dist/components/forms/SignaturePad.svelte.d.ts.map +1 -0
  81. package/dist/components/forms/Switch.svelte +8 -1
  82. package/dist/components/forms/Switch.svelte.d.ts +2 -0
  83. package/dist/components/forms/Switch.svelte.d.ts.map +1 -1
  84. package/dist/components/forms/Textarea.svelte +8 -1
  85. package/dist/components/forms/Textarea.svelte.d.ts +2 -0
  86. package/dist/components/forms/Textarea.svelte.d.ts.map +1 -1
  87. package/dist/components/forms/__tests__/CameraCapture.test.js +504 -0
  88. package/dist/components/forms/__tests__/Combobox.test.js +279 -12
  89. package/dist/components/forms/__tests__/Form.test.js +46 -0
  90. package/dist/components/forms/__tests__/Listbox.test.js +303 -0
  91. package/dist/components/forms/__tests__/MultiSelect.test.js +281 -0
  92. package/dist/components/forms/__tests__/SignaturePad.test.js +338 -0
  93. package/dist/components/forms/__tests__/camera-capture-session.test.js +207 -0
  94. package/dist/components/forms/__tests__/capture-form-retry.test.js +166 -0
  95. package/dist/components/forms/__tests__/capture-form.fixture.svelte +38 -0
  96. package/dist/components/forms/__tests__/capture-form.fixture.svelte.d.ts +10 -0
  97. package/dist/components/forms/__tests__/capture-form.fixture.svelte.d.ts.map +1 -0
  98. package/dist/components/forms/__tests__/capture-test-env.d.ts +70 -0
  99. package/dist/components/forms/__tests__/capture-test-env.d.ts.map +1 -0
  100. package/dist/components/forms/__tests__/capture-test-env.js +122 -0
  101. package/dist/components/forms/__tests__/capture-touch.fixture.svelte +15 -0
  102. package/dist/components/forms/__tests__/capture-touch.fixture.svelte.d.ts +8 -0
  103. package/dist/components/forms/__tests__/capture-touch.fixture.svelte.d.ts.map +1 -0
  104. package/dist/components/forms/__tests__/combobox-form.fixture.svelte +29 -0
  105. package/dist/components/forms/__tests__/combobox-form.fixture.svelte.d.ts +14 -0
  106. package/dist/components/forms/__tests__/combobox-form.fixture.svelte.d.ts.map +1 -0
  107. package/dist/components/forms/__tests__/form-attachment.fixture.svelte +28 -0
  108. package/dist/components/forms/__tests__/form-attachment.fixture.svelte.d.ts +11 -0
  109. package/dist/components/forms/__tests__/form-attachment.fixture.svelte.d.ts.map +1 -0
  110. package/dist/components/forms/__tests__/form-retry-form.fixture.svelte +31 -0
  111. package/dist/components/forms/__tests__/form-retry-form.fixture.svelte.d.ts +13 -0
  112. package/dist/components/forms/__tests__/form-retry-form.fixture.svelte.d.ts.map +1 -0
  113. package/dist/components/forms/__tests__/form-retry-form.test.js +107 -0
  114. package/dist/components/forms/__tests__/hidden-controls-form-retry.test.js +69 -0
  115. package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte +29 -0
  116. package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte.d.ts +9 -0
  117. package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte.d.ts.map +1 -0
  118. package/dist/components/forms/__tests__/listbox-form.fixture.svelte +27 -0
  119. package/dist/components/forms/__tests__/listbox-form.fixture.svelte.d.ts +13 -0
  120. package/dist/components/forms/__tests__/listbox-form.fixture.svelte.d.ts.map +1 -0
  121. package/dist/components/forms/__tests__/multiselect-form.fixture.svelte +27 -0
  122. package/dist/components/forms/__tests__/multiselect-form.fixture.svelte.d.ts +13 -0
  123. package/dist/components/forms/__tests__/multiselect-form.fixture.svelte.d.ts.map +1 -0
  124. package/dist/components/forms/__tests__/native-file-field.test.js +177 -0
  125. package/dist/components/forms/__tests__/segmented-posting.fixture.svelte +28 -0
  126. package/dist/components/forms/__tests__/segmented-posting.fixture.svelte.d.ts +12 -0
  127. package/dist/components/forms/__tests__/segmented-posting.fixture.svelte.d.ts.map +1 -0
  128. package/dist/components/forms/__tests__/segmented-posting.test.js +92 -0
  129. package/dist/components/forms/__tests__/signature-pad-logic.test.js +54 -0
  130. package/dist/components/forms/__tests__/touch-density.fixture.svelte +34 -0
  131. package/dist/components/forms/__tests__/touch-density.fixture.svelte.d.ts +8 -0
  132. package/dist/components/forms/__tests__/touch-density.fixture.svelte.d.ts.map +1 -0
  133. package/dist/components/forms/__tests__/touch-density.test.js +66 -0
  134. package/dist/components/forms/camera-capture-session.d.ts +81 -0
  135. package/dist/components/forms/camera-capture-session.d.ts.map +1 -0
  136. package/dist/components/forms/camera-capture-session.js +130 -0
  137. package/dist/components/forms/form-retry/__tests__/boundaries.test.js +23 -0
  138. package/dist/components/forms/form-retry/__tests__/content-digest.test.js +70 -0
  139. package/dist/components/forms/form-retry/__tests__/controller.test.js +802 -0
  140. package/dist/components/forms/form-retry/__tests__/fake-kit.d.ts +82 -0
  141. package/dist/components/forms/form-retry/__tests__/fake-kit.d.ts.map +1 -0
  142. package/dist/components/forms/form-retry/__tests__/fake-kit.js +180 -0
  143. package/dist/components/forms/form-retry/__tests__/form-draft.test.js +169 -0
  144. package/dist/components/forms/form-retry/__tests__/ssr.test.js +37 -0
  145. package/dist/components/forms/form-retry/__tests__/storage.d.ts +26 -0
  146. package/dist/components/forms/form-retry/__tests__/storage.d.ts.map +1 -0
  147. package/dist/components/forms/form-retry/__tests__/storage.js +63 -0
  148. package/dist/components/forms/form-retry/__tests__/submission-key.test.js +236 -0
  149. package/dist/components/forms/form-retry/__tests__/submitted-draft.test.js +334 -0
  150. package/dist/components/forms/form-retry/content-digest.d.ts +54 -0
  151. package/dist/components/forms/form-retry/content-digest.d.ts.map +1 -0
  152. package/dist/components/forms/form-retry/content-digest.js +169 -0
  153. package/dist/components/forms/form-retry/controller.d.ts +214 -0
  154. package/dist/components/forms/form-retry/controller.d.ts.map +1 -0
  155. package/dist/components/forms/form-retry/controller.js +404 -0
  156. package/dist/components/forms/form-retry/form-draft.d.ts +68 -0
  157. package/dist/components/forms/form-retry/form-draft.d.ts.map +1 -0
  158. package/dist/components/forms/form-retry/form-draft.js +161 -0
  159. package/dist/components/forms/form-retry/index.d.ts +19 -0
  160. package/dist/components/forms/form-retry/index.d.ts.map +1 -0
  161. package/dist/components/forms/form-retry/index.js +16 -0
  162. package/dist/components/forms/form-retry/submission-key.d.ts +244 -0
  163. package/dist/components/forms/form-retry/submission-key.d.ts.map +1 -0
  164. package/dist/components/forms/form-retry/submission-key.js +342 -0
  165. package/dist/components/forms/form-retry/submitted-draft.d.ts +112 -0
  166. package/dist/components/forms/form-retry/submitted-draft.d.ts.map +1 -0
  167. package/dist/components/forms/form-retry/submitted-draft.js +270 -0
  168. package/dist/components/forms/form-retry/types.d.ts +84 -0
  169. package/dist/components/forms/form-retry/types.d.ts.map +1 -0
  170. package/dist/components/forms/form-retry/types.js +13 -0
  171. package/dist/components/forms/index.d.ts +7 -1
  172. package/dist/components/forms/index.d.ts.map +1 -1
  173. package/dist/components/forms/index.js +8 -0
  174. package/dist/components/forms/native-file-field.d.ts +94 -0
  175. package/dist/components/forms/native-file-field.d.ts.map +1 -0
  176. package/dist/components/forms/native-file-field.js +147 -0
  177. package/dist/components/forms/signature-pad-logic.d.ts +50 -0
  178. package/dist/components/forms/signature-pad-logic.d.ts.map +1 -0
  179. package/dist/components/forms/signature-pad-logic.js +48 -0
  180. package/dist/components/forms/types.d.ts +41 -0
  181. package/dist/components/forms/types.d.ts.map +1 -1
  182. package/dist/components/nav/FilterChips.svelte +8 -1
  183. package/dist/components/nav/FilterChips.svelte.d.ts +2 -0
  184. package/dist/components/nav/FilterChips.svelte.d.ts.map +1 -1
  185. package/dist/components/ui/Button.svelte +9 -2
  186. package/dist/components/ui/Button.svelte.d.ts +2 -0
  187. package/dist/components/ui/Button.svelte.d.ts.map +1 -1
  188. package/dist/i18n/strings.ui.d.ts +24 -0
  189. package/dist/i18n/strings.ui.d.ts.map +1 -1
  190. package/dist/i18n/strings.ui.js +26 -0
  191. package/dist/styles/tokens.css +2 -0
  192. package/dist/svelte/__tests__/playground.test.js +2 -1
  193. package/dist/svelte/playground/CapturePreview.svelte +56 -0
  194. package/dist/svelte/playground/CapturePreview.svelte.d.ts +4 -0
  195. package/dist/svelte/playground/CapturePreview.svelte.d.ts.map +1 -0
  196. package/dist/svelte/playground.d.ts.map +1 -1
  197. package/dist/svelte/playground.js +10 -0
  198. package/dist/themes/ThemeProvider.svelte +9 -0
  199. package/dist/themes/ThemeProvider.svelte.d.ts +2 -0
  200. package/dist/themes/ThemeProvider.svelte.d.ts.map +1 -1
  201. package/dist/themes/css-generator.d.ts.map +1 -1
  202. package/dist/themes/css-generator.js +3 -0
  203. package/dist/themes/styles/glass.css +4 -0
  204. package/dist/themes/styles/happyvertical.css +4 -0
  205. package/dist/themes/styles/material.css +4 -0
  206. package/dist/themes/styles/smrt.css +4 -0
  207. package/dist/themes/styles/studio.css +4 -0
  208. package/package.json +8 -2
@@ -1,6 +1,8 @@
1
1
  import type { HTMLInputAttributes } from 'svelte/elements';
2
2
  import type { ControlInteractionOptions } from './control-interaction.js';
3
3
  export interface Props extends Omit<HTMLInputAttributes, 'type' | 'class' | 'checked'> {
4
+ /** Target density; omit to inherit the ThemeProvider setting. */
5
+ density?: 'comfortable' | 'touch';
4
6
  /** Whether the checkbox is checked (bindable). */
5
7
  checked?: boolean;
6
8
  /** Whether the checkbox shows indeterminate state. */
@@ -1 +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;AAW1E,MAAM,WAAW,KACf,SAAQ,IAAI,CAAC,mBAAmB,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;IAC/D,kDAAkD;IAClD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,sDAAsD;IACtD,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,iDAAiD;IACjD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,WAAW,CAAC,EAAE,yBAAyB,GAAG,KAAK,CAAC;CACjD;AAkHD,QAAA,MAAM,QAAQ,kDAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
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;AAW1E,MAAM,WAAW,KACf,SAAQ,IAAI,CAAC,mBAAmB,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;IAC/D,iEAAiE;IACjE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC;IAClC,kDAAkD;IAClD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,sDAAsD;IACtD,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,iDAAiD;IACjD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,WAAW,CAAC,EAAE,yBAAyB,GAAG,KAAK,CAAC;CACjD;AAmHD,QAAA,MAAM,QAAQ,kDAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
@@ -69,6 +69,7 @@ const interactionContext = tryGetControlInteractionContext();
69
69
  let rootEl = $state<HTMLDivElement | null>(null);
70
70
  let inputEl = $state<HTMLInputElement | null>(null);
71
71
  let open = $state(false);
72
+ const initialValue = untrack(() => value);
72
73
  /** The text for a value: its option's label, never a raw id (see valueLabel). */
73
74
  function labelForValue(candidate: string): string {
74
75
  if (!candidate) return '';
@@ -178,6 +179,45 @@ function handleKeydown(event: KeyboardEvent) {
178
179
  closeList();
179
180
  }
180
181
  }
182
+ $effect(() => {
183
+ const form = inputEl?.form;
184
+ if (!form) return;
185
+ let disposed = false;
186
+ const timers = new Set<ReturnType<typeof setTimeout>>();
187
+ const reset = (event: Event) => {
188
+ const valueAtDispatch = value;
189
+ const queryAtDispatch = query;
190
+ // Native dispatch can checkpoint microtasks between reset listeners.
191
+ // Wait for the next task so later cancellation and edits win.
192
+ const timer = setTimeout(() => {
193
+ timers.delete(timer);
194
+ if (
195
+ disposed ||
196
+ event.defaultPrevented ||
197
+ value !== valueAtDispatch ||
198
+ query !== queryAtDispatch
199
+ )
200
+ return;
201
+ value = initialValue;
202
+ query = labelForValue(initialValue);
203
+ typed = false;
204
+ open = false;
205
+ recordControlUserEdit(
206
+ interactionContext,
207
+ controlId,
208
+ interaction === false ? undefined : interaction?.subject,
209
+ );
210
+ onvaluechange?.(value);
211
+ });
212
+ timers.add(timer);
213
+ };
214
+ form.addEventListener('reset', reset);
215
+ return () => {
216
+ disposed = true;
217
+ form.removeEventListener('reset', reset);
218
+ for (const timer of timers) clearTimeout(timer);
219
+ };
220
+ });
181
221
  $effect(() => {
182
222
  if (!open) return;
183
223
  const dismissPointer = (event: PointerEvent) => {
@@ -1 +1 @@
1
- {"version":3,"file":"Combobox.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/Combobox.svelte.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,yBAAyB,EACzB,aAAa,EACd,MAAM,0BAA0B,CAAC;AAWlC,MAAM,WAAW,KAAK;IACpB,uCAAuC;IACvC,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,4BAA4B;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,8DAA8D;IAC9D,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,mDAAmD;IACnD,WAAW,CAAC,EAAE,yBAAyB,GAAG,KAAK,CAAC;IAChD;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gDAAgD;IAChD,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,oDAAoD;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAiOD,QAAA,MAAM,QAAQ,gDAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"Combobox.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/Combobox.svelte.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,yBAAyB,EACzB,aAAa,EACd,MAAM,0BAA0B,CAAC;AAWlC,MAAM,WAAW,KAAK;IACpB,uCAAuC;IACvC,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,4BAA4B;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,8DAA8D;IAC9D,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,mDAAmD;IACnD,WAAW,CAAC,EAAE,yBAAyB,GAAG,KAAK,CAAC;IAChD;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gDAAgD;IAChD,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,oDAAoD;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAyQD,QAAA,MAAM,QAAQ,gDAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
@@ -92,6 +92,11 @@ export function getFormId(): string {
92
92
  return resolvedFormId;
93
93
  }
94
94
 
95
+ /** Returns the native form while mounted, or null before mount/after unmount. */
96
+ export function getFormElement(): HTMLFormElement | null {
97
+ return formElement;
98
+ }
99
+
95
100
  function handleSubmit(event: SubmitEvent & { currentTarget: HTMLFormElement }) {
96
101
  if (preventDefault) event.preventDefault();
97
102
  onsubmit?.(event);
@@ -38,6 +38,7 @@ export interface Props extends Omit<HTMLFormAttributes, 'class'> {
38
38
  declare const Form: import("svelte").Component<Props, {
39
39
  getInteractionRegistry: () => ControlInteractionRegistry;
40
40
  getFormId: () => string;
41
+ getFormElement: () => HTMLFormElement | null;
41
42
  }, "">;
42
43
  type Form = ReturnType<typeof Form>;
43
44
  export default Form;
@@ -1 +1 @@
1
- {"version":3,"file":"Form.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/Form.svelte.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EACL,KAAK,uBAAuB,EAC5B,KAAK,0BAA0B,EAEhC,MAAM,0BAA0B,CAAC;AAKlC,MAAM,WAAW,KAAM,SAAQ,IAAI,CAAC,kBAAkB,EAAE,OAAO,CAAC;IAC9D,8CAA8C;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,mBAAmB,CAAC,EAAE,0BAA0B,CAAC;IACjD,sDAAsD;IACtD,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,IAAI,CAAC;IACzD,mEAAmE;IACnE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,iDAAiD;IACjD,QAAQ,EAAE,OAAO,CAAC;CACnB;AAgHD,QAAA,MAAM,IAAI;kCA9D0B,0BAA0B;qBAIvC,MAAM;MA0DqB,CAAC;AACnD,KAAK,IAAI,GAAG,UAAU,CAAC,OAAO,IAAI,CAAC,CAAC;AACpC,eAAe,IAAI,CAAC"}
1
+ {"version":3,"file":"Form.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/Form.svelte.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EACL,KAAK,uBAAuB,EAC5B,KAAK,0BAA0B,EAEhC,MAAM,0BAA0B,CAAC;AAKlC,MAAM,WAAW,KAAM,SAAQ,IAAI,CAAC,kBAAkB,EAAE,OAAO,CAAC;IAC9D,8CAA8C;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,mBAAmB,CAAC,EAAE,0BAA0B,CAAC;IACjD,sDAAsD;IACtD,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,IAAI,CAAC;IACzD,mEAAmE;IACnE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,iDAAiD;IACjD,QAAQ,EAAE,OAAO,CAAC;CACnB;AAqHD,QAAA,MAAM,IAAI;kCAnE0B,0BAA0B;qBAIvC,MAAM;0BAKD,eAAe,GAAG,IAAI;MA0DA,CAAC;AACnD,KAAK,IAAI,GAAG,UAAU,CAAC,OAAO,IAAI,CAAC,CAAC;AACpC,eAAe,IAAI,CAAC"}
@@ -18,6 +18,8 @@ import { tryGetFormGroupContext } from './form-group-context.js';
18
18
  import { useControlRegistration } from './use-control-registration.svelte.js';
19
19
 
20
20
  export interface Props extends Omit<HTMLInputAttributes, 'class' | 'value'> {
21
+ /** Target density; omit to inherit the ThemeProvider setting. */
22
+ density?: 'comfortable' | 'touch';
21
23
  /** Input value as string or number (bindable). */
22
24
  value?: string | number;
23
25
  /** CSS class to apply to the input element. */
@@ -40,6 +42,7 @@ let {
40
42
  'aria-label': ariaLabel,
41
43
  'aria-describedby': ariaDescribedby,
42
44
  'aria-invalid': ariaInvalid,
45
+ density,
43
46
  ...rest
44
47
  }: Props = $props();
45
48
 
@@ -210,7 +213,7 @@ export function getElement(): HTMLInputElement | null {
210
213
  aria-label={ariaLabel}
211
214
  aria-describedby={resolvedDescribedBy}
212
215
  aria-invalid={resolvedInvalid}
213
- class="input {className}"
216
+ data-density={density} class="input {className}"
214
217
  data-smrt-control={resolvedControlId}
215
218
  data-smrt-form={controlInteraction?.formId}
216
219
  data-smrt-subject-type={resolvedInteraction === false ? undefined : resolvedInteraction.subject?.type}
@@ -271,4 +274,8 @@ export function getElement(): HTMLInputElement | null {
271
274
  transition: none;
272
275
  }
273
276
  }
277
+
278
+ .input[data-density='touch'] { --smrt-control-target-min: var(--smrt-touch-target-min, 48px); }
279
+ .input[data-density='comfortable'] { --smrt-control-target-min: 0px; }
280
+ .input { min-height: var(--smrt-control-target-min, 0px); }
274
281
  </style>
@@ -1,6 +1,8 @@
1
1
  import type { HTMLInputAttributes } from 'svelte/elements';
2
2
  import type { ControlInteractionOptions } from './control-interaction.js';
3
3
  export interface Props extends Omit<HTMLInputAttributes, 'class' | 'value'> {
4
+ /** Target density; omit to inherit the ThemeProvider setting. */
5
+ density?: 'comfortable' | 'touch';
4
6
  /** Input value as string or number (bindable). */
5
7
  value?: string | number;
6
8
  /** CSS class to apply to the input element. */
@@ -1 +1 @@
1
- {"version":3,"file":"Input.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/Input.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAM3D,OAAO,KAAK,EACV,yBAAyB,EAE1B,MAAM,0BAA0B,CAAC;AAUlC,MAAM,WAAW,KAAM,SAAQ,IAAI,CAAC,mBAAmB,EAAE,OAAO,GAAG,OAAO,CAAC;IACzE,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,WAAW,CAAC,EAAE,yBAAyB,GAAG,KAAK,CAAC;CACjD;AA4LD,QAAA,MAAM,KAAK;iBAvBQ,IAAI;kBAIH,IAAI;6BAIS,MAAM,KAAG,IAAI;sBAItB,gBAAgB,GAAG,IAAI;WAWI,CAAC;AACpD,KAAK,KAAK,GAAG,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC;AACtC,eAAe,KAAK,CAAC"}
1
+ {"version":3,"file":"Input.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/Input.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAM3D,OAAO,KAAK,EACV,yBAAyB,EAE1B,MAAM,0BAA0B,CAAC;AAUlC,MAAM,WAAW,KAAM,SAAQ,IAAI,CAAC,mBAAmB,EAAE,OAAO,GAAG,OAAO,CAAC;IACzE,iEAAiE;IACjE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC;IAClC,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,WAAW,CAAC,EAAE,yBAAyB,GAAG,KAAK,CAAC;CACjD;AA6LD,QAAA,MAAM,KAAK;iBAvBQ,IAAI;kBAIH,IAAI;6BAIS,MAAM,KAAG,IAAI;sBAItB,gBAAgB,GAAG,IAAI;WAWI,CAAC;AACpD,KAAK,KAAK,GAAG,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC;AACtC,eAAe,KAAK,CAAC"}
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { untrack } from 'svelte';
2
3
  import {
3
4
  emitControlChange,
4
5
  highlightControl,
@@ -13,6 +14,7 @@ import {
13
14
  tryGetControlInteractionContext,
14
15
  } from './control-interaction-context.js';
15
16
  import {
17
+ matchingOption,
16
18
  prepareEnabledOptionValue,
17
19
  validatesEnabledOption,
18
20
  } from './control-value-validation.js';
@@ -49,13 +51,14 @@ const instanceId = $props.id();
49
51
  const interactionContext = tryGetControlInteractionContext();
50
52
  let rootEl = $state<HTMLDivElement | null>(null);
51
53
  let optionEls = $state<Array<HTMLButtonElement | null>>([]);
54
+ const initialValue = untrack(() => value);
52
55
  const controlId = $derived(
53
56
  interaction === false
54
57
  ? undefined
55
58
  : (interaction?.id ?? name ?? `listbox-${instanceId}`),
56
59
  );
57
60
  function select(next: unknown, userEdit = false) {
58
- const option = options.find((item) => String(item.value) === String(next));
61
+ const option = matchingOption(options, next);
59
62
  if (!option || option.disabled || disabled) return;
60
63
  const changed = !Object.is(value, option.value);
61
64
  value = option.value;
@@ -85,6 +88,40 @@ function move(event: KeyboardEvent, index: number) {
85
88
  : (next + 1) % options.length;
86
89
  optionEls[next]?.focus();
87
90
  }
91
+ $effect(() => {
92
+ const form = rootEl?.closest('form');
93
+ if (!form) return;
94
+ let disposed = false;
95
+ const timers = new Set<ReturnType<typeof setTimeout>>();
96
+ const reset = (event: Event) => {
97
+ const valueAtDispatch = value;
98
+ // Native dispatch can checkpoint microtasks between reset listeners.
99
+ // Wait for the next task so later cancellation and edits win.
100
+ const timer = setTimeout(() => {
101
+ timers.delete(timer);
102
+ if (
103
+ disposed ||
104
+ event.defaultPrevented ||
105
+ !Object.is(value, valueAtDispatch)
106
+ )
107
+ return;
108
+ value = initialValue;
109
+ recordControlUserEdit(
110
+ interactionContext,
111
+ controlId,
112
+ interaction === false ? undefined : interaction?.subject,
113
+ );
114
+ if (value !== undefined) onvaluechange?.(value);
115
+ });
116
+ timers.add(timer);
117
+ };
118
+ form.addEventListener('reset', reset);
119
+ return () => {
120
+ disposed = true;
121
+ form.removeEventListener('reset', reset);
122
+ for (const timer of timers) clearTimeout(timer);
123
+ };
124
+ });
88
125
  useControlRegistration(() => {
89
126
  const root = rootEl;
90
127
  if (!root || interaction === false) return false;
@@ -120,6 +157,7 @@ useControlRegistration(() => {
120
157
  <div bind:this={rootEl} class="listbox {className}" role="listbox" aria-label={label} aria-disabled={disabled} data-smrt-control={controlId} data-smrt-form={interactionContext?.formId}
121
158
  data-smrt-subject-type={interaction === false ? undefined : interaction?.subject?.type}
122
159
  data-smrt-subject-id={interaction === false ? undefined : interaction?.subject?.id}>
160
+ {#if name}<input type="hidden" {name} value={value ?? ''} disabled={disabled || matchingOption(options, value)?.disabled} />{/if}
123
161
  {#each options as option, index (option.value)}<button bind:this={optionEls[index]} type="button" role="option" aria-selected={value === option.value}
124
162
  disabled={disabled || option.disabled} tabindex={value === option.value || (value === undefined && index === 0) ? 0 : -1}
125
163
  onkeydown={(event) => move(event, index)} onclick={() => select(option.value, true)}>{option.label}</button>{/each}
@@ -1 +1 @@
1
- {"version":3,"file":"Listbox.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/Listbox.svelte.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,yBAAyB,EACzB,aAAa,EACd,MAAM,0BAA0B,CAAC;AAWlC,MAAM,WAAW,KAAK;IACpB,uCAAuC;IACvC,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,4BAA4B;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mDAAmD;IACnD,WAAW,CAAC,EAAE,yBAAyB,GAAG,KAAK,CAAC;IAChD,gDAAgD;IAChD,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,KAAK,IAAI,CAAC;IACjD,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAiGD,QAAA,MAAM,OAAO,gDAAwC,CAAC;AACtD,KAAK,OAAO,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC;AAC1C,eAAe,OAAO,CAAC"}
1
+ {"version":3,"file":"Listbox.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/Listbox.svelte.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,yBAAyB,EACzB,aAAa,EACd,MAAM,0BAA0B,CAAC;AAYlC,MAAM,WAAW,KAAK;IACpB,uCAAuC;IACvC,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,4BAA4B;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mDAAmD;IACnD,WAAW,CAAC,EAAE,yBAAyB,GAAG,KAAK,CAAC;IAChD,gDAAgD;IAChD,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,KAAK,IAAI,CAAC;IACjD,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAsID,QAAA,MAAM,OAAO,gDAAwC,CAAC;AACtD,KAAK,OAAO,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC;AAC1C,eAAe,OAAO,CAAC"}
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { tick } from 'svelte';
2
+ import { tick, untrack } from 'svelte';
3
3
  import {
4
4
  emitControlChange,
5
5
  highlightControl,
@@ -59,6 +59,7 @@ let rootEl = $state<HTMLDivElement | null>(null);
59
59
  let triggerEl = $state<HTMLButtonElement | null>(null);
60
60
  let optionEls = $state<Array<HTMLButtonElement | null>>([]);
61
61
  let open = $state(false);
62
+ const initialValues = untrack(() => [...values]);
62
63
  const controlId = $derived(
63
64
  interaction === false ? undefined : (interaction?.id ?? name ?? listId),
64
65
  );
@@ -136,6 +137,44 @@ function handleOptionsKeydown(event: KeyboardEvent) {
136
137
  enabled.length;
137
138
  enabled[next]?.focus();
138
139
  }
140
+ $effect(() => {
141
+ const form = triggerEl?.form;
142
+ if (!form) return;
143
+ let disposed = false;
144
+ const timers = new Set<ReturnType<typeof setTimeout>>();
145
+ const reset = (event: Event) => {
146
+ const valuesAtDispatch = values;
147
+ const snapshot = [...values];
148
+ // Native dispatch can checkpoint microtasks between reset listeners.
149
+ // Wait for the next task so later cancellation and edits win.
150
+ const timer = setTimeout(() => {
151
+ timers.delete(timer);
152
+ if (
153
+ disposed ||
154
+ event.defaultPrevented ||
155
+ values !== valuesAtDispatch ||
156
+ values.length !== snapshot.length ||
157
+ values.some((value, index) => !Object.is(value, snapshot[index]))
158
+ )
159
+ return;
160
+ values = [...initialValues];
161
+ closeOptions();
162
+ recordControlUserEdit(
163
+ interactionContext,
164
+ controlId,
165
+ interaction === false ? undefined : interaction?.subject,
166
+ );
167
+ onvalueschange?.(values);
168
+ });
169
+ timers.add(timer);
170
+ };
171
+ form.addEventListener('reset', reset);
172
+ return () => {
173
+ disposed = true;
174
+ form.removeEventListener('reset', reset);
175
+ for (const timer of timers) clearTimeout(timer);
176
+ };
177
+ });
139
178
  $effect(() => {
140
179
  if (!open) return;
141
180
  const dismiss = (event: PointerEvent) => {
@@ -178,6 +217,7 @@ useControlRegistration(() => {
178
217
  <div bind:this={rootEl} class="multi-select {className}" data-smrt-control={controlId} data-smrt-form={interactionContext?.formId}
179
218
  data-smrt-subject-type={interaction === false ? undefined : interaction?.subject?.type}
180
219
  data-smrt-subject-id={interaction === false ? undefined : interaction?.subject?.id}>
220
+ {#if name}{#each canonicalValues as value}<input type="hidden" {name} value={String(value)} disabled={disabled || options.some((option) => Object.is(option.value, value) && option.disabled)} />{/each}{/if}
181
221
  <span class="label" id={`${listId}-label`}>{label}</span><button bind:this={triggerEl} id={triggerId} type="button" class="trigger" {disabled} aria-haspopup="listbox" aria-expanded={open} aria-controls={listId} aria-labelledby={`${listId}-label ${triggerId}`} onclick={() => open ? closeOptions() : openOptions()} onkeydown={handleTriggerKeydown}>{selectedLabels.length ? selectedLabels.join(', ') : placeholder}</button>
182
222
  {#if open}<div id={listId} class="options" role="listbox" tabindex="-1" aria-multiselectable="true" aria-labelledby={`${listId}-label`} onkeydown={handleOptionsKeydown}>{#each options as option, index (option.value)}<button bind:this={optionEls[index]} type="button" role="option" tabindex="-1" aria-selected={canonicalValues.some((value) => Object.is(value, option.value))} disabled={option.disabled} onclick={() => toggle(option)}><span aria-hidden="true">{canonicalValues.some((value) => Object.is(value, option.value)) ? '✓' : ''}</span>{option.label}</button>{/each}</div>{/if}
183
223
  </div>
@@ -1 +1 @@
1
- {"version":3,"file":"MultiSelect.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/MultiSelect.svelte.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,yBAAyB,EACzB,aAAa,EACd,MAAM,0BAA0B,CAAC;AAalC,MAAM,WAAW,KAAK;IACpB,uCAAuC;IACvC,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,mDAAmD;IACnD,MAAM,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;IAChC,0CAA0C;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,4BAA4B;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mDAAmD;IACnD,WAAW,CAAC,EAAE,yBAAyB,GAAG,KAAK,CAAC;IAChD,gDAAgD;IAChD,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,KAAK,IAAI,CAAC;IAC1D,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAwJD,QAAA,MAAM,WAAW,iDAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"MultiSelect.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/MultiSelect.svelte.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,yBAAyB,EACzB,aAAa,EACd,MAAM,0BAA0B,CAAC;AAalC,MAAM,WAAW,KAAK;IACpB,uCAAuC;IACvC,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,mDAAmD;IACnD,MAAM,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;IAChC,0CAA0C;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,4BAA4B;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mDAAmD;IACnD,WAAW,CAAC,EAAE,yBAAyB,GAAG,KAAK,CAAC;IAChD,gDAAgD;IAChD,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,KAAK,IAAI,CAAC;IAC1D,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAgMD,QAAA,MAAM,WAAW,iDAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -42,6 +42,7 @@ function handleChange(event: Event & { currentTarget: HTMLInputElement }) {
42
42
  input:checked + .radio__mark { border-color: var(--smrt-color-primary); }
43
43
  input:checked + .radio__mark::after { transform: scale(1); }
44
44
  input:focus-visible + .radio__mark { outline: 2px solid var(--smrt-color-primary); outline-offset: 3px; }
45
+ .radio { min-height: var(--smrt-control-target-min, 0px); min-width: var(--smrt-control-target-min, 0px); }
45
46
 
46
47
  /* Table cells: grow vertically only, never into a neighbouring cell. */
47
48
  :global(:is(td, th)) .radio::before { inline-size: var(--_box); margin-inline-start: calc(var(--_box) / -2); }
@@ -14,6 +14,8 @@ import {
14
14
  import { setRadioGroupContext } from './radio-group-context.js';
15
15
  import { useControlRegistration } from './use-control-registration.svelte.js';
16
16
  export interface Props {
17
+ /** Target density; omit to inherit the ThemeProvider setting. */
18
+ density?: 'comfortable' | 'touch';
17
19
  /** Identifier for this radio group and its form field. */
18
20
  name: string;
19
21
  /** The currently selected option value. */
@@ -49,6 +51,7 @@ let {
49
51
  onvaluechange,
50
52
  children,
51
53
  class: className = '',
54
+ density,
52
55
  }: Props = $props();
53
56
  const interactionContext = tryGetControlInteractionContext();
54
57
  let fieldsetEl = $state<HTMLFieldSetElement | null>(null);
@@ -133,7 +136,7 @@ useControlRegistration(() => {
133
136
  };
134
137
  });
135
138
  </script>
136
- <fieldset bind:this={fieldsetEl} class="radio-group {className}" {disabled}
139
+ <fieldset bind:this={fieldsetEl} data-density={density} class="radio-group {className}" {disabled}
137
140
  aria-describedby={description ? `${name}-description` : undefined} data-smrt-control={controlId} data-smrt-form={interactionContext?.formId}
138
141
  data-smrt-subject-type={interaction === false ? undefined : interaction?.subject?.type}
139
142
  data-smrt-subject-id={interaction === false ? undefined : interaction?.subject?.id}>
@@ -149,4 +152,7 @@ useControlRegistration(() => {
149
152
  .error { color: var(--smrt-color-error); }
150
153
  .options { display: flex; flex-wrap: wrap; gap: var(--smrt-spacing-3); }
151
154
  :global(.radio-group[data-smrt-highlighted='true']) { outline: 3px solid var(--smrt-color-tertiary); outline-offset: 4px; border-radius: var(--smrt-radius-small); }
155
+
156
+ .radio-group[data-density='touch'] { --smrt-control-target-min: var(--smrt-touch-target-min, 48px); }
157
+ .radio-group[data-density='comfortable'] { --smrt-control-target-min: 0px; }
152
158
  </style>
@@ -1,6 +1,8 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  import type { ControlInteractionOptions } from './control-interaction.js';
3
3
  export interface Props {
4
+ /** Target density; omit to inherit the ThemeProvider setting. */
5
+ density?: 'comfortable' | 'touch';
4
6
  /** Identifier for this radio group and its form field. */
5
7
  name: string;
6
8
  /** The currently selected option value. */
@@ -1 +1 @@
1
- {"version":3,"file":"RadioGroup.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/RadioGroup.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAGtC,OAAO,KAAK,EACV,yBAAyB,EAE1B,MAAM,0BAA0B,CAAC;AASlC,MAAM,WAAW,KAAK;IACpB,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,2CAA2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qDAAqD;IACrD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8CAA8C;IAC9C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2CAA2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yDAAyD;IACzD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mFAAmF;IACnF,WAAW,CAAC,EAAE,yBAAyB,GAAG,KAAK,CAAC;IAChD,oDAAoD;IACpD,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,6CAA6C;IAC7C,QAAQ,EAAE,OAAO,CAAC;IAClB,kCAAkC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAqHD,QAAA,MAAM,UAAU,gDAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"RadioGroup.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/RadioGroup.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAGtC,OAAO,KAAK,EACV,yBAAyB,EAE1B,MAAM,0BAA0B,CAAC;AASlC,MAAM,WAAW,KAAK;IACpB,iEAAiE;IACjE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC;IAClC,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,2CAA2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qDAAqD;IACrD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8CAA8C;IAC9C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2CAA2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yDAAyD;IACzD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mFAAmF;IACnF,WAAW,CAAC,EAAE,yBAAyB,GAAG,KAAK,CAAC;IAChD,oDAAoD;IACpD,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,6CAA6C;IAC7C,QAAQ,EAAE,OAAO,CAAC;IAClB,kCAAkC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAsHD,QAAA,MAAM,UAAU,gDAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { tick, untrack } from 'svelte';
2
3
  import {
3
4
  emitControlChange,
4
5
  highlightControl,
@@ -16,6 +17,8 @@ import {
16
17
  import type { SegmentedControlOption } from './types.js';
17
18
  import { useControlRegistration } from './use-control-registration.svelte.js';
18
19
  export interface Props {
20
+ /** Target density; omit to inherit the ThemeProvider setting. */
21
+ density?: 'comfortable' | 'touch';
19
22
  /** The available segments and their values. */
20
23
  options: SegmentedControlOption[];
21
24
  /** The currently selected option value. */
@@ -24,6 +27,8 @@ export interface Props {
24
27
  label: string;
25
28
  /** Form field identifier. */
26
29
  name?: string;
30
+ /** Requires an enabled option to be selected before native form submission. */
31
+ required?: boolean;
27
32
  /** Blocks interaction and submission of this control. */
28
33
  disabled?: boolean;
29
34
  /** Makes the control stretch to full container width. */
@@ -40,13 +45,128 @@ let {
40
45
  value = $bindable(),
41
46
  label,
42
47
  name,
48
+ required = false,
43
49
  disabled = false,
44
50
  fullWidth = false,
45
51
  interaction,
46
52
  onvaluechange,
47
53
  class: className = '',
54
+ density,
48
55
  }: Props = $props();
49
56
  const instanceId = $props.id();
57
+ const initialValue = untrack(() => value);
58
+ const selectedOption = $derived(
59
+ options.find((option) => option.value === value && !option.disabled),
60
+ );
61
+ const firstEnabledOption = $derived(options.find((option) => !option.disabled));
62
+ const validationOption = $derived(selectedOption ?? firstEnabledOption);
63
+ // Required nameless radio groups need explicit validity in browsers, while
64
+ // remaining unnamed so native FormData never receives a generated field.
65
+ $effect(() => {
66
+ rootEl
67
+ ?.querySelectorAll<HTMLInputElement>('input[type="radio"]')
68
+ .forEach((input) => {
69
+ input.setCustomValidity(
70
+ !name &&
71
+ required &&
72
+ !selectedOption &&
73
+ options[Number(input.dataset.optionIndex)] === validationOption
74
+ ? 'Please select an option.'
75
+ : '',
76
+ );
77
+ });
78
+ });
79
+ function initializeRadio(
80
+ node: HTMLInputElement,
81
+ option: SegmentedControlOption,
82
+ ) {
83
+ node.defaultChecked = initialValue === option.value && !option.disabled;
84
+ }
85
+ function handleKeydown(event: KeyboardEvent) {
86
+ if (
87
+ !(event.target instanceof HTMLInputElement) ||
88
+ event.target.matches(':disabled')
89
+ )
90
+ return;
91
+ const radios = Array.from(
92
+ rootEl?.querySelectorAll<HTMLInputElement>(
93
+ 'input[type="radio"]:not(:disabled)',
94
+ ) ?? [],
95
+ );
96
+ const index = radios.indexOf(event.target);
97
+ let next: number;
98
+ switch (event.key) {
99
+ case 'ArrowRight':
100
+ case 'ArrowDown':
101
+ next = (index + 1) % radios.length;
102
+ break;
103
+ case 'ArrowLeft':
104
+ case 'ArrowUp':
105
+ next = (index - 1 + radios.length) % radios.length;
106
+ break;
107
+ case 'Home':
108
+ next = 0;
109
+ break;
110
+ case 'End':
111
+ next = radios.length - 1;
112
+ break;
113
+ default:
114
+ return;
115
+ }
116
+ const radio = radios[next];
117
+ if (!radio) return;
118
+ event.preventDefault();
119
+ radio.focus();
120
+ setValue(options[Number(radio.dataset.optionIndex)].value, true);
121
+ }
122
+ $effect(() => {
123
+ const root = rootEl;
124
+ if (!root) return;
125
+ let form: HTMLFormElement | null = null;
126
+ let disposed = false;
127
+ const reset = (event: Event) => {
128
+ const valueAtDispatch = value;
129
+ // Native event dispatch may checkpoint microtasks between listeners; wait
130
+ // for the next task so a later form listener can still cancel the reset.
131
+ setTimeout(async () => {
132
+ if (
133
+ event.defaultPrevented ||
134
+ !rootEl?.isConnected ||
135
+ !Object.is(value, valueAtDispatch)
136
+ )
137
+ return;
138
+ const resetOption = options.find(
139
+ (option) => option.value === initialValue && !option.disabled,
140
+ );
141
+ value = resetOption?.value;
142
+ recordControlUserEdit(
143
+ interactionContext,
144
+ controlId,
145
+ interaction === false ? undefined : interaction?.subject,
146
+ );
147
+ await tick();
148
+ // A canceled reset never reaches this reconciliation; unchanged bound values
149
+ // still need checked properties restored after the browser's native reset.
150
+ rootEl
151
+ ?.querySelectorAll<HTMLInputElement>('input[type="radio"]')
152
+ .forEach((input) => {
153
+ input.checked =
154
+ options[Number(input.dataset.optionIndex)]?.value === value &&
155
+ !input.disabled;
156
+ });
157
+ });
158
+ };
159
+ // Parent form ancestry is complete after the mount flush, including in SSR hydration.
160
+ tick().then(() => {
161
+ if (disposed) return;
162
+ form = root.closest('form');
163
+ form?.addEventListener('reset', reset);
164
+ });
165
+ return () => {
166
+ disposed = true;
167
+ form?.removeEventListener('reset', reset);
168
+ };
169
+ });
50
170
  const interactionContext = tryGetControlInteractionContext();
51
171
  let rootEl = $state<HTMLDivElement | null>(null);
52
172
  const controlId = $derived(
@@ -54,7 +174,7 @@ const controlId = $derived(
54
174
  ? undefined
55
175
  : (interaction?.id ?? name ?? `segment-${instanceId}`),
56
176
  );
57
- function setValue(next: unknown, userEdit = false) {
177
+ function setValue(next: unknown, userEdit = false, emitChange = true) {
58
178
  const option = options.find((item) => String(item.value) === String(next));
59
179
  if (!option || option.disabled) return;
60
180
  const changed = !Object.is(value, option.value);
@@ -66,7 +186,7 @@ function setValue(next: unknown, userEdit = false) {
66
186
  controlId,
67
187
  interaction === false ? undefined : interaction?.subject,
68
188
  );
69
- if (rootEl) emitControlChange(rootEl);
189
+ if (rootEl && emitChange) emitControlChange(rootEl);
70
190
  }
71
191
  }
72
192
  useControlRegistration(() => {
@@ -83,6 +203,7 @@ useControlRegistration(() => {
83
203
  readable: interaction?.readable,
84
204
  writable: interaction?.writable,
85
205
  options,
206
+ constraints: { required },
86
207
  },
87
208
  getValue: () => value,
88
209
  prepareValue: (next) => prepareEnabledOptionValue(options, next),
@@ -93,31 +214,51 @@ useControlRegistration(() => {
93
214
  },
94
215
  focus: () =>
95
216
  root
96
- .querySelector<HTMLElement>('[role="radio"]:not([disabled])')
217
+ .querySelector<HTMLElement>('input[type="radio"]:not(:disabled)')
97
218
  ?.focus(),
98
219
  reveal: () => revealControl(root),
99
220
  highlight: (durationMs) => highlightControl(root, durationMs),
221
+ validate: () =>
222
+ root
223
+ .querySelector<HTMLInputElement>('input[type="radio"]:not(:disabled)')
224
+ ?.reportValidity() ?? true,
100
225
  validateValue: (next) => validatesEnabledOption(options, next),
101
226
  getState: () => ({
102
227
  disabled: disabled || root.closest('fieldset:disabled') !== null,
228
+ valid:
229
+ !required ||
230
+ !!selectedOption ||
231
+ disabled ||
232
+ root.closest('fieldset:disabled') !== null,
103
233
  }),
104
234
  };
105
235
  });
106
236
  </script>
107
- <div bind:this={rootEl} class="segmented {className}" class:full-width={fullWidth} role="radiogroup" aria-label={label} data-smrt-control={controlId} data-smrt-form={interactionContext?.formId}
237
+ <div bind:this={rootEl} data-density={density} class="segmented {className}" class:full-width={fullWidth} role="radiogroup" tabindex="-1" aria-label={label} aria-required={required} onkeydown={handleKeydown} data-smrt-control={controlId} data-smrt-form={interactionContext?.formId}
108
238
  data-smrt-subject-type={interaction === false ? undefined : interaction?.subject?.type}
109
239
  data-smrt-subject-id={interaction === false ? undefined : interaction?.subject?.id}>
110
- {#each options as option (option.value)}
111
- <button type="button" role="radio" aria-checked={value === option.value} disabled={disabled || option.disabled}
112
- tabindex={value === option.value || (value === undefined && option === options[0]) ? 0 : -1}
113
- class:selected={value === option.value} onclick={() => setValue(option.value, true)}>{option.label}</button>
240
+ {#each options as option, index (option.value)}
241
+ <label class:selected={selectedOption === option} class:disabled={disabled || option.disabled}>
242
+ <input type="radio" {name} value={option.value} disabled={disabled || option.disabled}
243
+ required={required && validationOption === option} aria-label={option.label}
244
+ checked={selectedOption === option} aria-checked={selectedOption === option}
245
+ tabindex={selectedOption === option || (!selectedOption && firstEnabledOption === option) ? 0 : -1}
246
+ data-option-index={index} use:initializeRadio={option}
247
+ onchange={() => setValue(option.value, true, false)} />
248
+ <span>{option.label}</span>
249
+ </label>
114
250
  {/each}
115
251
  </div>
116
252
  <style>
117
253
  .segmented { display: inline-flex; border: 1px solid var(--smrt-color-outline); border-radius: var(--smrt-radius-full); overflow: hidden; }
118
254
  .segmented.full-width { display: flex; width: 100%; }
119
- button { flex: 1; min-height: 2.5rem; padding: 0 var(--smrt-spacing-4); border: 0; border-right: 1px solid var(--smrt-color-outline); background: var(--smrt-color-surface); color: var(--smrt-color-on-surface); cursor: pointer; }
120
- button:last-child { border-right: 0; } button.selected { background: var(--smrt-color-secondary-container); color: var(--smrt-color-on-secondary-container); }
121
- button:focus-visible { outline: 2px solid var(--smrt-color-primary); outline-offset: -3px; } button:disabled { opacity: .5; cursor: not-allowed; }
255
+ label { position: relative; display: inline-flex; align-items: center; justify-content: center; flex: 1; min-height: 2.5rem; padding: 0 var(--smrt-spacing-4); border: 0; border-right: 1px solid var(--smrt-color-outline); background: var(--smrt-color-surface); color: var(--smrt-color-on-surface); cursor: pointer; }
256
+ label:last-child { border-right: 0; } label.selected { background: var(--smrt-color-secondary-container); color: var(--smrt-color-on-secondary-container); }
257
+ label:has(input:focus-visible) { outline: 2px solid var(--smrt-color-primary); outline-offset: -3px; } label.disabled, label:has(input:disabled) { opacity: .5; cursor: not-allowed; }
122
258
  :global(.segmented[data-smrt-highlighted='true']) { outline: 3px solid var(--smrt-color-tertiary); outline-offset: 4px; }
259
+
260
+ .segmented[data-density='touch'] { --smrt-control-target-min: var(--smrt-touch-target-min, 48px); }
261
+ .segmented[data-density='comfortable'] { --smrt-control-target-min: 0px; }
262
+ label { min-height: max(2.5rem, var(--smrt-control-target-min, 0px)); min-width: var(--smrt-control-target-min, 0px); }
263
+ input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: inherit; }
123
264
  </style>
@@ -1,6 +1,8 @@
1
1
  import type { ControlInteractionOptions } from './control-interaction.js';
2
2
  import type { SegmentedControlOption } from './types.js';
3
3
  export interface Props {
4
+ /** Target density; omit to inherit the ThemeProvider setting. */
5
+ density?: 'comfortable' | 'touch';
4
6
  /** The available segments and their values. */
5
7
  options: SegmentedControlOption[];
6
8
  /** The currently selected option value. */
@@ -9,6 +11,8 @@ export interface Props {
9
11
  label: string;
10
12
  /** Form field identifier. */
11
13
  name?: string;
14
+ /** Requires an enabled option to be selected before native form submission. */
15
+ required?: boolean;
12
16
  /** Blocks interaction and submission of this control. */
13
17
  disabled?: boolean;
14
18
  /** Makes the control stretch to full container width. */
@@ -1 +1 @@
1
- {"version":3,"file":"SegmentedControl.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/SegmentedControl.svelte.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAS1E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAGzD,MAAM,WAAW,KAAK;IACpB,+CAA+C;IAC/C,OAAO,EAAE,sBAAsB,EAAE,CAAC;IAClC,2CAA2C;IAC3C,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,6BAA6B;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yDAAyD;IACzD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,yDAAyD;IACzD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,mFAAmF;IACnF,WAAW,CAAC,EAAE,yBAAyB,GAAG,KAAK,CAAC;IAChD,+CAA+C;IAC/C,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,KAAK,IAAI,CAAC;IACjD,kCAAkC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAuFD,QAAA,MAAM,gBAAgB,gDAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
1
+ {"version":3,"file":"SegmentedControl.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/SegmentedControl.svelte.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAS1E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAGzD,MAAM,WAAW,KAAK;IACpB,iEAAiE;IACjE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC;IAClC,+CAA+C;IAC/C,OAAO,EAAE,sBAAsB,EAAE,CAAC;IAClC,2CAA2C;IAC3C,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,6BAA6B;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,yDAAyD;IACzD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,yDAAyD;IACzD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,mFAAmF;IACnF,WAAW,CAAC,EAAE,yBAAyB,GAAG,KAAK,CAAC;IAChD,+CAA+C;IAC/C,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,KAAK,IAAI,CAAC;IACjD,kCAAkC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAwND,QAAA,MAAM,gBAAgB,gDAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}