@happyvertical/smrt-ui 0.51.39 → 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 (392) hide show
  1. package/AGENTS.md +25 -3
  2. package/README.md +558 -8
  3. package/dist/actions/__tests__/swipe-dismiss.test.js +69 -0
  4. package/dist/actions/swipe-dismiss.d.ts +42 -0
  5. package/dist/actions/swipe-dismiss.d.ts.map +1 -0
  6. package/dist/actions/swipe-dismiss.js +69 -0
  7. package/dist/components/calendar/Calendar.svelte +5 -0
  8. package/dist/components/calendar/Calendar.svelte.d.ts.map +1 -1
  9. package/dist/components/calendar/CalendarView.svelte +1250 -0
  10. package/dist/components/calendar/CalendarView.svelte.d.ts +66 -0
  11. package/dist/components/calendar/CalendarView.svelte.d.ts.map +1 -0
  12. package/dist/components/calendar/DayView.svelte +5 -0
  13. package/dist/components/calendar/DayView.svelte.d.ts.map +1 -1
  14. package/dist/components/calendar/__tests__/CalendarView.test.js +226 -0
  15. package/dist/components/calendar/__tests__/calendar-model.test.js +138 -0
  16. package/dist/components/calendar/calendar-model.d.ts +178 -0
  17. package/dist/components/calendar/calendar-model.d.ts.map +1 -0
  18. package/dist/components/calendar/calendar-model.js +362 -0
  19. package/dist/components/calendar/index.d.ts +12 -1
  20. package/dist/components/calendar/index.d.ts.map +1 -1
  21. package/dist/components/calendar/index.js +11 -1
  22. package/dist/components/data/CollectionList.svelte +23 -5
  23. package/dist/components/data/CollectionList.svelte.d.ts +8 -2
  24. package/dist/components/data/CollectionList.svelte.d.ts.map +1 -1
  25. package/dist/components/data/DataTable.svelte +178 -55
  26. package/dist/components/data/DataTable.svelte.d.ts.map +1 -1
  27. package/dist/components/data/DataTableController.d.ts +4 -0
  28. package/dist/components/data/DataTableController.d.ts.map +1 -1
  29. package/dist/components/data/DataTableController.js +11 -4
  30. package/dist/components/data/DataTableResponsive.d.ts +4 -0
  31. package/dist/components/data/DataTableResponsive.d.ts.map +1 -0
  32. package/dist/components/data/DataTableResponsive.js +22 -0
  33. package/dist/components/data/ListSortSelect.svelte +121 -0
  34. package/dist/components/data/ListSortSelect.svelte.d.ts +22 -0
  35. package/dist/components/data/ListSortSelect.svelte.d.ts.map +1 -0
  36. package/dist/components/data/SortableHeader.svelte +169 -0
  37. package/dist/components/data/SortableHeader.svelte.d.ts +30 -0
  38. package/dist/components/data/SortableHeader.svelte.d.ts.map +1 -0
  39. package/dist/components/data/__tests__/CollectionList.test.js +37 -0
  40. package/dist/components/data/__tests__/DataTable.test.js +76 -0
  41. package/dist/components/data/__tests__/DataTableController.test.js +35 -0
  42. package/dist/components/data/__tests__/DataTableResponsive.test.js +80 -0
  43. package/dist/components/data/__tests__/SortableHeader.test.js +101 -0
  44. package/dist/components/data/__tests__/link-step-surface.test.js +172 -0
  45. package/dist/components/data/__tests__/list-sort-state.svelte.test.js +71 -0
  46. package/dist/components/data/__tests__/list-sort.test.js +91 -0
  47. package/dist/components/data/__tests__/responsive-table.fixture.svelte +29 -0
  48. package/dist/components/data/__tests__/responsive-table.fixture.svelte.d.ts +7 -0
  49. package/dist/components/data/__tests__/responsive-table.fixture.svelte.d.ts.map +1 -0
  50. package/dist/components/data/__tests__/surface-navigation.test.js +121 -0
  51. package/dist/components/data/data-surface.d.ts +1 -0
  52. package/dist/components/data/data-surface.d.ts.map +1 -1
  53. package/dist/components/data/data-surface.js +1 -0
  54. package/dist/components/data/index.d.ts +6 -0
  55. package/dist/components/data/index.d.ts.map +1 -1
  56. package/dist/components/data/index.js +6 -0
  57. package/dist/components/data/link-surface.d.ts +79 -0
  58. package/dist/components/data/link-surface.d.ts.map +1 -0
  59. package/dist/components/data/link-surface.js +155 -0
  60. package/dist/components/data/list-sort-state.svelte.d.ts +39 -0
  61. package/dist/components/data/list-sort-state.svelte.d.ts.map +1 -0
  62. package/dist/components/data/list-sort-state.svelte.js +47 -0
  63. package/dist/components/data/list-sort.d.ts +94 -0
  64. package/dist/components/data/list-sort.d.ts.map +1 -0
  65. package/dist/components/data/list-sort.js +182 -0
  66. package/dist/components/data/step-surface.d.ts +73 -0
  67. package/dist/components/data/step-surface.d.ts.map +1 -0
  68. package/dist/components/data/step-surface.js +177 -0
  69. package/dist/components/data/surface-navigation.d.ts +49 -0
  70. package/dist/components/data/surface-navigation.d.ts.map +1 -0
  71. package/dist/components/data/surface-navigation.js +100 -0
  72. package/dist/components/data/types.d.ts +27 -0
  73. package/dist/components/data/types.d.ts.map +1 -1
  74. package/dist/components/display/DateDisplay.svelte +76 -14
  75. package/dist/components/display/DateDisplay.svelte.d.ts +2 -0
  76. package/dist/components/display/DateDisplay.svelte.d.ts.map +1 -1
  77. package/dist/components/display/Icon.svelte +3 -16
  78. package/dist/components/display/Icon.svelte.d.ts +0 -9
  79. package/dist/components/display/Icon.svelte.d.ts.map +1 -1
  80. package/dist/components/display/StatusBadge.svelte +29 -2
  81. package/dist/components/display/StatusBadge.svelte.d.ts +4 -2
  82. package/dist/components/display/StatusBadge.svelte.d.ts.map +1 -1
  83. package/dist/components/display/__tests__/DateDisplay.test.js +82 -1
  84. package/dist/components/display/__tests__/Icon.test.js +132 -2
  85. package/dist/components/display/__tests__/StatusBadge.test.js +78 -0
  86. package/dist/components/display/icons.svelte.d.ts +11 -0
  87. package/dist/components/display/icons.svelte.d.ts.map +1 -0
  88. package/dist/components/display/icons.svelte.js +60 -0
  89. package/dist/components/display/index.d.ts +3 -1
  90. package/dist/components/display/index.d.ts.map +1 -1
  91. package/dist/components/display/index.js +1 -0
  92. package/dist/components/display/types.d.ts +2 -0
  93. package/dist/components/display/types.d.ts.map +1 -1
  94. package/dist/components/feedback/ConfirmDialog.svelte +59 -48
  95. package/dist/components/feedback/ConfirmDialog.svelte.d.ts +14 -2
  96. package/dist/components/feedback/ConfirmDialog.svelte.d.ts.map +1 -1
  97. package/dist/components/feedback/PhoneSheet.svelte +224 -0
  98. package/dist/components/feedback/PhoneSheet.svelte.d.ts +35 -0
  99. package/dist/components/feedback/PhoneSheet.svelte.d.ts.map +1 -0
  100. package/dist/components/feedback/ToastViewport.svelte +156 -8
  101. package/dist/components/feedback/ToastViewport.svelte.d.ts +5 -1
  102. package/dist/components/feedback/ToastViewport.svelte.d.ts.map +1 -1
  103. package/dist/components/feedback/WorkingStrip.svelte +426 -0
  104. package/dist/components/feedback/WorkingStrip.svelte.d.ts +37 -0
  105. package/dist/components/feedback/WorkingStrip.svelte.d.ts.map +1 -0
  106. package/dist/components/feedback/__tests__/ConfirmDialog.test.js +38 -20
  107. package/dist/components/feedback/__tests__/PhoneSheet.test.js +101 -0
  108. package/dist/components/feedback/__tests__/WorkingStrip.test.js +172 -0
  109. package/dist/components/feedback/__tests__/confirm-message.fixture.svelte +9 -0
  110. package/dist/components/feedback/__tests__/confirm-message.fixture.svelte.d.ts +19 -0
  111. package/dist/components/feedback/__tests__/confirm-message.fixture.svelte.d.ts.map +1 -0
  112. package/dist/components/feedback/__tests__/toast.test.js +142 -1
  113. package/dist/components/feedback/index.d.ts +5 -1
  114. package/dist/components/feedback/index.d.ts.map +1 -1
  115. package/dist/components/feedback/index.js +5 -0
  116. package/dist/components/feedback/toast.d.ts +13 -1
  117. package/dist/components/feedback/toast.d.ts.map +1 -1
  118. package/dist/components/feedback/toast.js +51 -10
  119. package/dist/components/feedback/working-status.d.ts +34 -0
  120. package/dist/components/feedback/working-status.d.ts.map +1 -0
  121. package/dist/components/feedback/working-status.js +5 -0
  122. package/dist/components/forms/CameraCapture.svelte +619 -0
  123. package/dist/components/forms/CameraCapture.svelte.d.ts +42 -0
  124. package/dist/components/forms/CameraCapture.svelte.d.ts.map +1 -0
  125. package/dist/components/forms/Checkbox.svelte +34 -3
  126. package/dist/components/forms/Checkbox.svelte.d.ts +2 -0
  127. package/dist/components/forms/Checkbox.svelte.d.ts.map +1 -1
  128. package/dist/components/forms/Combobox.svelte +108 -12
  129. package/dist/components/forms/Combobox.svelte.d.ts +6 -0
  130. package/dist/components/forms/Combobox.svelte.d.ts.map +1 -1
  131. package/dist/components/forms/DictationButton.svelte +128 -0
  132. package/dist/components/forms/DictationButton.svelte.d.ts +19 -0
  133. package/dist/components/forms/DictationButton.svelte.d.ts.map +1 -0
  134. package/dist/components/forms/DictationStatus.svelte +164 -0
  135. package/dist/components/forms/DictationStatus.svelte.d.ts +11 -0
  136. package/dist/components/forms/DictationStatus.svelte.d.ts.map +1 -0
  137. package/dist/components/forms/Fieldset.svelte +5 -1
  138. package/dist/components/forms/Fieldset.svelte.d.ts +2 -0
  139. package/dist/components/forms/Fieldset.svelte.d.ts.map +1 -1
  140. package/dist/components/forms/Form.svelte +5 -0
  141. package/dist/components/forms/Form.svelte.d.ts +1 -0
  142. package/dist/components/forms/Form.svelte.d.ts.map +1 -1
  143. package/dist/components/forms/FormActionBar.svelte +84 -0
  144. package/dist/components/forms/FormActionBar.svelte.d.ts +23 -0
  145. package/dist/components/forms/FormActionBar.svelte.d.ts.map +1 -0
  146. package/dist/components/forms/Input.svelte +17 -1
  147. package/dist/components/forms/Input.svelte.d.ts +2 -0
  148. package/dist/components/forms/Input.svelte.d.ts.map +1 -1
  149. package/dist/components/forms/Listbox.svelte +39 -1
  150. package/dist/components/forms/Listbox.svelte.d.ts.map +1 -1
  151. package/dist/components/forms/MultiSelect.svelte +41 -1
  152. package/dist/components/forms/MultiSelect.svelte.d.ts.map +1 -1
  153. package/dist/components/forms/Radio.svelte +16 -4
  154. package/dist/components/forms/RadioGroup.svelte +7 -1
  155. package/dist/components/forms/RadioGroup.svelte.d.ts +2 -0
  156. package/dist/components/forms/RadioGroup.svelte.d.ts.map +1 -1
  157. package/dist/components/forms/SearchInput.svelte +207 -0
  158. package/dist/components/forms/SearchInput.svelte.d.ts +22 -0
  159. package/dist/components/forms/SearchInput.svelte.d.ts.map +1 -0
  160. package/dist/components/forms/SegmentedControl.svelte +152 -11
  161. package/dist/components/forms/SegmentedControl.svelte.d.ts +4 -0
  162. package/dist/components/forms/SegmentedControl.svelte.d.ts.map +1 -1
  163. package/dist/components/forms/Select.svelte +17 -1
  164. package/dist/components/forms/Select.svelte.d.ts +2 -0
  165. package/dist/components/forms/Select.svelte.d.ts.map +1 -1
  166. package/dist/components/forms/SignaturePad.svelte +436 -0
  167. package/dist/components/forms/SignaturePad.svelte.d.ts +32 -0
  168. package/dist/components/forms/SignaturePad.svelte.d.ts.map +1 -0
  169. package/dist/components/forms/StagedControlReview.svelte +21 -12
  170. package/dist/components/forms/StagedControlReview.svelte.d.ts.map +1 -1
  171. package/dist/components/forms/Switch.svelte +18 -5
  172. package/dist/components/forms/Switch.svelte.d.ts +2 -0
  173. package/dist/components/forms/Switch.svelte.d.ts.map +1 -1
  174. package/dist/components/forms/TagsInput.svelte +8 -1
  175. package/dist/components/forms/Textarea.svelte +16 -1
  176. package/dist/components/forms/Textarea.svelte.d.ts +2 -0
  177. package/dist/components/forms/Textarea.svelte.d.ts.map +1 -1
  178. package/dist/components/forms/__tests__/CameraCapture.test.js +504 -0
  179. package/dist/components/forms/__tests__/Combobox.test.js +354 -0
  180. package/dist/components/forms/__tests__/ControlHitArea.test.js +73 -0
  181. package/dist/components/forms/__tests__/DictationButton.test.js +176 -0
  182. package/dist/components/forms/__tests__/Fieldset.test.js +37 -0
  183. package/dist/components/forms/__tests__/Form.test.js +46 -0
  184. package/dist/components/forms/__tests__/FormActionBar.test.js +22 -0
  185. package/dist/components/forms/__tests__/Listbox.test.js +303 -0
  186. package/dist/components/forms/__tests__/MultiSelect.test.js +281 -0
  187. package/dist/components/forms/__tests__/SearchInput.test.js +92 -0
  188. package/dist/components/forms/__tests__/SignaturePad.test.js +338 -0
  189. package/dist/components/forms/__tests__/StagedControlReview.test.js +37 -3
  190. package/dist/components/forms/__tests__/camera-capture-session.test.js +207 -0
  191. package/dist/components/forms/__tests__/capture-form-retry.test.js +166 -0
  192. package/dist/components/forms/__tests__/capture-form.fixture.svelte +38 -0
  193. package/dist/components/forms/__tests__/capture-form.fixture.svelte.d.ts +10 -0
  194. package/dist/components/forms/__tests__/capture-form.fixture.svelte.d.ts.map +1 -0
  195. package/dist/components/forms/__tests__/capture-test-env.d.ts +70 -0
  196. package/dist/components/forms/__tests__/capture-test-env.d.ts.map +1 -0
  197. package/dist/components/forms/__tests__/capture-test-env.js +122 -0
  198. package/dist/components/forms/__tests__/capture-touch.fixture.svelte +15 -0
  199. package/dist/components/forms/__tests__/capture-touch.fixture.svelte.d.ts +8 -0
  200. package/dist/components/forms/__tests__/capture-touch.fixture.svelte.d.ts.map +1 -0
  201. package/dist/components/forms/__tests__/combobox-form.fixture.svelte +29 -0
  202. package/dist/components/forms/__tests__/combobox-form.fixture.svelte.d.ts +14 -0
  203. package/dist/components/forms/__tests__/combobox-form.fixture.svelte.d.ts.map +1 -0
  204. package/dist/components/forms/__tests__/combobox-labels.fixture.svelte +19 -0
  205. package/dist/components/forms/__tests__/combobox-labels.fixture.svelte.d.ts +11 -0
  206. package/dist/components/forms/__tests__/combobox-labels.fixture.svelte.d.ts.map +1 -0
  207. package/dist/components/forms/__tests__/composite-registration.fixture.svelte +29 -0
  208. package/dist/components/forms/__tests__/composite-registration.fixture.svelte.d.ts +8 -0
  209. package/dist/components/forms/__tests__/composite-registration.fixture.svelte.d.ts.map +1 -0
  210. package/dist/components/forms/__tests__/control-interaction.test.js +19 -1
  211. package/dist/components/forms/__tests__/control-proposals.test.js +63 -0
  212. package/dist/components/forms/__tests__/dictation-recording.test.js +582 -0
  213. package/dist/components/forms/__tests__/dictation.test.js +486 -0
  214. package/dist/components/forms/__tests__/form-attachment.fixture.svelte +28 -0
  215. package/dist/components/forms/__tests__/form-attachment.fixture.svelte.d.ts +11 -0
  216. package/dist/components/forms/__tests__/form-attachment.fixture.svelte.d.ts.map +1 -0
  217. package/dist/components/forms/__tests__/form-retry-form.fixture.svelte +31 -0
  218. package/dist/components/forms/__tests__/form-retry-form.fixture.svelte.d.ts +13 -0
  219. package/dist/components/forms/__tests__/form-retry-form.fixture.svelte.d.ts.map +1 -0
  220. package/dist/components/forms/__tests__/form-retry-form.test.js +107 -0
  221. package/dist/components/forms/__tests__/hidden-controls-form-retry.test.js +69 -0
  222. package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte +29 -0
  223. package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte.d.ts +9 -0
  224. package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte.d.ts.map +1 -0
  225. package/dist/components/forms/__tests__/listbox-form.fixture.svelte +27 -0
  226. package/dist/components/forms/__tests__/listbox-form.fixture.svelte.d.ts +13 -0
  227. package/dist/components/forms/__tests__/listbox-form.fixture.svelte.d.ts.map +1 -0
  228. package/dist/components/forms/__tests__/long-press.test.js +175 -0
  229. package/dist/components/forms/__tests__/multiselect-form.fixture.svelte +27 -0
  230. package/dist/components/forms/__tests__/multiselect-form.fixture.svelte.d.ts +13 -0
  231. package/dist/components/forms/__tests__/multiselect-form.fixture.svelte.d.ts.map +1 -0
  232. package/dist/components/forms/__tests__/native-file-field.test.js +177 -0
  233. package/dist/components/forms/__tests__/place-picker.fixture.svelte +33 -0
  234. package/dist/components/forms/__tests__/place-picker.fixture.svelte.d.ts +12 -0
  235. package/dist/components/forms/__tests__/place-picker.fixture.svelte.d.ts.map +1 -0
  236. package/dist/components/forms/__tests__/segmented-posting.fixture.svelte +28 -0
  237. package/dist/components/forms/__tests__/segmented-posting.fixture.svelte.d.ts +12 -0
  238. package/dist/components/forms/__tests__/segmented-posting.fixture.svelte.d.ts.map +1 -0
  239. package/dist/components/forms/__tests__/segmented-posting.test.js +92 -0
  240. package/dist/components/forms/__tests__/signature-pad-logic.test.js +54 -0
  241. package/dist/components/forms/__tests__/touch-density.fixture.svelte +34 -0
  242. package/dist/components/forms/__tests__/touch-density.fixture.svelte.d.ts +8 -0
  243. package/dist/components/forms/__tests__/touch-density.fixture.svelte.d.ts.map +1 -0
  244. package/dist/components/forms/__tests__/touch-density.test.js +66 -0
  245. package/dist/components/forms/audio-capture.d.ts +69 -0
  246. package/dist/components/forms/audio-capture.d.ts.map +1 -0
  247. package/dist/components/forms/audio-capture.js +193 -0
  248. package/dist/components/forms/camera-capture-session.d.ts +81 -0
  249. package/dist/components/forms/camera-capture-session.d.ts.map +1 -0
  250. package/dist/components/forms/camera-capture-session.js +130 -0
  251. package/dist/components/forms/control-interaction.d.ts +6 -0
  252. package/dist/components/forms/control-interaction.d.ts.map +1 -1
  253. package/dist/components/forms/control-interaction.js +12 -1
  254. package/dist/components/forms/control-proposals.d.ts +55 -0
  255. package/dist/components/forms/control-proposals.d.ts.map +1 -0
  256. package/dist/components/forms/control-proposals.js +188 -0
  257. package/dist/components/forms/dictation-transcribe.d.ts +36 -0
  258. package/dist/components/forms/dictation-transcribe.d.ts.map +1 -0
  259. package/dist/components/forms/dictation-transcribe.js +65 -0
  260. package/dist/components/forms/dictation.svelte.d.ts +200 -0
  261. package/dist/components/forms/dictation.svelte.d.ts.map +1 -0
  262. package/dist/components/forms/dictation.svelte.js +686 -0
  263. package/dist/components/forms/form-retry/__tests__/boundaries.test.js +23 -0
  264. package/dist/components/forms/form-retry/__tests__/content-digest.test.js +70 -0
  265. package/dist/components/forms/form-retry/__tests__/controller.test.js +802 -0
  266. package/dist/components/forms/form-retry/__tests__/fake-kit.d.ts +82 -0
  267. package/dist/components/forms/form-retry/__tests__/fake-kit.d.ts.map +1 -0
  268. package/dist/components/forms/form-retry/__tests__/fake-kit.js +180 -0
  269. package/dist/components/forms/form-retry/__tests__/form-draft.test.js +169 -0
  270. package/dist/components/forms/form-retry/__tests__/ssr.test.js +37 -0
  271. package/dist/components/forms/form-retry/__tests__/storage.d.ts +26 -0
  272. package/dist/components/forms/form-retry/__tests__/storage.d.ts.map +1 -0
  273. package/dist/components/forms/form-retry/__tests__/storage.js +63 -0
  274. package/dist/components/forms/form-retry/__tests__/submission-key.test.js +236 -0
  275. package/dist/components/forms/form-retry/__tests__/submitted-draft.test.js +334 -0
  276. package/dist/components/forms/form-retry/content-digest.d.ts +54 -0
  277. package/dist/components/forms/form-retry/content-digest.d.ts.map +1 -0
  278. package/dist/components/forms/form-retry/content-digest.js +169 -0
  279. package/dist/components/forms/form-retry/controller.d.ts +214 -0
  280. package/dist/components/forms/form-retry/controller.d.ts.map +1 -0
  281. package/dist/components/forms/form-retry/controller.js +404 -0
  282. package/dist/components/forms/form-retry/form-draft.d.ts +68 -0
  283. package/dist/components/forms/form-retry/form-draft.d.ts.map +1 -0
  284. package/dist/components/forms/form-retry/form-draft.js +161 -0
  285. package/dist/components/forms/form-retry/index.d.ts +19 -0
  286. package/dist/components/forms/form-retry/index.d.ts.map +1 -0
  287. package/dist/components/forms/form-retry/index.js +16 -0
  288. package/dist/components/forms/form-retry/submission-key.d.ts +244 -0
  289. package/dist/components/forms/form-retry/submission-key.d.ts.map +1 -0
  290. package/dist/components/forms/form-retry/submission-key.js +342 -0
  291. package/dist/components/forms/form-retry/submitted-draft.d.ts +112 -0
  292. package/dist/components/forms/form-retry/submitted-draft.d.ts.map +1 -0
  293. package/dist/components/forms/form-retry/submitted-draft.js +270 -0
  294. package/dist/components/forms/form-retry/types.d.ts +84 -0
  295. package/dist/components/forms/form-retry/types.d.ts.map +1 -0
  296. package/dist/components/forms/form-retry/types.js +13 -0
  297. package/dist/components/forms/index.d.ts +21 -2
  298. package/dist/components/forms/index.d.ts.map +1 -1
  299. package/dist/components/forms/index.js +22 -1
  300. package/dist/components/forms/insert-text.d.ts +12 -0
  301. package/dist/components/forms/insert-text.d.ts.map +1 -0
  302. package/dist/components/forms/insert-text.js +38 -0
  303. package/dist/components/forms/long-press.d.ts +88 -0
  304. package/dist/components/forms/long-press.d.ts.map +1 -0
  305. package/dist/components/forms/long-press.js +213 -0
  306. package/dist/components/forms/native-file-field.d.ts +94 -0
  307. package/dist/components/forms/native-file-field.d.ts.map +1 -0
  308. package/dist/components/forms/native-file-field.js +147 -0
  309. package/dist/components/forms/ready-beep.d.ts +24 -0
  310. package/dist/components/forms/ready-beep.d.ts.map +1 -0
  311. package/dist/components/forms/ready-beep.js +82 -0
  312. package/dist/components/forms/signature-pad-logic.d.ts +50 -0
  313. package/dist/components/forms/signature-pad-logic.d.ts.map +1 -0
  314. package/dist/components/forms/signature-pad-logic.js +48 -0
  315. package/dist/components/forms/types.d.ts +41 -0
  316. package/dist/components/forms/types.d.ts.map +1 -1
  317. package/dist/components/layout/PageHeader.svelte +151 -17
  318. package/dist/components/layout/PageHeader.svelte.d.ts +47 -7
  319. package/dist/components/layout/PageHeader.svelte.d.ts.map +1 -1
  320. package/dist/components/layout/__tests__/PageHeader.test.js +87 -1
  321. package/dist/components/layout/index.d.ts +1 -0
  322. package/dist/components/layout/index.d.ts.map +1 -1
  323. package/dist/components/layout/index.js +1 -0
  324. package/dist/components/layout/page-header-context.d.ts +25 -0
  325. package/dist/components/layout/page-header-context.d.ts.map +1 -0
  326. package/dist/components/layout/page-header-context.js +16 -0
  327. package/dist/components/nav/FilterChips.svelte +8 -1
  328. package/dist/components/nav/FilterChips.svelte.d.ts +2 -0
  329. package/dist/components/nav/FilterChips.svelte.d.ts.map +1 -1
  330. package/dist/components/nav/Tabs.svelte +241 -0
  331. package/dist/components/nav/Tabs.svelte.d.ts +15 -0
  332. package/dist/components/nav/Tabs.svelte.d.ts.map +1 -1
  333. package/dist/components/nav/__tests__/LinkTabs.test.js +117 -0
  334. package/dist/components/nav/index.d.ts +1 -0
  335. package/dist/components/nav/index.d.ts.map +1 -1
  336. package/dist/components/nav/index.js +1 -0
  337. package/dist/components/nav/tabs-overflow.d.ts +12 -0
  338. package/dist/components/nav/tabs-overflow.d.ts.map +1 -0
  339. package/dist/components/nav/tabs-overflow.js +17 -0
  340. package/dist/components/nav/types.d.ts +8 -0
  341. package/dist/components/nav/types.d.ts.map +1 -1
  342. package/dist/components/ui/Button.svelte +16 -2
  343. package/dist/components/ui/Button.svelte.d.ts +2 -0
  344. package/dist/components/ui/Button.svelte.d.ts.map +1 -1
  345. package/dist/components/ui/Card.svelte +14 -5
  346. package/dist/components/ui/Dropdown.svelte +55 -1
  347. package/dist/components/ui/Dropdown.svelte.d.ts +8 -0
  348. package/dist/components/ui/Dropdown.svelte.d.ts.map +1 -1
  349. package/dist/components/ui/IconToggle.svelte +139 -0
  350. package/dist/components/ui/IconToggle.svelte.d.ts +32 -0
  351. package/dist/components/ui/IconToggle.svelte.d.ts.map +1 -0
  352. package/dist/components/ui/IconToggleGroup.svelte +56 -0
  353. package/dist/components/ui/IconToggleGroup.svelte.d.ts +21 -0
  354. package/dist/components/ui/IconToggleGroup.svelte.d.ts.map +1 -0
  355. package/dist/components/ui/Pagination.svelte +1 -1
  356. package/dist/components/ui/Popover.svelte +17 -1
  357. package/dist/components/ui/Popover.svelte.d.ts +8 -0
  358. package/dist/components/ui/Popover.svelte.d.ts.map +1 -1
  359. package/dist/components/ui/__tests__/IconToggle.test.js +59 -0
  360. package/dist/components/ui/__tests__/IconToggleGroup.test.js +30 -0
  361. package/dist/components/ui/__tests__/Popover.test.js +44 -0
  362. package/dist/components/ui/__tests__/dropdown-popover-icon.test.js +125 -0
  363. package/dist/components/ui/__tests__/phone-touch-targets.test.js +39 -0
  364. package/dist/components/ui/index.d.ts +2 -0
  365. package/dist/components/ui/index.d.ts.map +1 -1
  366. package/dist/components/ui/index.js +2 -0
  367. package/dist/i18n/strings.ui.d.ts +65 -0
  368. package/dist/i18n/strings.ui.d.ts.map +1 -1
  369. package/dist/i18n/strings.ui.js +72 -0
  370. package/dist/styles/tokens.css +2 -0
  371. package/dist/svelte/__tests__/playground.test.js +3 -1
  372. package/dist/svelte/playground/CalendarPreview.svelte +140 -0
  373. package/dist/svelte/playground/CalendarPreview.svelte.d.ts +4 -0
  374. package/dist/svelte/playground/CalendarPreview.svelte.d.ts.map +1 -0
  375. package/dist/svelte/playground/CapturePreview.svelte +56 -0
  376. package/dist/svelte/playground/CapturePreview.svelte.d.ts +4 -0
  377. package/dist/svelte/playground/CapturePreview.svelte.d.ts.map +1 -0
  378. package/dist/svelte/playground.d.ts.map +1 -1
  379. package/dist/svelte/playground.js +20 -0
  380. package/dist/themes/ThemeProvider.svelte +9 -0
  381. package/dist/themes/ThemeProvider.svelte.d.ts +2 -0
  382. package/dist/themes/ThemeProvider.svelte.d.ts.map +1 -1
  383. package/dist/themes/css-generator.d.ts.map +1 -1
  384. package/dist/themes/css-generator.js +3 -0
  385. package/dist/themes/styles/glass.css +4 -0
  386. package/dist/themes/styles/happyvertical.css +4 -0
  387. package/dist/themes/styles/material.css +4 -0
  388. package/dist/themes/styles/smrt.css +4 -0
  389. package/dist/themes/styles/studio.css +4 -0
  390. package/dist/types-generic.d.ts +2 -0
  391. package/dist/types-generic.d.ts.map +1 -1
  392. package/package.json +9 -3
@@ -17,6 +17,8 @@ import { useControlRegistration } from './use-control-registration.svelte.js';
17
17
 
18
18
  export interface Props
19
19
  extends Omit<HTMLInputAttributes, 'type' | 'class' | 'checked'> {
20
+ /** Target density; omit to inherit the ThemeProvider setting. */
21
+ density?: 'comfortable' | 'touch';
20
22
  /** Whether the checkbox is checked (bindable). */
21
23
  checked?: boolean;
22
24
  /** Whether the checkbox shows indeterminate state. */
@@ -43,6 +45,7 @@ let {
43
45
  onchange,
44
46
  'aria-label': ariaLabel,
45
47
  'aria-describedby': ariaDescribedby,
48
+ density,
46
49
  ...rest
47
50
  }: Props = $props();
48
51
 
@@ -121,8 +124,9 @@ useControlRegistration(() => {
121
124
  </script>
122
125
 
123
126
  <label
124
- class="checkbox {className}"
127
+ data-density={density} class="checkbox {className}"
125
128
  class:checkbox--disabled={disabled}
129
+ data-smrt-hit-target
126
130
  data-smrt-control={controlId}
127
131
  data-smrt-form={interactionContext?.formId}
128
132
  data-smrt-subject-type={resolvedInteraction === false ? undefined : resolvedInteraction.subject?.type}
@@ -150,14 +154,41 @@ useControlRegistration(() => {
150
154
  </label>
151
155
 
152
156
  <style>
153
- .checkbox { display: inline-flex; align-items: center; gap: var(--smrt-spacing-2, .5rem); cursor: pointer; color: var(--smrt-color-on-surface); }
157
+ /* The visible box stays 18px. A transparent 44x44 hit area (WCAG 2.5.8 / M3 touch target) is
158
+ centred on it with ::before, so clicks nearby still land on the wrapping <label>. It adds no
159
+ layout. The box and the label text sit above every hit area (z-index 1 over 0), so one
160
+ control's hit area never steals a click from another control's box or text.
161
+ The hit area is painted above plain content around it, so it only grows sideways when the
162
+ checkbox stands alone in its parent. Beside anything else (a row title, a link, other text)
163
+ it keeps to the box's own column and grows vertically only. Resize it with
164
+ --smrt-control-hit-size (0px turns it off). */
165
+ .checkbox { --_box: 1.125rem; --_hit: max(var(--smrt-control-hit-size, 2.75rem), var(--_box)); position: relative; display: inline-flex; align-items: center; gap: var(--smrt-spacing-2, .5rem); cursor: pointer; color: var(--smrt-color-on-surface); }
166
+ .checkbox::before { content: ''; position: absolute; z-index: 0; inset-block-start: 50%; inset-inline-start: calc(var(--_box) / 2); inline-size: var(--_hit); block-size: var(--_hit); margin-block-start: calc(var(--_hit) / -2); margin-inline-start: calc(var(--_hit) / -2); }
154
167
  .checkbox--disabled { opacity: .5; cursor: not-allowed; }
155
168
  input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
156
- .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); }
169
+ .checkbox__box { position: relative; z-index: 1; 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); }
170
+ .checkbox__label { position: relative; z-index: 1; }
157
171
  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); }
158
172
  input:focus-visible + .checkbox__box { outline: 2px solid var(--smrt-color-primary); outline-offset: 3px; }
159
173
  .checkbox__check { font-size: .8rem; font-weight: 800; line-height: 1; }
160
174
  .checkbox__dash { width: .6rem; height: 2px; background: currentColor; }
175
+ .checkbox:not(:only-child)::before { inline-size: var(--_box); margin-inline-start: calc(var(--_box) / -2); }
161
176
  .checkbox[data-smrt-highlighted='true'] { outline: 3px solid var(--smrt-color-tertiary); outline-offset: 4px; border-radius: var(--smrt-radius-small); }
177
+
178
+ /* Table cells: neighbouring cells hold links and other controls, so the hit area never leaves
179
+ the cell's column. It grows vertically only (box wide)... */
180
+ :global(:is(td, th)) .checkbox::before { inline-size: var(--_box); margin-inline-start: calc(var(--_box) / -2); }
181
+ /* ...unless the checkbox is the cell's only content: then the whole cell, clipped to its own
182
+ edges, is the hit area (the 44px target the header and every row share). The cell only needs
183
+ a containing block, so :where() keeps that rule at zero specificity: a sticky header or a
184
+ pinned column's own position always wins (a sticky cell contains the hit area too). */
185
+ :global(:where(td:has(> [data-smrt-hit-target]:only-child), th:has(> [data-smrt-hit-target]:only-child))) { position: relative; }
186
+ :global(:is(td, th):has(> [data-smrt-hit-target]:only-child)) > .checkbox { position: static; }
187
+ :global(:is(td, th):has(> [data-smrt-hit-target]:only-child)) > .checkbox::before { inset: 0; inline-size: auto; block-size: auto; margin: 0; }
188
+
162
189
  @media (prefers-reduced-motion: reduce) { .checkbox__box { transition: none; } }
190
+
191
+ .checkbox[data-density='touch'] { --smrt-control-target-min: var(--smrt-touch-target-min, 48px); }
192
+ .checkbox[data-density='comfortable'] { --smrt-control-target-min: 0px; }
193
+ .checkbox { min-height: var(--smrt-control-target-min, 0px); min-width: var(--smrt-control-target-min, 0px); }
163
194
  </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, '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"}
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { untrack } from 'svelte';
2
3
  import {
3
4
  emitControlChange,
4
5
  highlightControl,
@@ -36,6 +37,12 @@ export interface Props {
36
37
  allowCustom?: boolean;
37
38
  /** Interaction options or false to disable all. */
38
39
  interaction?: ControlInteractionOptions | false;
40
+ /**
41
+ * Label for `value` while its option is not in `options` yet (options load
42
+ * asynchronously, or only the current record is known). Without it the
43
+ * field stays empty rather than flashing the raw value (an id).
44
+ */
45
+ valueLabel?: string;
39
46
  /** Callback when the selected value changes. */
40
47
  onvaluechange?: (value: string) => void;
41
48
  /** CSS class to apply to the combobox container. */
@@ -51,6 +58,7 @@ let {
51
58
  required = false,
52
59
  allowCustom = false,
53
60
  interaction,
61
+ valueLabel,
54
62
  onvaluechange,
55
63
  class: className = '',
56
64
  }: Props = $props();
@@ -61,12 +69,25 @@ const interactionContext = tryGetControlInteractionContext();
61
69
  let rootEl = $state<HTMLDivElement | null>(null);
62
70
  let inputEl = $state<HTMLInputElement | null>(null);
63
71
  let open = $state(false);
64
- let query = $state(value);
72
+ const initialValue = untrack(() => value);
73
+ /** The text for a value: its option's label, never a raw id (see valueLabel). */
74
+ function labelForValue(candidate: string): string {
75
+ if (!candidate) return '';
76
+ const option = options.find((item) => String(item.value) === candidate);
77
+ if (option) return option.label;
78
+ if (valueLabel) return valueLabel;
79
+ return allowCustom ? candidate : '';
80
+ }
81
+ let query = $state(untrack(() => labelForValue(value)));
82
+ /** The person has typed since opening: only then does the list filter. */
83
+ let typed = $state(false);
65
84
  let activeIndex = $state(0);
66
85
  const filtered = $derived(
67
- options.filter((option) =>
68
- option.label.toLowerCase().includes(query.toLowerCase()),
69
- ),
86
+ typed
87
+ ? options.filter((option) =>
88
+ option.label.toLowerCase().includes(query.toLowerCase()),
89
+ )
90
+ : options,
70
91
  );
71
92
  const controlId = $derived(
72
93
  interaction === false ? undefined : (interaction?.id ?? name ?? inputId),
@@ -76,6 +97,7 @@ function commit(option: ControlOption, userEdit = false) {
76
97
  const changed = String(option.value) !== value;
77
98
  value = String(option.value);
78
99
  query = option.label;
100
+ typed = false;
79
101
  open = false;
80
102
  onvaluechange?.(value);
81
103
  if (userEdit && changed) {
@@ -94,6 +116,7 @@ function setValue(next: unknown) {
94
116
  else if (allowCustom) {
95
117
  value = candidate;
96
118
  query = candidate;
119
+ typed = false;
97
120
  onvaluechange?.(candidate);
98
121
  }
99
122
  }
@@ -104,8 +127,24 @@ function prepareValue(next: unknown) {
104
127
  if (allowCustom) return candidate;
105
128
  return candidate;
106
129
  }
130
+ function openList() {
131
+ if (open) return;
132
+ open = true;
133
+ // Reopening shows every option, starting at the current choice.
134
+ typed = false;
135
+ const selected = options.findIndex(
136
+ (option) => String(option.value) === value,
137
+ );
138
+ activeIndex = selected >= 0 ? selected : 0;
139
+ }
140
+ function closeList() {
141
+ open = false;
142
+ // Leaving without choosing puts the chosen label back.
143
+ typed = false;
144
+ }
107
145
  function handleInput(event: Event & { currentTarget: HTMLInputElement }) {
108
146
  query = event.currentTarget.value;
147
+ typed = true;
109
148
  open = true;
110
149
  activeIndex = 0;
111
150
  if (allowCustom) {
@@ -116,7 +155,10 @@ function handleInput(event: Event & { currentTarget: HTMLInputElement }) {
116
155
  function handleKeydown(event: KeyboardEvent) {
117
156
  if (event.key === 'ArrowDown') {
118
157
  event.preventDefault();
119
- open = true;
158
+ if (!open) {
159
+ openList();
160
+ return;
161
+ }
120
162
  const enabled = filtered
121
163
  .map((option, index) => (option.disabled ? -1 : index))
122
164
  .filter((index) => index >= 0);
@@ -134,16 +176,55 @@ function handleKeydown(event: KeyboardEvent) {
134
176
  event.preventDefault();
135
177
  commit(filtered[activeIndex], true);
136
178
  } else if (event.key === 'Escape') {
137
- open = false;
179
+ closeList();
138
180
  }
139
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
+ });
140
221
  $effect(() => {
141
222
  if (!open) return;
142
223
  const dismissPointer = (event: PointerEvent) => {
143
- if (rootEl && !rootEl.contains(event.target as Node)) open = false;
224
+ if (rootEl && !rootEl.contains(event.target as Node)) closeList();
144
225
  };
145
226
  const dismissFocus = (event: FocusEvent) => {
146
- if (rootEl && !rootEl.contains(event.target as Node)) open = false;
227
+ if (rootEl && !rootEl.contains(event.target as Node)) closeList();
147
228
  };
148
229
  document.addEventListener('pointerdown', dismissPointer, true);
149
230
  document.addEventListener('focusin', dismissFocus, true);
@@ -152,9 +233,21 @@ $effect(() => {
152
233
  document.removeEventListener('focusin', dismissFocus, true);
153
234
  };
154
235
  });
236
+ // Follow the value (and options that load later) unless the person is typing.
237
+ $effect(() => {
238
+ const label = labelForValue(value);
239
+ if (!typed) query = label;
240
+ });
241
+ // Validity follows the committed `value`, not the visible text: a value whose
242
+ // option has not loaded shows an empty field but is filled in, and text typed
243
+ // without choosing an option is not a value.
155
244
  $effect(() => {
156
- const option = options.find((item) => String(item.value) === value);
157
- if (option && document.activeElement !== inputEl) query = option.label;
245
+ if (!inputEl) return;
246
+ inputEl.setCustomValidity(
247
+ required && !disabled && !value && query.trim() !== ''
248
+ ? 'Choose an option from the list.'
249
+ : '',
250
+ );
158
251
  });
159
252
  useControlRegistration(() => {
160
253
  const root = rootEl;
@@ -203,9 +296,12 @@ useControlRegistration(() => {
203
296
  <div bind:this={rootEl} class="combobox {className}" data-smrt-control={controlId} data-smrt-form={interactionContext?.formId}
204
297
  data-smrt-subject-type={interaction === false ? undefined : interaction?.subject?.type}
205
298
  data-smrt-subject-id={interaction === false ? undefined : interaction?.subject?.id}>
206
- <label for={inputId}>{label}</label><input bind:this={inputEl} id={inputId} {name} role="combobox" autocomplete="off" {placeholder} {disabled} {required} value={query}
299
+ <label for={inputId}>{label}</label>
300
+ <!-- The form posts the committed value (an option id), never the label the person sees. -->
301
+ {#if name}<input type="hidden" {name} {value} {disabled} />{/if}
302
+ <input bind:this={inputEl} id={inputId} role="combobox" autocomplete="off" {placeholder} {disabled} required={required && !value} aria-required={required ? 'true' : undefined} value={query}
207
303
  aria-expanded={open} aria-controls={listId} aria-autocomplete="list" aria-activedescendant={open && filtered[activeIndex] ? `${listId}-${activeIndex}` : undefined}
208
- onfocus={() => open = true} oninput={handleInput} onkeydown={handleKeydown} />
304
+ onfocus={openList} onclick={openList} oninput={handleInput} onkeydown={handleKeydown} />
209
305
  {#if open && filtered.length}<div id={listId} class="options" role="listbox">{#each filtered as option, index (option.value)}<button id={`${listId}-${index}`} type="button" role="option"
210
306
  aria-selected={String(option.value) === value} class:active={index === activeIndex} disabled={option.disabled} onpointerdown={(event) => event.preventDefault()} onclick={() => commit(option, true)}>{option.label}</button>{/each}</div>{/if}
211
307
  </div>
@@ -18,6 +18,12 @@ export interface Props {
18
18
  allowCustom?: boolean;
19
19
  /** Interaction options or false to disable all. */
20
20
  interaction?: ControlInteractionOptions | false;
21
+ /**
22
+ * Label for `value` while its option is not in `options` yet (options load
23
+ * asynchronously, or only the current record is known). Without it the
24
+ * field stays empty rather than flashing the raw value (an id).
25
+ */
26
+ valueLabel?: string;
21
27
  /** Callback when the selected value changes. */
22
28
  onvaluechange?: (value: string) => void;
23
29
  /** CSS class to apply to the combobox container. */
@@ -1 +1 @@
1
- {"version":3,"file":"Combobox.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/Combobox.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,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,gDAAgD;IAChD,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,oDAAoD;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA+KD,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"}
@@ -0,0 +1,128 @@
1
+ <script lang="ts">
2
+ /**
3
+ * DictationButton — the microphone next to a text field (see `Dictation`).
4
+ *
5
+ * Tap to start listening, tap again to stop. It is always there, so people
6
+ * who cannot (or do not know to) press and hold the field can still speak.
7
+ * 44px target, named by `aria-label`, `aria-pressed` while listening, and a
8
+ * listening dot that does not rely on color alone (the name changes too).
9
+ * Pair it with `DictationStatus` for the words ("Listening…", errors).
10
+ */
11
+ import { M } from '../../i18n/strings.ui.js';
12
+ import { useI18n } from '../../i18n/use-i18n.js';
13
+ import Button from '../ui/Button.svelte';
14
+ import type { Dictation } from './dictation.svelte.js';
15
+ import { primeReadyBeep } from './ready-beep.js';
16
+
17
+ export interface Props {
18
+ /** The dictation this button starts and stops. */
19
+ dictation: Dictation;
20
+ /** Disable the button (for example while the form sends). */
21
+ disabled?: boolean;
22
+ /** Accessible name to start (default "Speak instead of typing"). */
23
+ label?: string;
24
+ /** Accessible name to stop (default "Stop listening"). */
25
+ stopLabel?: string;
26
+ /** Tooltip (default mentions press-and-hold on the text box). */
27
+ title?: string;
28
+ /** Extra class names. */
29
+ class?: string;
30
+ }
31
+
32
+ let {
33
+ dictation,
34
+ disabled = false,
35
+ label,
36
+ stopLabel,
37
+ title,
38
+ class: className = '',
39
+ }: Props = $props();
40
+
41
+ const { t } = useI18n();
42
+ const active = $derived(dictation.active);
43
+ // While a recording is being written down, a tap would do nothing.
44
+ const busy = $derived(dictation.state === 'transcribing');
45
+ const name = $derived(
46
+ active
47
+ ? (stopLabel ?? t(M['ui.dictation.stop']))
48
+ : (label ?? t(M['ui.dictation.start'])),
49
+ );
50
+
51
+ function handleClick() {
52
+ // A tap is a user gesture: wake the audio so the ready beep can play.
53
+ primeReadyBeep();
54
+ void dictation.unlock();
55
+ void dictation.toggle();
56
+ }
57
+ </script>
58
+
59
+ <Button
60
+ type="button"
61
+ variant="ghost"
62
+ size="sm"
63
+ class={`smrt-dictation-button${active ? ' smrt-dictation-button--listening' : ''} ${className}`.trim()}
64
+ aria-label={name}
65
+ aria-pressed={active}
66
+ aria-busy={busy || undefined}
67
+ title={active ? name : (title ?? t(M['ui.dictation.start_hint']))}
68
+ disabled={disabled || busy}
69
+ onclick={handleClick}
70
+ data-dictation-state={dictation.state}
71
+ >
72
+ <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
73
+ <rect x="9" y="2" width="6" height="12" rx="3"></rect>
74
+ <path d="M5 10a7 7 0 0 0 14 0"></path>
75
+ <path d="M12 17v4"></path>
76
+ </svg>
77
+ {#if active}
78
+ <span class="smrt-dictation-dot" aria-hidden="true"></span>
79
+ {/if}
80
+ </Button>
81
+
82
+ <style>
83
+ :global(.smrt-dictation-button) {
84
+ position: relative;
85
+ flex-shrink: 0;
86
+ display: inline-flex;
87
+ align-items: center;
88
+ justify-content: center;
89
+ inline-size: 44px;
90
+ block-size: 44px;
91
+ min-inline-size: 44px;
92
+ padding: 0;
93
+ border-radius: var(--smrt-radius-full, 9999px);
94
+ color: var(--smrt-color-on-surface-variant, #43474e);
95
+ }
96
+
97
+ :global(.smrt-dictation-button--listening) {
98
+ background: var(--smrt-color-error-container, #ffdad6);
99
+ color: var(--smrt-color-on-error-container, #410002);
100
+ }
101
+
102
+ .smrt-dictation-dot {
103
+ position: absolute;
104
+ inset-block-start: 6px;
105
+ inset-inline-end: 6px;
106
+ inline-size: 10px;
107
+ block-size: 10px;
108
+ border-radius: 50%;
109
+ background: var(--smrt-color-error, #ba1a1a);
110
+ animation: smrt-dictation-pulse 1.2s ease-in-out infinite;
111
+ }
112
+
113
+ @keyframes smrt-dictation-pulse {
114
+ 0%,
115
+ 100% {
116
+ opacity: 1;
117
+ }
118
+ 50% {
119
+ opacity: 0.35;
120
+ }
121
+ }
122
+
123
+ @media (prefers-reduced-motion: reduce) {
124
+ .smrt-dictation-dot {
125
+ animation: none;
126
+ }
127
+ }
128
+ </style>
@@ -0,0 +1,19 @@
1
+ import type { Dictation } from './dictation.svelte.js';
2
+ export interface Props {
3
+ /** The dictation this button starts and stops. */
4
+ dictation: Dictation;
5
+ /** Disable the button (for example while the form sends). */
6
+ disabled?: boolean;
7
+ /** Accessible name to start (default "Speak instead of typing"). */
8
+ label?: string;
9
+ /** Accessible name to stop (default "Stop listening"). */
10
+ stopLabel?: string;
11
+ /** Tooltip (default mentions press-and-hold on the text box). */
12
+ title?: string;
13
+ /** Extra class names. */
14
+ class?: string;
15
+ }
16
+ declare const DictationButton: import("svelte").Component<Props, {}, "">;
17
+ type DictationButton = ReturnType<typeof DictationButton>;
18
+ export default DictationButton;
19
+ //# sourceMappingURL=DictationButton.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DictationButton.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/DictationButton.svelte.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAIvD,MAAM,WAAW,KAAK;IACpB,kDAAkD;IAClD,SAAS,EAAE,SAAS,CAAC;IACrB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yBAAyB;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAoDD,QAAA,MAAM,eAAe,2CAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
@@ -0,0 +1,164 @@
1
+ <script lang="ts">
2
+ /**
3
+ * DictationStatus — what dictation is doing, in words (see `Dictation`).
4
+ *
5
+ * "Listening…" (with the words heard so far) while the microphone is on,
6
+ * "Writing it down…" while a recording is turned into text, and
7
+ * a plain explanation when it cannot listen: the browser cannot turn speech
8
+ * into text, the microphone is blocked, or nothing was heard. Nothing while
9
+ * idle. Listening is announced politely; problems are announced as alerts.
10
+ */
11
+ import { M } from '../../i18n/strings.ui.js';
12
+ import { useI18n } from '../../i18n/use-i18n.js';
13
+ import type { Dictation, DictationErrorKind } from './dictation.svelte.js';
14
+
15
+ export interface Props {
16
+ /** The dictation session whose listening state and messages this shows. */
17
+ dictation: Dictation;
18
+ /** Extra class names. */
19
+ class?: string;
20
+ }
21
+
22
+ let { dictation, class: className = '' }: Props = $props();
23
+
24
+ const { t } = useI18n();
25
+ const showLine = $derived(
26
+ dictation.state === 'starting' ||
27
+ dictation.state === 'listening' ||
28
+ dictation.state === 'stopping' ||
29
+ dictation.state === 'transcribing',
30
+ );
31
+
32
+ function errorText(kind: DictationErrorKind | null): string {
33
+ switch (kind) {
34
+ case 'unsupported':
35
+ return t(M['ui.dictation.unsupported']);
36
+ case 'denied':
37
+ return t(M['ui.dictation.denied']);
38
+ case 'no-speech':
39
+ return t(M['ui.dictation.no_speech']);
40
+ case 'microphone':
41
+ return t(M['ui.dictation.microphone']);
42
+ case 'interrupted':
43
+ return t(M['ui.dictation.interrupted']);
44
+ case 'too-long':
45
+ return t(M['ui.dictation.too_long']);
46
+ case 'not-transcribed':
47
+ return t(M['ui.dictation.not_transcribed']);
48
+ case 'unavailable':
49
+ return t(M['ui.dictation.unavailable']);
50
+ case 'forbidden':
51
+ return t(M['ui.dictation.forbidden']);
52
+ default:
53
+ return t(M['ui.dictation.failed']);
54
+ }
55
+ }
56
+ </script>
57
+
58
+ <div class={`smrt-dictation-status ${className}`.trim()}>
59
+ <p
60
+ class="smrt-dictation-status-line"
61
+ class:smrt-dictation-status--empty={!showLine}
62
+ role="status"
63
+ aria-live="polite"
64
+ >
65
+ {#if dictation.state === 'starting'}
66
+ {t(M['ui.dictation.starting'])}
67
+ {:else if dictation.state === 'listening' || dictation.state === 'stopping'}
68
+ <span class="smrt-dictation-status-dot" aria-hidden="true"></span>
69
+ {t(M['ui.dictation.listening'])}
70
+ {#if dictation.interim}
71
+ <span class="smrt-dictation-status-interim">“{dictation.interim}”</span>
72
+ {/if}
73
+ {:else if dictation.state === 'transcribing'}
74
+ <span class="smrt-dictation-status-spinner" aria-hidden="true"></span>
75
+ {t(M['ui.dictation.transcribing'])}
76
+ {/if}
77
+ </p>
78
+ <p
79
+ class="smrt-dictation-status-error"
80
+ class:smrt-dictation-status--empty={dictation.state !== 'error'}
81
+ role="alert"
82
+ data-dictation-error={dictation.state === 'error'
83
+ ? (dictation.errorCode ?? dictation.errorKind)
84
+ : undefined}
85
+ >
86
+ {#if dictation.state === 'error'}
87
+ {errorText(dictation.errorKind)}
88
+ {/if}
89
+ </p>
90
+ </div>
91
+
92
+ <style>
93
+ .smrt-dictation-status {
94
+ display: contents;
95
+ }
96
+
97
+ .smrt-dictation-status-line,
98
+ .smrt-dictation-status-error {
99
+ margin: 0;
100
+ font: var(--smrt-typography-body-small-font, 0.8125rem / 1.4 sans-serif);
101
+ }
102
+
103
+
104
+ .smrt-dictation-status-line {
105
+ display: flex;
106
+ flex-wrap: wrap;
107
+ align-items: center;
108
+ gap: 0.4rem;
109
+ padding: 0.25rem 0;
110
+ color: var(--smrt-color-on-surface, #1a1c1e);
111
+ }
112
+
113
+ /* Kept mounted while empty (live regions must exist before they speak),
114
+ but take no room. */
115
+ .smrt-dictation-status--empty {
116
+ position: absolute;
117
+ inline-size: 1px;
118
+ block-size: 1px;
119
+ overflow: hidden;
120
+ clip-path: inset(50%);
121
+ padding: 0;
122
+ white-space: nowrap;
123
+ }
124
+
125
+ .smrt-dictation-status-dot {
126
+ inline-size: 8px;
127
+ block-size: 8px;
128
+ border-radius: 50%;
129
+ background: var(--smrt-color-error, #ba1a1a);
130
+ }
131
+
132
+ .smrt-dictation-status-spinner {
133
+ inline-size: 12px;
134
+ block-size: 12px;
135
+ border-radius: 50%;
136
+ border: 2px solid var(--smrt-color-outline-variant, #c4c6cf);
137
+ border-block-start-color: var(--smrt-color-primary, #2f5ea8);
138
+ animation: smrt-dictation-spin 0.9s linear infinite;
139
+ }
140
+
141
+ @keyframes smrt-dictation-spin {
142
+ to {
143
+ transform: rotate(360deg);
144
+ }
145
+ }
146
+
147
+ @media (prefers-reduced-motion: reduce) {
148
+ .smrt-dictation-status-spinner {
149
+ animation: none;
150
+ }
151
+ }
152
+
153
+ .smrt-dictation-status-interim {
154
+ color: var(--smrt-color-on-surface-variant, #43474e);
155
+ font-style: italic;
156
+ }
157
+
158
+ .smrt-dictation-status-error {
159
+ padding: 0.4rem 0.6rem;
160
+ border-radius: var(--smrt-radius-medium, 8px);
161
+ background: var(--smrt-color-error-container, #ffdad6);
162
+ color: var(--smrt-color-on-error-container, #410002);
163
+ }
164
+ </style>
@@ -0,0 +1,11 @@
1
+ import type { Dictation } from './dictation.svelte.js';
2
+ export interface Props {
3
+ /** The dictation session whose listening state and messages this shows. */
4
+ dictation: Dictation;
5
+ /** Extra class names. */
6
+ class?: string;
7
+ }
8
+ declare const DictationStatus: import("svelte").Component<Props, {}, "">;
9
+ type DictationStatus = ReturnType<typeof DictationStatus>;
10
+ export default DictationStatus;
11
+ //# sourceMappingURL=DictationStatus.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DictationStatus.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/DictationStatus.svelte.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,SAAS,EAAsB,MAAM,uBAAuB,CAAC;AAG3E,MAAM,WAAW,KAAK;IACpB,2EAA2E;IAC3E,SAAS,EAAE,SAAS,CAAC;IACrB,yBAAyB;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAwED,QAAA,MAAM,eAAe,2CAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}