@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
@@ -0,0 +1,92 @@
1
+ import { fireEvent, render, screen, waitFor } from '@testing-library/svelte';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { describe, expect, it, vi } from 'vitest';
4
+ import Fixture from './segmented-posting.fixture.svelte';
5
+ function form(container) {
6
+ return container.querySelector('form');
7
+ }
8
+ describe('SegmentedControl native forms', () => {
9
+ it('posts selected values and preserves numeric zero in bindings', async () => {
10
+ const { container } = render(Fixture);
11
+ expect(new FormData(form(container)).get('kind')).toBe('one');
12
+ await userEvent.click(screen.getByRole('radio', { name: 'Zero' }));
13
+ expect(new FormData(form(container)).get('kind')).toBe('0');
14
+ expect(container.querySelector('[data-bound-value]')).toHaveTextContent('0');
15
+ });
16
+ it.each([
17
+ { disabled: true },
18
+ { outerDisabled: true },
19
+ { initial: 'off' },
20
+ { initial: 'missing' },
21
+ { named: false },
22
+ ])('omits disabled, unmatched, or unnamed values %j', (props) => {
23
+ const { container } = render(Fixture, { props });
24
+ expect(new FormData(form(container)).has('kind')).toBe(false);
25
+ });
26
+ it('requires an enabled selection including zero', async () => {
27
+ const { container } = render(Fixture, {
28
+ props: { required: true, initial: 'missing' },
29
+ });
30
+ expect(form(container).checkValidity()).toBe(false);
31
+ await userEvent.click(screen.getByRole('radio', { name: 'Zero' }));
32
+ expect(form(container).checkValidity()).toBe(true);
33
+ });
34
+ it('validates required unnamed controls without posting a generated field', async () => {
35
+ const { container } = render(Fixture, {
36
+ props: { required: true, named: false, initial: 'missing' },
37
+ });
38
+ expect(form(container).checkValidity()).toBe(false);
39
+ await userEvent.click(screen.getByRole('radio', { name: 'Zero' }));
40
+ expect(form(container).checkValidity()).toBe(true);
41
+ expect([...new FormData(form(container)).keys()]).toEqual([]);
42
+ });
43
+ it('resets to the initial selection and binding and honors reset cancellation', async () => {
44
+ const { container } = render(Fixture);
45
+ await userEvent.click(screen.getByRole('radio', { name: 'Zero' }));
46
+ await userEvent.click(screen.getByRole('button', { name: 'Reset' }));
47
+ await waitFor(() => expect(new FormData(form(container)).get('kind')).toBe('one'));
48
+ await waitFor(() => expect(container.querySelector('[data-bound-value]')).toHaveTextContent('one'));
49
+ await userEvent.click(screen.getByRole('radio', { name: 'Zero' }));
50
+ form(container).addEventListener('reset', (e) => e.preventDefault());
51
+ await userEvent.click(screen.getByRole('button', { name: 'Reset' }));
52
+ expect(new FormData(form(container)).get('kind')).toBe('0');
53
+ });
54
+ it('resets a numeric initial value and clears unmatched initial values', async () => {
55
+ const { container, unmount } = render(Fixture, { props: { initial: 0 } });
56
+ await userEvent.click(screen.getByRole('radio', { name: 'One' }));
57
+ form(container).reset();
58
+ await waitFor(() => expect(container.querySelector('[data-bound-value]')).toHaveTextContent('0'));
59
+ expect(container.querySelector('[data-bound-value]')).toHaveAttribute('data-bound-type', 'number');
60
+ unmount();
61
+ const second = render(Fixture, {
62
+ props: { initial: 'missing', required: true },
63
+ });
64
+ await userEvent.click(screen.getByRole('radio', { name: 'One' }));
65
+ form(second.container).reset();
66
+ await waitFor(() => expect(second.container.querySelector('[data-bound-value]')).toHaveTextContent('undefined'));
67
+ expect(form(second.container).checkValidity()).toBe(false);
68
+ expect(new FormData(form(second.container)).has('kind')).toBe(false);
69
+ });
70
+ it('emits one native change and one value callback per pointer selection', async () => {
71
+ const onvaluechange = vi.fn();
72
+ const { container } = render(Fixture, { props: { onvaluechange } });
73
+ const onchange = vi.fn();
74
+ form(container).addEventListener('change', onchange);
75
+ await userEvent.click(screen.getByRole('radio', { name: 'Zero' }));
76
+ expect(onvaluechange).toHaveBeenCalledExactlyOnceWith(0);
77
+ expect(onchange).toHaveBeenCalledTimes(1);
78
+ });
79
+ it('supports arrow/home/end navigation while skipping disabled options', async () => {
80
+ render(Fixture);
81
+ const one = screen.getByRole('radio', { name: 'One' });
82
+ one.focus();
83
+ await fireEvent.keyDown(one, { key: 'ArrowRight' });
84
+ expect(screen.getByRole('radio', { name: 'Zero' })).toBeChecked();
85
+ expect(screen.getByRole('radio', { name: 'Zero' })).toHaveFocus();
86
+ await fireEvent.keyDown(screen.getByRole('radio', { name: 'Zero' }), {
87
+ key: 'Home',
88
+ });
89
+ expect(one).toBeChecked();
90
+ expect(screen.getByRole('radio', { name: 'Disabled' })).toBeDisabled();
91
+ });
92
+ });
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Pure SignaturePad input logic, no DOM (smrt#3290). Ported from teamworks-os
3
+ * `signature-pad-logic.test.ts`.
4
+ */
5
+ import { describe, expect, it } from 'vitest';
6
+ import { isAcceptedPointerType, mapPointerToCanvasPoint, } from '../signature-pad-logic.js';
7
+ describe('isAcceptedPointerType', () => {
8
+ it('accepts only pen input when stylusOnly is true', () => {
9
+ expect(isAcceptedPointerType('pen', true)).toBe(true);
10
+ expect(isAcceptedPointerType('touch', true)).toBe(false);
11
+ expect(isAcceptedPointerType('mouse', true)).toBe(false);
12
+ });
13
+ it('accepts pen, touch, and mouse when stylusOnly is false', () => {
14
+ expect(isAcceptedPointerType('pen', false)).toBe(true);
15
+ expect(isAcceptedPointerType('touch', false)).toBe(true);
16
+ expect(isAcceptedPointerType('mouse', false)).toBe(true);
17
+ });
18
+ it('rejects unknown and empty pointer types in both modes', () => {
19
+ for (const stylusOnly of [true, false]) {
20
+ expect(isAcceptedPointerType('unknown', stylusOnly)).toBe(false);
21
+ expect(isAcceptedPointerType('', stylusOnly)).toBe(false);
22
+ }
23
+ });
24
+ });
25
+ describe('mapPointerToCanvasPoint', () => {
26
+ it('subtracts the rect offset unscaled when display and backing sizes match', () => {
27
+ const rect = { left: 10, top: 20, width: 600, height: 240 };
28
+ expect(mapPointerToCanvasPoint(110, 70, rect, 600, 240)).toEqual({
29
+ x: 100,
30
+ y: 50,
31
+ });
32
+ });
33
+ it('scales up when displayed smaller than the backing resolution', () => {
34
+ const rect = { left: 0, top: 0, width: 300, height: 120 };
35
+ expect(mapPointerToCanvasPoint(30, 12, rect, 600, 240)).toEqual({
36
+ x: 60,
37
+ y: 24,
38
+ });
39
+ });
40
+ it('scales down when displayed larger than the backing resolution', () => {
41
+ const rect = { left: 0, top: 0, width: 1200, height: 480 };
42
+ expect(mapPointerToCanvasPoint(1200, 480, rect, 600, 240)).toEqual({
43
+ x: 600,
44
+ y: 240,
45
+ });
46
+ });
47
+ it('falls back to 1:1 for a zero-size (not laid out) rect', () => {
48
+ const rect = { left: 5, top: 5, width: 0, height: 0 };
49
+ expect(mapPointerToCanvasPoint(15, 25, rect, 600, 240)).toEqual({
50
+ x: 10,
51
+ y: 20,
52
+ });
53
+ });
54
+ });
@@ -0,0 +1,34 @@
1
+ <script lang="ts">
2
+ import ThemeProvider from '../../../themes/ThemeProvider.svelte';
3
+ import DataTable from '../../data/DataTable.svelte';
4
+ import FilterChips from '../../nav/FilterChips.svelte';
5
+ import Button from '../../ui/Button.svelte';
6
+ import Checkbox from '../Checkbox.svelte';
7
+ import Input from '../Input.svelte';
8
+ import Radio from '../Radio.svelte';
9
+ import RadioGroup from '../RadioGroup.svelte';
10
+ import SegmentedControl from '../SegmentedControl.svelte';
11
+ import Select from '../Select.svelte';
12
+ import Switch from '../Switch.svelte';
13
+ import Textarea from '../Textarea.svelte';
14
+
15
+ let {
16
+ density = 'touch',
17
+ target = '48px',
18
+ }: { density?: 'comfortable' | 'touch'; target?: string } = $props();
19
+ </script>
20
+ <ThemeProvider {density} overrides={{ '--smrt-touch-target-min': target }} persist={false} inlineVariables>
21
+ <Input aria-label="Name" name="name" size={12} />
22
+ <Select aria-label="Choice"><option value="one">One</option></Select>
23
+ <Textarea aria-label="Notes" />
24
+ <Checkbox label="Approve" />
25
+ <Switch label="Enable" />
26
+ <RadioGroup name="radio" label="Radio"><Radio value="one" label="Option one" /></RadioGroup>
27
+ <Button size="sm">Save</Button>
28
+ <Button href="/details" size="sm">Details</Button>
29
+ <FilterChips size="sm" options={[{ value: 'one', label: 'One' }, { value: 'off', label: 'Disabled', disabled: true }]} selected="one" />
30
+ <SegmentedControl label="Segments" options={[{ value: 'one', label: 'One' }, { value: 'two', label: 'Two' }]} />
31
+ <DataTable data={[{ id: 'one', title: 'Item' }]} columns={[{ id: 'title', label: 'Title', sortable: true }]} sortable rowKey="id" />
32
+ <Input aria-label="Local touch" density="touch" />
33
+ <Input aria-label="Local comfortable" density="comfortable" />
34
+ </ThemeProvider>
@@ -0,0 +1,8 @@
1
+ type $$ComponentProps = {
2
+ density?: 'comfortable' | 'touch';
3
+ target?: string;
4
+ };
5
+ declare const TouchDensity: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type TouchDensity = ReturnType<typeof TouchDensity>;
7
+ export default TouchDensity;
8
+ //# sourceMappingURL=touch-density.fixture.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"touch-density.fixture.svelte.d.ts","sourceRoot":"","sources":["../../../../src/components/forms/__tests__/touch-density.fixture.svelte.ts"],"names":[],"mappings":"AAgBC,KAAK,gBAAgB,GAAI;IAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAsCjF,QAAA,MAAM,YAAY,sDAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
@@ -0,0 +1,66 @@
1
+ import { render, screen } from '@testing-library/svelte';
2
+ import { createRawSnippet } from 'svelte';
3
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
4
+ import DataTable from '../../data/DataTable.svelte';
5
+ import FilterChips from '../../nav/FilterChips.svelte';
6
+ import Button from '../../ui/Button.svelte';
7
+ import Checkbox from '../Checkbox.svelte';
8
+ import Input from '../Input.svelte';
9
+ import RadioGroup from '../RadioGroup.svelte';
10
+ import SegmentedControl from '../SegmentedControl.svelte';
11
+ import Select from '../Select.svelte';
12
+ import Switch from '../Switch.svelte';
13
+ import Textarea from '../Textarea.svelte';
14
+ import TouchDensityFixture from './touch-density.fixture.svelte';
15
+ describe('touch density', () => {
16
+ beforeEach(() => {
17
+ window.matchMedia = vi.fn().mockReturnValue({
18
+ matches: false,
19
+ addEventListener: vi.fn(),
20
+ removeEventListener: vi.fn(),
21
+ });
22
+ });
23
+ it.each([
24
+ ['Input', Input, {}],
25
+ [
26
+ 'RadioGroup',
27
+ RadioGroup,
28
+ {
29
+ name: 'choice',
30
+ children: createRawSnippet(() => ({
31
+ render: () => '<span>Options</span>',
32
+ })),
33
+ },
34
+ ],
35
+ [
36
+ 'Select',
37
+ Select,
38
+ {
39
+ children: createRawSnippet(() => ({
40
+ render: () => '<option value="one">One</option>',
41
+ })),
42
+ },
43
+ ],
44
+ ['Textarea', Textarea, {}],
45
+ ['Checkbox', Checkbox, {}],
46
+ ['Switch', Switch, {}],
47
+ ['Button', Button, {}],
48
+ ['FilterChips', FilterChips, { options: [], selected: '' }],
49
+ ['SegmentedControl', SegmentedControl, { options: [], label: 'Segments' }],
50
+ ['DataTable', DataTable, { columns: [], data: [] }],
51
+ ])('%s consumes density on its target root', (_name, component, props) => {
52
+ const { container } = render(component, {
53
+ props: { ...props, density: 'touch' },
54
+ });
55
+ expect(container.querySelector('[data-density="touch"]')).toBeTruthy();
56
+ expect(container.querySelector('[density]')).toBeNull();
57
+ });
58
+ it('inherits global density while preserving native size and local overrides', () => {
59
+ const { container } = render(TouchDensityFixture);
60
+ expect(container.querySelector('.smrt-theme-root')).toHaveAttribute('style', expect.stringContaining('--smrt-control-target-min: var(--smrt-touch-target-min, 48px)'));
61
+ expect(screen.getByRole('textbox', { name: 'Name' })).toHaveAttribute('size', '12');
62
+ expect(screen.getByRole('textbox', { name: 'Name' })).not.toHaveAttribute('data-density');
63
+ expect(screen.getByRole('textbox', { name: 'Local comfortable' })).toHaveAttribute('data-density', 'comfortable');
64
+ expect(screen.getByRole('textbox', { name: 'Local touch' })).toHaveAttribute('data-density', 'touch');
65
+ });
66
+ });
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Record a short spoken message with `MediaRecorder`, for dictation in
3
+ * browsers whose speech recognition is missing or broken (Firefox, Brave).
4
+ *
5
+ * The recording is compressed by the browser: WebM/Opus where supported
6
+ * (Chrome, Edge, Firefox, Brave), MP4/AAC on Safari. It stops by itself at
7
+ * `maxDurationMs` (the recording so far is kept) and fails when it grows past
8
+ * `maxBytes`, so a forgotten microphone cannot fill memory or exceed what the
9
+ * server accepts. The audio stays in memory; nothing is written or logged.
10
+ */
11
+ /** MIME types tried in order; the first `MediaRecorder` supports wins. */
12
+ export declare const DICTATION_AUDIO_MIME_TYPES: readonly string[];
13
+ /** Default cap on a recording's length: 2 minutes. */
14
+ export declare const DICTATION_MAX_DURATION_MS = 120000;
15
+ /** Default cap on a recording's size: 10 MB. */
16
+ export declare const DICTATION_MAX_BYTES: number;
17
+ /** A finished recording. */
18
+ export interface DictationRecording {
19
+ audio: Blob;
20
+ /** The recording's MIME type, e.g. `audio/webm;codecs=opus`. */
21
+ mimeType: string;
22
+ /** How long it recorded, in milliseconds. */
23
+ durationMs: number;
24
+ /** Whether it stopped because it reached `maxDurationMs`. */
25
+ reachedTimeLimit: boolean;
26
+ }
27
+ /** Records one message. `Dictation` creates a fresh one per message. */
28
+ export interface DictationAudioCapture {
29
+ /** Opens the microphone and starts recording. Rejects when it cannot. */
30
+ start(): Promise<void>;
31
+ /** Stops recording and resolves with the audio. */
32
+ stop(): Promise<DictationRecording>;
33
+ /** Stops recording and throws the audio away. */
34
+ cancel(): void;
35
+ /**
36
+ * Called when the recording stops by itself: `time` (reached
37
+ * `maxDurationMs`; `stop()` still returns the audio) or `size` (grew past
38
+ * `maxBytes`; `stop()` rejects with a `too-long` `DictationError`).
39
+ */
40
+ onLimit(callback: (reason: 'time' | 'size') => void): () => void;
41
+ }
42
+ export interface DictationAudioCaptureOptions {
43
+ /** Default {@link DICTATION_MAX_DURATION_MS}. */
44
+ maxDurationMs?: number;
45
+ /** Default {@link DICTATION_MAX_BYTES}. */
46
+ maxBytes?: number;
47
+ /** MIME types to try, in order. Default {@link DICTATION_AUDIO_MIME_TYPES}. */
48
+ mimeTypes?: readonly string[];
49
+ /** Media constraints for `getUserMedia`. Default `{ audio: true }`. */
50
+ constraints?: MediaStreamConstraints;
51
+ }
52
+ /** Creates a recorder for one message. */
53
+ export type DictationAudioCaptureFactory = (options: DictationAudioCaptureOptions) => DictationAudioCapture;
54
+ /**
55
+ * An error that already knows which plain-words message it needs.
56
+ * `classifyDictationError` reads `dictationKind` first.
57
+ */
58
+ export declare class DictationError extends Error {
59
+ readonly dictationKind: string;
60
+ readonly code: string;
61
+ constructor(dictationKind: string, message: string, code?: string);
62
+ }
63
+ /** Whether this browser can record audio for dictation. */
64
+ export declare function canCaptureDictationAudio(): boolean;
65
+ /** The first MIME type `MediaRecorder` can record, or `''` (its default). */
66
+ export declare function pickDictationMimeType(candidates?: readonly string[]): string;
67
+ /** Records with `MediaRecorder` (see the module comment). */
68
+ export declare const createMediaRecorderCapture: DictationAudioCaptureFactory;
69
+ //# sourceMappingURL=audio-capture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audio-capture.d.ts","sourceRoot":"","sources":["../../../src/components/forms/audio-capture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,0EAA0E;AAC1E,eAAO,MAAM,0BAA0B,EAAE,SAAS,MAAM,EAKvD,CAAC;AAEF,sDAAsD;AACtD,eAAO,MAAM,yBAAyB,SAAU,CAAC;AAEjD,gDAAgD;AAChD,eAAO,MAAM,mBAAmB,QAAmB,CAAC;AAEpD,4BAA4B;AAC5B,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,IAAI,CAAC;IACZ,gEAAgE;IAChE,QAAQ,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,6DAA6D;IAC7D,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAED,wEAAwE;AACxE,MAAM,WAAW,qBAAqB;IACpC,yEAAyE;IACzE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,mDAAmD;IACnD,IAAI,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACpC,iDAAiD;IACjD,MAAM,IAAI,IAAI,CAAC;IACf;;;;OAIG;IACH,OAAO,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CAClE;AAED,MAAM,WAAW,4BAA4B;IAC3C,iDAAiD;IACjD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,uEAAuE;IACvE,WAAW,CAAC,EAAE,sBAAsB,CAAC;CACtC;AAED,0CAA0C;AAC1C,MAAM,MAAM,4BAA4B,GAAG,CACzC,OAAO,EAAE,4BAA4B,KAClC,qBAAqB,CAAC;AAK3B;;;GAGG;AACH,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM;CAMlE;AASD,2DAA2D;AAC3D,wBAAgB,wBAAwB,IAAI,OAAO,CAMlD;AAED,6EAA6E;AAC7E,wBAAgB,qBAAqB,CACnC,UAAU,GAAE,SAAS,MAAM,EAA+B,GACzD,MAAM,CAOR;AAED,6DAA6D;AAC7D,eAAO,MAAM,0BAA0B,EAAE,4BA+IxC,CAAC"}
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Record a short spoken message with `MediaRecorder`, for dictation in
3
+ * browsers whose speech recognition is missing or broken (Firefox, Brave).
4
+ *
5
+ * The recording is compressed by the browser: WebM/Opus where supported
6
+ * (Chrome, Edge, Firefox, Brave), MP4/AAC on Safari. It stops by itself at
7
+ * `maxDurationMs` (the recording so far is kept) and fails when it grows past
8
+ * `maxBytes`, so a forgotten microphone cannot fill memory or exceed what the
9
+ * server accepts. The audio stays in memory; nothing is written or logged.
10
+ */
11
+ /** MIME types tried in order; the first `MediaRecorder` supports wins. */
12
+ export const DICTATION_AUDIO_MIME_TYPES = [
13
+ 'audio/webm;codecs=opus',
14
+ 'audio/webm',
15
+ 'audio/mp4',
16
+ 'audio/ogg;codecs=opus',
17
+ ];
18
+ /** Default cap on a recording's length: 2 minutes. */
19
+ export const DICTATION_MAX_DURATION_MS = 120_000;
20
+ /** Default cap on a recording's size: 10 MB. */
21
+ export const DICTATION_MAX_BYTES = 10 * 1024 * 1024;
22
+ /**
23
+ * An error that already knows which plain-words message it needs.
24
+ * `classifyDictationError` reads `dictationKind` first.
25
+ */
26
+ export class DictationError extends Error {
27
+ dictationKind;
28
+ code;
29
+ constructor(dictationKind, message, code) {
30
+ super(message);
31
+ this.name = 'DictationError';
32
+ this.dictationKind = dictationKind;
33
+ this.code = code ?? dictationKind;
34
+ }
35
+ }
36
+ /** How long `stop()` waits for the recorder's `stop` event. */
37
+ const STOP_EVENT_TIMEOUT_MS = 3000;
38
+ function captureError(kind, message) {
39
+ return new DictationError(kind, message);
40
+ }
41
+ /** Whether this browser can record audio for dictation. */
42
+ export function canCaptureDictationAudio() {
43
+ return (typeof MediaRecorder !== 'undefined' &&
44
+ typeof navigator !== 'undefined' &&
45
+ typeof navigator.mediaDevices?.getUserMedia === 'function');
46
+ }
47
+ /** The first MIME type `MediaRecorder` can record, or `''` (its default). */
48
+ export function pickDictationMimeType(candidates = DICTATION_AUDIO_MIME_TYPES) {
49
+ if (typeof MediaRecorder === 'undefined')
50
+ return '';
51
+ const supports = typeof MediaRecorder.isTypeSupported === 'function'
52
+ ? (type) => MediaRecorder.isTypeSupported(type)
53
+ : () => false;
54
+ return candidates.find((type) => supports(type)) ?? '';
55
+ }
56
+ /** Records with `MediaRecorder` (see the module comment). */
57
+ export const createMediaRecorderCapture = (options = {}) => {
58
+ const maxDurationMs = options.maxDurationMs ?? DICTATION_MAX_DURATION_MS;
59
+ const maxBytes = options.maxBytes ?? DICTATION_MAX_BYTES;
60
+ const limitListeners = new Set();
61
+ let stream = null;
62
+ let recorder = null;
63
+ let chunks = [];
64
+ let bytes = 0;
65
+ let startedAt = 0;
66
+ let endedAt = 0;
67
+ let timer = null;
68
+ let reachedTimeLimit = false;
69
+ let tooBig = false;
70
+ let cancelled = false;
71
+ let stopped = null;
72
+ let resolveStopped = null;
73
+ const emitLimit = (reason) => {
74
+ for (const listener of limitListeners) {
75
+ try {
76
+ listener(reason);
77
+ }
78
+ catch {
79
+ // A broken listener must not keep the microphone open.
80
+ }
81
+ }
82
+ };
83
+ const release = () => {
84
+ if (timer)
85
+ clearTimeout(timer);
86
+ timer = null;
87
+ for (const track of stream?.getTracks() ?? [])
88
+ track.stop();
89
+ stream = null;
90
+ };
91
+ const halt = () => {
92
+ endedAt ||= Date.now();
93
+ if (recorder && recorder.state !== 'inactive') {
94
+ try {
95
+ recorder.stop();
96
+ }
97
+ catch {
98
+ resolveStopped?.();
99
+ }
100
+ }
101
+ else {
102
+ resolveStopped?.();
103
+ }
104
+ release();
105
+ };
106
+ return {
107
+ async start() {
108
+ if (!canCaptureDictationAudio()) {
109
+ throw captureError('unsupported', 'This browser cannot record audio (no MediaRecorder).');
110
+ }
111
+ stream = await navigator.mediaDevices.getUserMedia(options.constraints ?? { audio: true });
112
+ if (cancelled) {
113
+ release();
114
+ return;
115
+ }
116
+ const mimeType = pickDictationMimeType(options.mimeTypes);
117
+ try {
118
+ recorder = mimeType
119
+ ? new MediaRecorder(stream, { mimeType })
120
+ : new MediaRecorder(stream);
121
+ }
122
+ catch (error) {
123
+ release();
124
+ throw error;
125
+ }
126
+ stopped = new Promise((resolve) => {
127
+ resolveStopped = resolve;
128
+ });
129
+ recorder.addEventListener('dataavailable', (event) => {
130
+ if (!event.data || event.data.size === 0 || cancelled)
131
+ return;
132
+ chunks.push(event.data);
133
+ bytes += event.data.size;
134
+ if (bytes > maxBytes && !tooBig) {
135
+ tooBig = true;
136
+ chunks = [];
137
+ emitLimit('size');
138
+ halt();
139
+ }
140
+ });
141
+ recorder.addEventListener('stop', () => resolveStopped?.());
142
+ recorder.addEventListener('error', () => halt());
143
+ // Hand over data every second so the size cap is checked as it grows.
144
+ recorder.start(1000);
145
+ startedAt = Date.now();
146
+ timer = setTimeout(() => {
147
+ reachedTimeLimit = true;
148
+ emitLimit('time');
149
+ halt();
150
+ }, maxDurationMs);
151
+ },
152
+ async stop() {
153
+ if (!recorder) {
154
+ release();
155
+ throw captureError('unsupported', 'Recording never started.');
156
+ }
157
+ halt();
158
+ // A browser that never fires the recorder's `stop` event must not leave
159
+ // the field stuck "writing it down": give up waiting and use the audio
160
+ // that has arrived.
161
+ let giveUp;
162
+ await Promise.race([
163
+ stopped,
164
+ new Promise((resolve) => {
165
+ giveUp = setTimeout(resolve, STOP_EVENT_TIMEOUT_MS);
166
+ }),
167
+ ]);
168
+ if (giveUp)
169
+ clearTimeout(giveUp);
170
+ const type = recorder.mimeType || pickDictationMimeType(options.mimeTypes);
171
+ const recording = {
172
+ audio: new Blob(chunks, type ? { type } : undefined),
173
+ mimeType: type || 'audio/webm',
174
+ durationMs: Math.max(0, (endedAt || Date.now()) - startedAt),
175
+ reachedTimeLimit,
176
+ };
177
+ chunks = [];
178
+ if (tooBig || recording.audio.size > maxBytes) {
179
+ throw captureError('too-long', 'The recording is too large to send.');
180
+ }
181
+ return recording;
182
+ },
183
+ cancel() {
184
+ cancelled = true;
185
+ chunks = [];
186
+ halt();
187
+ },
188
+ onLimit(callback) {
189
+ limitListeners.add(callback);
190
+ return () => limitListeners.delete(callback);
191
+ },
192
+ };
193
+ };
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Framework-free `getUserMedia()` lifecycle for `CameraCapture.svelte`.
3
+ *
4
+ * Owns exactly one acquired `MediaStream`: requesting it, classifying a
5
+ * rejection into one of the states the component renders, and releasing every
6
+ * track it acquired. It never touches a `<video>` or `<canvas>`, so permission
7
+ * denied, no camera, unsupported browser and stream cleanup are unit-testable
8
+ * against a fake `MediaDevices` with no DOM at all. The component stays thin
9
+ * DOM glue around it.
10
+ *
11
+ * Guards one race a naive "await getUserMedia, then store the stream" misses:
12
+ * the user can dismiss the permission prompt late, or the component can be
13
+ * disabled or unmounted, while `getUserMedia()` is still pending. Storing that
14
+ * late result would leave the camera light on with nothing left to stop it.
15
+ * `stop()` and every new `start()` bump a generation counter; `start()`
16
+ * compares the generation it was issued under once the promise settles and
17
+ * discards a stale result, stopping any tracks it just acquired.
18
+ *
19
+ * Ported from teamworks-os (`camera-capture-session.ts` and
20
+ * `camera-capture-logic.ts`), smrt#3290.
21
+ */
22
+ /** Camera facing mode requested from `getUserMedia()`. */
23
+ export type CameraFacingMode = 'environment' | 'user';
24
+ /** The failure states a camera request can surface. */
25
+ export type CameraCaptureErrorKind = 'permission-denied' | 'no-camera' | 'unsupported' | 'error';
26
+ /**
27
+ * Structural stand-in for `{ mediaDevices?: MediaDevices }`. lib.dom types
28
+ * `Navigator.mediaDevices` as always present, but a browser without camera
29
+ * support (or a page outside a secure context) leaves it `undefined`.
30
+ */
31
+ export interface MaybeMediaDevicesHost {
32
+ mediaDevices?: {
33
+ getUserMedia?: unknown;
34
+ } | undefined;
35
+ }
36
+ /** The part of `MediaDevices` the session needs. */
37
+ export interface MediaDevicesLike {
38
+ getUserMedia(constraints: MediaStreamConstraints): Promise<MediaStream>;
39
+ }
40
+ /** Result of one `start()` call. `cancelled` means a `stop()` superseded it. */
41
+ export type CameraSessionResult = {
42
+ ok: true;
43
+ stream: MediaStream;
44
+ } | {
45
+ ok: false;
46
+ kind: CameraCaptureErrorKind | 'cancelled';
47
+ /** Diagnostic message from the rejection; not localized, not for display. */
48
+ message: string;
49
+ };
50
+ export interface CameraSessionDeps {
51
+ /**
52
+ * `navigator.mediaDevices`, resolved by the caller (`undefined` when the
53
+ * browser has none) so this module never reaches for a global itself.
54
+ */
55
+ mediaDevices: MediaDevicesLike | undefined;
56
+ }
57
+ /**
58
+ * Whether this runtime exposes `getUserMedia` at all. An unsupported browser is
59
+ * never reported as "no camera": those states need different recovery copy.
60
+ */
61
+ export declare function isCameraApiSupported(nav: MaybeMediaDevicesHost | undefined): boolean;
62
+ /**
63
+ * Maps a `getUserMedia()` rejection to a rendered state by `DOMException.name`
64
+ * (the cross-browser contract), never by message text.
65
+ *
66
+ * Deliberately does not gate on `instanceof Error`: a browser `DOMException`
67
+ * does not inherit from `Error` in shipping engines, so that check would send
68
+ * every real rejection to the generic state. Node's `DOMException` happening to
69
+ * extend `Error` is what hides that bug in local tests.
70
+ */
71
+ export declare function classifyGetUserMediaError(error: unknown): CameraCaptureErrorKind;
72
+ /** Create a session that owns at most one live camera stream. */
73
+ export declare function createCameraSession(deps: CameraSessionDeps): {
74
+ start: (facingMode: CameraFacingMode) => Promise<CameraSessionResult>;
75
+ stop: () => void;
76
+ /** The live stream, or `null` when none is held. */
77
+ readonly stream: MediaStream | null;
78
+ };
79
+ /** A camera session created by {@link createCameraSession}. */
80
+ export type CameraSession = ReturnType<typeof createCameraSession>;
81
+ //# sourceMappingURL=camera-capture-session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"camera-capture-session.d.ts","sourceRoot":"","sources":["../../../src/components/forms/camera-capture-session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,0DAA0D;AAC1D,MAAM,MAAM,gBAAgB,GAAG,aAAa,GAAG,MAAM,CAAC;AAEtD,uDAAuD;AACvD,MAAM,MAAM,sBAAsB,GAC9B,mBAAmB,GACnB,WAAW,GACX,aAAa,GACb,OAAO,CAAC;AAEZ;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,YAAY,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,SAAS,CAAC;CACvD;AAED,oDAAoD;AACpD,MAAM,WAAW,gBAAgB;IAC/B,YAAY,CAAC,WAAW,EAAE,sBAAsB,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;CACzE;AAED,gFAAgF;AAChF,MAAM,MAAM,mBAAmB,GAC3B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,WAAW,CAAA;CAAE,GACjC;IACE,EAAE,EAAE,KAAK,CAAC;IACV,IAAI,EAAE,sBAAsB,GAAG,WAAW,CAAC;IAC3C,6EAA6E;IAC7E,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEN,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,YAAY,EAAE,gBAAgB,GAAG,SAAS,CAAC;CAC5C;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,qBAAqB,GAAG,SAAS,GACrC,OAAO,CAET;AAUD;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,OAAO,GACb,sBAAsB,CAaxB;AAMD,iEAAiE;AACjE,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,iBAAiB;wBAgB3C,gBAAgB,KAC3B,OAAO,CAAC,mBAAmB,CAAC;gBAuCd,IAAI;IASnB,oDAAoD;qBACtC,WAAW,GAAG,IAAI;EAInC;AAED,+DAA+D;AAC/D,MAAM,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC"}