@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
package/AGENTS.md CHANGED
@@ -46,13 +46,14 @@ components are exempt — they *are* the primitives.
46
46
  | `.` | barrel re-exporting the primitives, registry, and theme system |
47
47
  | `./playground` | shared-host previews for the complete foundation catalog; rendered inside the active preset and color scheme |
48
48
  | `./ui` | `Button`, `Card`, `Badge`, `Avatar`, `Chip`, `Skeleton`, `Tooltip`, `Dropdown`/`Menu`, `Popover`, `Disclosure`, `Accordion`, `Tree`, `Pagination`, … |
49
- | `./feedback` | `Alert`, `ToastViewport`, `Modal`, `Drawer`/`Sheet`, `ConfirmDialog`, `LoadingOverlay`, `Progress`, `Meter`, `Spinner` |
49
+ | `./feedback` | `Alert`, `ToastViewport`, `Modal`, `Drawer`/`Sheet`, `ConfirmDialog`, `LoadingOverlay`, `Progress`, `Meter`, `Spinner`, phone surfaces `PhoneSheet` / `WorkingStrip` and the `swipeDismiss` action |
50
50
  | `./data` | `CollectionToolbar`, `CollectionList`/`ContentList`, `DataTable` and their types — a **component barrel**: it re-exports `.svelte` files, so a plain Node `import()` of it throws `ERR_UNKNOWN_FILE_EXTENSION` |
51
51
  | `./data-surface` | **Svelte-free**: registry contracts and shared protocol limits (`DATA_SURFACE_MAX_REQUEST_BYTES`, `DATA_SURFACE_IDENTIFIER_MAX_LENGTH`, …). Server adapters must import these from here, not from `./data` — a server entry that reaches the barrel breaks the `smrt` CLI's plain-Node `.smrt/register.js` load (issue #2924) |
52
52
  | `./layout` | `Container`, `Grid`, `Header`, `Footer`, `PageHeader`, `EmptyState`, … |
53
- | `./calendar` | `Calendar`, `DayView` |
53
+ | `./calendar` | `CalendarView` (time-zone-aware month grid + phone agenda) and its Svelte-free date model (`toEntries`, `layoutMonth`, `dateKeyInZone`, …); deprecated `Calendar`, `DayView` |
54
54
  | `./chat` | `MessageBubble`, `ReactionPicker`, `TypingIndicator` |
55
- | `./forms` | Provider-free fields, choice controls, sliders/ranges, combobox/listbox/multiselect/tags, date/time/file controls, plus the transport-neutral control interaction registry |
55
+ | `./forms` | Provider-free fields, choice controls, sliders/ranges, combobox/listbox/multiselect/tags, date/time/file controls, `CameraCapture`/`SignaturePad` capture, plus the transport-neutral control interaction registry |
56
+ | `./form-retry` | **Svelte-free** form retry (#3291): submission key, in-flight refusal, conditional reset, opt-in restore-after-reload, browser `runOnce()` digest. Also re-exported from `./forms`. Framework-free TS typed structurally against SvelteKit's `SubmitFunction` — never import `@sveltejs/kit` here. Guide: `docs/content/form-retry.md` |
56
57
  | `./i18n` | i18n **client**: `useI18n`, `<Trans>`, `defineMessages`, `renderTemplate` (no `smrt-languages` import — the server resolver stays in `smrt-svelte/i18n/server`) |
57
58
  | `./registry` | `ModuleUIRegistry` for cross-package component discovery |
58
59
  | `./theme` | deprecated compatibility path forwarding to the canonical theme system |
@@ -149,7 +150,28 @@ other text pairing clears WCAG AA in both schemes.
149
150
 
150
151
  ## Gotchas
151
152
 
153
+ - **Calendar dates are `YYYY-MM-DD` keys in the view's `timeZone`.** Project
154
+ instants into the zone once (`dateKeyInZone`) and do all grid/span math on
155
+ keys; never `new Date(y, m, d)` / `getDay()` in the browser zone. Months are
156
+ 1-12 in the public API (`CalendarMonth`), unlike `Date#getMonth`.
157
+ - **Capture controls post natively.** `CameraCapture` and `SignaturePad` fill
158
+ a hidden named file input via `DataTransfer`, falling back to a `formdata`
159
+ listener (`native-file-field.ts`). Keep camera lifecycle in the
160
+ framework-free `camera-capture-session.ts` and the pointer gate in
161
+ `signature-pad-logic.ts`; their effects must not read rendered state. The
162
+ `<input capture>` path stays opt-in (`fileInputFallback`), off by default.
163
+ A form `reset` empties a native file input, so both clear their capture on
164
+ their form's reset (`attachFormResetListener`), even while `disabled`;
165
+ otherwise the UI shows a file the next submit no longer posts.
166
+
152
167
  - **i18n split**: the client (here) is dependency-free; the Node-only server
153
168
  resolver (`buildI18nSnapshot`, → `@happyvertical/smrt-languages`) stays in
154
169
  `@happyvertical/smrt-svelte/i18n/server`. Package string catalogs register via
155
170
  `defineMessages` imported from `@happyvertical/smrt-ui/i18n`.
171
+
172
+ - **Consumers type-checking a local checkout** hit "two different types with
173
+ this name" on `Snippet` when two Svelte copies meet (svelte2tsx's
174
+ `/// <reference types="svelte" />` in compiled sources ignores `paths`). Keep
175
+ every Svelte export's `types` condition first and pointing at built
176
+ `*.svelte.d.ts`; the consumer recipe is
177
+ [docs/development/svelte-consumer-linking.md](../../docs/development/svelte-consumer-linking.md).
package/README.md CHANGED
@@ -29,22 +29,214 @@ pnpm add @happyvertical/smrt-ui
29
29
 
30
30
  | Area | Components |
31
31
  | --- | --- |
32
- | Fields | `Form`, `Field`/`FormGroup`, `Fieldset`, `InputGroup`, `ErrorSummary` |
33
- | Text and structured input | `Input`, `Textarea`, `Select`, `Combobox`, `Listbox`, `MultiSelect`, `TagsInput` |
32
+ | Fields | `Form`, `Field`/`FormGroup`, `Fieldset`, `InputGroup`, `ErrorSummary`, `FormActionBar` |
33
+ | Text and structured input | `Input`, `Textarea`, `Select`, `Combobox`, `Listbox`, `MultiSelect`, `TagsInput`, `SearchInput` |
34
34
  | Choices | `Checkbox`, `RadioGroup`/`Radio`, `Switch`, `Toggle`, `ToggleButton`, `SegmentedControl` |
35
35
  | Values and files | `Slider`, `RangeSlider`, `DatePicker`, `TimePicker`, `FilePicker` |
36
+ | Capture | `CameraCapture`, `SignaturePad` |
36
37
  | Actions and display | `Button`, `Dropdown`/`Menu`, `Badge`, `Chip`, `Avatar`, `Card`, `Skeleton`, `Tooltip`, `Tree` |
37
- | Disclosure and overlays | `Popover`, `Disclosure`, `Accordion`/`AccordionItem`, `Modal`, `Drawer`/`Sheet`, `ConfirmDialog` |
38
- | Feedback | `Alert`, `ToastViewport`, `Progress`, `Meter`, `Spinner`, `LoadingOverlay` |
38
+ | Disclosure and overlays | `Popover`, `Disclosure`, `Accordion`/`AccordionItem`, `Modal`, `Drawer`/`Sheet`, `PhoneSheet`, `ConfirmDialog` |
39
+ | Feedback | `Alert`, `ToastViewport`, `Progress`, `Meter`, `Spinner`, `LoadingOverlay`, `WorkingStrip` |
39
40
  | Collections | `CollectionToolbar`, `CollectionList`/`ContentList`, `DataTable`, `Pagination` |
40
41
  | Layout and navigation | `Container`, `Grid`, `Header`, `Footer`, `PageHeader`, `EmptyState`, `Tabs`, `FilterChips` |
41
-
42
- Use the focused subpaths (`/forms`, `/ui`, `/feedback`, `/data`,
42
+ | Calendar | `CalendarView` (deprecated: `Calendar`, `DayView`) |
43
+
44
+ ### Phone surfaces
45
+
46
+ - `PhoneSheet` (`/feedback`) is the phone replacement for drawers and centered
47
+ modals: `variant="page"` covers the area, `variant="sheet"` rises from the
48
+ bottom and closes on a swipe down its header. It is non-modal, closes on its
49
+ button and Escape, returns focus to the opener, and can stay mounted while
50
+ closed (hidden and inert) so its content keeps state. It is
51
+ `position: absolute; inset: 0` — render it in AdminShell's `overlays`.
52
+ - `WorkingStrip` (`/feedback`) shows a transport-neutral `WorkingStatus`
53
+ (`idle` · `working` · `paused` · `waiting` · `done` · `failed` ·
54
+ `cancelled`, a `label` for the current step and an optional `goal`) as a
55
+ strip above a phone bottom bar, a floating pill (`variant="floating"`, the
56
+ host positions it), or a line at the top of a pane. Optional controls:
57
+ reopen (`onopen`), Pause/Continue (`onpause`/`onresume`; Escape pauses),
58
+ Review while waiting (`onreview`), Stop, and Close once finished
59
+ (`ondismiss`); all 44px. Done uses the success colors, failed the error
60
+ colors, always with an icon and text. Step changes are announced politely
61
+ (throttled by `announceIntervalMs`), `waiting` and `failed` assertively; it
62
+ never takes focus.
63
+ - `FormActionBar` (`/forms`) groups a form's actions (primary last). On phones
64
+ it is fixed to the bottom, carries `data-form-action-bar` so AdminShell hides
65
+ its phone bottom bar, and hides while `:root[data-keyboard-open]`.
66
+ - `swipeDismiss` / `swipeDismisses` (`/feedback`) are the touch action and pure
67
+ decision behind swipe-to-close.
68
+ - `Dictation` / `DictationButton` / `DictationStatus` (`/forms`) let people
69
+ speak into a text field (tap the microphone, or press and hold the field
70
+ with `longPress`). The speech `source` is normally smrt-svelte's
71
+ `createSttDictationSource()` (the browser's own speech recognition). Give
72
+ it a `transcribe` function too (`createHttpTranscriber('/your/route')`)
73
+ and, where the browser has no speech recognition (Firefox) or no speech
74
+ service behind it (Brave: `network` / `service-not-allowed`), it records
75
+ the message with `MediaRecorder` instead (WebM/Opus, MP4 on Safari; at most
76
+ `maxDurationMs` 2 minutes and `maxBytes` 10 MB), shows "Writing it down…"
77
+ while the route turns it into text, and puts the text at the cursor.
78
+ Keep the speech service's key on the server: the route takes the raw audio
79
+ body (`Content-Type` is the recording's type, `?language=&durationMs=`)
80
+ and answers `{ text }`; 413, 503 and 401/403 become "too long", "not set
81
+ up" and "not allowed" messages.
82
+
83
+ ### Link tabs
84
+
85
+ `Tabs` with an `href` on every tab renders a page's sections as navigation —
86
+ a `<nav>` of links, `aria-current="page"` on the active one, each tab its own
87
+ URL — instead of an ARIA tablist. `maxVisible` moves extra tabs into a "More"
88
+ menu while the active tab always stays in the row (`splitTabs` is the pure
89
+ rule), `badge` marks a tab needing attention (and dots "More" when a hidden
90
+ tab has one), and the row scrolls sideways on phones with the active tab
91
+ kept in view. It carries `data-shell-tabs`, so AdminShell keeps it sticky
92
+ under the phone top bar. Pair it with `useLinkSurface` (smrt-svelte) so
93
+ agents can switch tabs too.
94
+
95
+ Use the focused subpaths (`/forms`, `/form-retry`, `/ui`, `/feedback`, `/data`,
43
96
  `/data-surface`, `/layout`, `/themes`) to keep imports explicit. The
44
97
  Svelte-free `/data-surface` entry exposes the registry contracts and shared
45
- protocol limits for server adapters. The package root remains a compatibility
98
+ protocol limits for server adapters; the Svelte-free `/form-retry` entry
99
+ exposes the form-retry helper. The package root remains a compatibility
46
100
  barrel.
47
101
 
102
+ ### Calendar dates and shop time
103
+
104
+ `DateDisplay` accepts `timeZone` (an IANA id such as `America/Edmonton`) for
105
+ instant inputs (`Date`, numeric timestamps, and timestamp strings). Omit it to
106
+ keep browser-local absolute formatting and the existing duration-based relative
107
+ format. With an explicit zone, relative today/yesterday/tomorrow boundaries use
108
+ calendar days in that zone, including daylight saving transitions.
109
+
110
+ A bare `YYYY-MM-DD` string always denotes that calendar date, independent of the
111
+ viewer or shop zone. It formats in UTC, retains a date-only `datetime` attribute,
112
+ and ignores `showTime` because it contains no time. Relative calendar-date
113
+ labels compare it with today's date in the supplied zone (or the browser zone).
114
+ Impossible calendar dates render `fallback`. Invalid explicit zones used for
115
+ instant or relative formatting also render `fallback` rather than throwing.
116
+
117
+ ### Combobox form submission
118
+
119
+ A named `Combobox` submits its selected option value through a hidden native
120
+ input. Its visible search text and option label are display-only. With
121
+ `allowCustom`, typed text becomes the submitted value; otherwise searching keeps
122
+ the last committed selection. A named empty selection submits an empty string;
123
+ controls with no name or an empty name are omitted. Disabled controls, including
124
+ those inside disabled fieldsets, are omitted by native `FormData`. Native form
125
+ reset restores the initial selection and its label, unless reset is canceled.
126
+
127
+ ### MultiSelect form submission
128
+
129
+ A named `MultiSelect` submits one hidden native input per selected option value,
130
+ using repeated field names in selection order. Read them with `FormData.getAll`.
131
+ Option labels are display-only, and numeric option values submit as strings.
132
+ Empty selections, missing or empty names, and disabled controls (including
133
+ ancestor fieldsets) contribute no entries. Native reset restores the initial
134
+ selection unless reset is canceled.
135
+
136
+ ### Listbox form submission
137
+
138
+ A named `Listbox` submits one hidden native input containing its selected option
139
+ value, with numeric values encoded as strings. A named listbox without a
140
+ selection submits an empty string. Missing or empty names and disabled controls
141
+ (including ancestor fieldsets) are omitted. Native reset restores the initial
142
+ selection unless reset is canceled.
143
+
144
+ ### Status badge tones
145
+
146
+ `StatusBadge` accepts `tone="success"`, `"warning"`, `"danger"`, `"info"`, or
147
+ `"neutral"` for custom status vocabulary such as `awaiting_cert`. An explicit
148
+ tone overrides the built-in domain scheme while label, size, and outline variant
149
+ continue to work independently. Tones use the active theme's paired container
150
+ and text tokens. Without a tone, known domain statuses retain their existing
151
+ colors and unknown statuses retain the neutral fallback. `StatusTone` is exported
152
+ as a TypeScript type from the package root.
153
+
154
+ ### Touch targets for Checkbox, Radio and Switch
155
+
156
+ `Checkbox`, `Radio` and `Switch` have an invisible 44x44px hit area around the
157
+ visible control (about 18px for a box or mark, the 2.75rem track for a switch).
158
+ A transparent `::before` on the wrapping `<label>` makes it, so a click anywhere
159
+ in the area toggles the input, no layout shifts, and the focus ring stays on the
160
+ visible box. The box and the label text sit above every hit area, so adjacent
161
+ controls never steal each other's clicks.
162
+
163
+ The hit area paints above plain (unpositioned) content around it, so it is
164
+ constrained to where that cannot cost a neighbour a click:
165
+
166
+ - **Beside other content.** A `Checkbox` or `Radio` that is not the only element
167
+ in its parent (a row title or link next to it, a group's other options) keeps
168
+ its hit area in the box's own column: it grows vertically only, never over the
169
+ text or link beside it. Only a control standing alone gets the full 44x44.
170
+ - **Table cells.** Inside a `td`/`th` the area never leaves the cell. A
171
+ `Checkbox` that is the cell's only content uses the whole cell (clipped to its
172
+ edges). The cell gets `position: relative` at zero specificity (`:where()`), so
173
+ a sticky header or a pinned column keeps its own positioning.
174
+ - **Stacked controls.** Hit areas of tightly stacked controls overlap in the
175
+ gaps; the later control wins a gap, never a neighbour's box or text. Content
176
+ directly above or below a box, closer than 13px, is covered by its hit area:
177
+ give it room, or shrink the area with `--smrt-control-hit-size`.
178
+ - **Resize or turn off.** `--smrt-control-hit-size` (default `2.75rem`); `0px`
179
+ gives only the visible control. Pages never add their own padding hacks.
180
+ - `DataTable`'s built-in selection checkboxes and `CollectionList`'s select box
181
+ are still native inputs and do not use this yet.
182
+
183
+ ## Calendar
184
+
185
+ `CalendarView` (`@happyvertical/smrt-ui/calendar`) is a generic month grid
186
+ with a phone agenda:
187
+
188
+ ```svelte
189
+ <script lang="ts">
190
+ import { CalendarView } from '@happyvertical/smrt-ui/calendar';
191
+ import { goto } from '$app/navigation';
192
+ let { data } = $props(); // { year, month, items }
193
+ </script>
194
+
195
+ <CalendarView
196
+ items={data.items}
197
+ year={data.year}
198
+ month={data.month}
199
+ timeZone="America/Edmonton"
200
+ onNavigate={({ year, month }) => goto(`?y=${year}&m=${month}`, { keepFocus: true })}
201
+ />
202
+ ```
203
+
204
+ - **Items** are generic `CalendarItem`s: `{ id, title, start, end?, allDay?,
205
+ tone?, color?, label?, group?, href? }`. `start`/`end` take a `Date`, an ISO
206
+ date-time, or a `YYYY-MM-DD` date (all-day). Timed `end` is exclusive; an
207
+ all-day `YYYY-MM-DD` end is the inclusive last day, and an all-day end
208
+ instant at local midnight is exclusive. `tone` maps to the theme's color
209
+ roles; without it, `group` picks a stable tone. Items with `href` render as
210
+ links, others as buttons; both call `onItemSelect`.
211
+ - **Time zone and locale**: every day is computed in `timeZone` (IANA;
212
+ defaults to the browser's — pass it explicitly so server and client
213
+ agree). Month, weekday, date, and time text come from `Intl` in `locale`
214
+ (defaults to the i18n locale); `weekStartsOn` defaults to the locale's.
215
+ - **Modes**: `month` is a `role="grid"` month with roving focus (arrows,
216
+ Home/End, PageUp/PageDown cross months). All-day and multi-day items are
217
+ bands across the days they cover; a day with more than `maxPerDay` rows
218
+ shows "+N more", which opens the day in a panel under the grid, or follows
219
+ `dayHref` when given. `agenda` is a horizontally scrolling strip of the
220
+ month's days (44px targets, arrow keys) above a day-by-day list. `auto`
221
+ (default) uses the agenda below 48rem, matching AdminShell's phone
222
+ breakpoint. No modals.
223
+ - **URL state**: `year` + `month` (1-12) and `selectedDate` (`YYYY-MM-DD`) are
224
+ controlled when passed; changes are reported via `onNavigate` and
225
+ `onSelectDate`, so a page can keep them in its URL.
226
+ - The Svelte-free date model (`toEntries`, `layoutMonth`, `dateKeyInZone`,
227
+ `monthWeeks`, `shiftMonth`, …) is exported from the same subpath for
228
+ server-side range queries and tests.
229
+
230
+ **Migrating from `Calendar` / `DayView` (deprecated).** They compute days in
231
+ the browser zone, print English-only names, and hard-code game/meeting/event
232
+ emoji and routes. Map each `DayEventDetail` to a `CalendarItem` (`name` →
233
+ `title`, `type` → `group`/`label`, your own route → `href`), replace
234
+ `Calendar`'s `year`/`month` (0-indexed) with `CalendarView`'s `year`/`month`
235
+ (1-12) and `onMonthNavigate` with `onNavigate`, `baseUrl` with a `dayHref`
236
+ function that builds your day route from the `YYYY-MM-DD` key, and a `DayView`
237
+ page with `CalendarView` in agenda mode (`selectedDate` set to the day) or the
238
+ month grid's day panel. They will be removed in a future minor release.
239
+
48
240
  ### Currency display
49
241
 
50
242
  `CurrencyDisplay` accepts ISO 4217 codes as a public `string` prop so persisted
@@ -91,6 +283,34 @@ Application-specific editors, maps, charts, media workbenches, and domain
91
283
  records remain composites built from this foundation rather than generic base
92
284
  components.
93
285
 
286
+ ## Native and enhanced forms
287
+
288
+ `Form` forwards native form attributes and Svelte attachments through its rest
289
+ props to the underlying `<form>`. For a SvelteKit action, adapt `enhance` with
290
+ `fromAction` and set `preventDefault={false}` so the enhancement handles submission:
291
+
292
+ ```svelte
293
+ <script lang="ts">
294
+ import { enhance } from '$app/forms';
295
+ import { fromAction } from 'svelte/attachments';
296
+ import { Form, Input } from '@happyvertical/smrt-ui/forms';
297
+ </script>
298
+
299
+ <Form method="POST" action="?/save" preventDefault={false} {@attach fromAction(enhance)}>
300
+ <Input name="displayName" />
301
+ <button type="submit">Save</button>
302
+ </Form>
303
+ ```
304
+
305
+ For a customized enhancement, pass its callback as a getter:
306
+ `{@attach fromAction(enhance, () => submitFunction)}`. The attachment runs on the
307
+ native element and cleans up when it unmounts; the interaction registry and
308
+ staged-review surface remain available. A component reference obtained with
309
+ `bind:this` exposes `getFormElement(): HTMLFormElement | null`, which returns
310
+ `null` before mount and after unmount. For ordinary browser GET/POST forms, use
311
+ `preventDefault={false}` without an attachment. The default remains `true` for
312
+ existing handler-driven forms.
313
+
94
314
  ## Agent-addressable forms
95
315
 
96
316
  `Form` can expose its controls to a chat, voice, tutorial, or test adapter
@@ -128,7 +348,8 @@ without coupling controls to a transport:
128
348
 
129
349
  Controls publish serializable metadata, constraints, options, sensitivity, and
130
350
  capabilities. Adapters can focus, reveal, highlight, explain, validate, and
131
- stage reviewable proposals. Agents cannot apply, discard, clear, or undo;
351
+ stage reviewable proposals. An agent never moves keyboard focus: its `focus`
352
+ reveals and highlights the control instead. Agents cannot apply, discard, clear, or undo;
132
353
  those value-changing actions require a trusted local gesture handled by the
133
354
  framework review surface. Secret/read-only controls reject agent mutations.
134
355
  Staging remains separate so proposals never change user state before review.
@@ -178,6 +399,175 @@ not await a same-control mutation through a captured registry reference: that
178
399
  call is indistinguishable from an independent caller in browser runtimes and,
179
400
  like any hook that never settles, can hold the ordered queue indefinitely.
180
401
 
402
+ ### Composite controls and form proposals
403
+
404
+ A composite field — a place picker, a slug field with its own preview, a
405
+ record picker — registers as ONE control with `useControlRegistration` (call
406
+ it during component init inside any `Form`, rich `Form`, or `FormScope`). Give
407
+ it a stable `controlId`, a plain `label`, and, when its value is not a plain
408
+ string/number/boolean, a `valueSchema` so adapters can describe it:
409
+
410
+ ```svelte
411
+ <script lang="ts">
412
+ import { useControlRegistration } from '@happyvertical/smrt-ui/forms';
413
+
414
+ let { value = $bindable(null) } = $props();
415
+
416
+ useControlRegistration(() => ({
417
+ controlId: 'place',
418
+ metadata: {
419
+ kind: 'custom',
420
+ label: 'Town location',
421
+ valueSchema: {
422
+ type: 'object',
423
+ properties: { name: { type: 'string' }, latitude: { type: 'number' }, longitude: { type: 'number' } },
424
+ },
425
+ },
426
+ getValue: () => value,
427
+ setValue: (next) => { value = next; },
428
+ }));
429
+ </script>
430
+ ```
431
+
432
+ Call `recordControlUserEdit(context, controlId, subject)` from the
433
+ composite's own user-event handlers so a staged proposal goes stale when the
434
+ person edits over it (native inputs bubble their events to the Form instead).
435
+ `focusControl`, `revealControl`, `highlightControl`, and `emitControlChange`
436
+ are the same DOM helpers the built-in primitives use for their `focus`,
437
+ `reveal`, and `highlight` handles.
438
+
439
+ `controlProposalInputSchema(registry, formId)` and
440
+ `stageControlProposals(registry, formId, values)` are the transport-neutral
441
+ halves of a "propose values for this form" tool: the schema has one optional
442
+ property per proposable control (never secret/sensitive, unwritable,
443
+ disabled, read-only, file, or password controls), and staging only ever
444
+ creates reviewable proposals. smrt-svelte's `<Form webmcp>` and `FormScope`
445
+ build their `*_stage_changes` tool from these.
446
+
447
+ ## Form retry
448
+
449
+ `createFormRetry()` is the browser half of `runOnce()` (`@happyvertical/smrt-core`)
450
+ for a SvelteKit enhanced form: a per-tab, per-form submission key, a refused
451
+ second submit while one is in flight, typed values kept across a validation or
452
+ transport failure, a reset after success only when the fields are unchanged
453
+ since submit, and opt-in restore after a reload (a file that cannot be restored
454
+ is reported, never silently dropped). It is framework-free and imports neither
455
+ Svelte nor SvelteKit; the submit function is typed against SvelteKit's
456
+ `SubmitFunction` shape structurally.
457
+
458
+ ```svelte
459
+ <script lang="ts">
460
+ import { enhance } from '$app/forms';
461
+ import { createFormRetry } from '@happyvertical/smrt-ui/form-retry';
462
+
463
+ const retry = createFormRetry({ form: 'report' });
464
+ </script>
465
+
466
+ <form method="POST" use:enhance={retry.enhance()} {@attach retry.attach}>
467
+ <input name="title" />
468
+ <button type="submit">Send</button>
469
+ {#if $retry.status === 'transport-error'}<p role="alert">Send it again unchanged.</p>{/if}
470
+ </form>
471
+ ```
472
+
473
+ With smrt-ui's own `Form`, attach the same two pieces to it (see
474
+ [Native and enhanced forms](#native-and-enhanced-forms)):
475
+ `<Form method="POST" preventDefault={false} {@attach fromAction(enhance, () => retry.enhance())} {@attach retry.attach}>`.
476
+
477
+ `/forms` re-exports the same API as the Svelte-free `/form-retry` entry. See the
478
+ [form retry guide](../../docs/content/form-retry.md) for the server half, the
479
+ per-result table, storage and private-window behaviour, and restore.
480
+
481
+ ## Camera and signature capture
482
+
483
+ `CameraCapture` takes a photo from the device camera and `SignaturePad` takes a
484
+ signature, each inside an ordinary form. Both live in `/forms`, need only
485
+ browser APIs, and post their file through a plain multipart form when given a
486
+ `name`; the page wires no hidden input and no submit handler.
487
+
488
+ ```svelte
489
+ <script lang="ts">
490
+ import { CameraCapture, SignaturePad } from '@happyvertical/smrt-ui/forms';
491
+ </script>
492
+
493
+ <form method="POST" enctype="multipart/form-data">
494
+ <CameraCapture name="photo" facingMode="environment" />
495
+ <SignaturePad name="signature" stylusOnly={settings.signatureStylusOnly} />
496
+ <button type="submit">Send</button>
497
+ </form>
498
+ ```
499
+
500
+ **CameraCapture** uses `getUserMedia` with a live preview. The user takes a
501
+ photo, reviews it, and can retake it before "Use photo" commits it. Committing
502
+ calls `onCapture({ blob, dataUrl })` and fills the named field (`photo.jpg` by
503
+ default; set `fileName`, `imageType`, and `quality` to change it). "Retake"
504
+ after a commit empties the field and calls `onClear`. The root's `data-state`
505
+ is one of `starting`, `streaming`, `reviewing`, `committed`, `off` (disabled),
506
+ `permission-denied`, `no-camera`, `unsupported`, `error`, or `fallback`. Each
507
+ problem state has its own copy and, where it can help, a "Try again" action.
508
+ `getUserMedia` exists only in a secure context, so a page served over plain
509
+ HTTP renders `unsupported`.
510
+
511
+ The stream's tracks are stopped on unmount, when `disabled` turns on, when
512
+ `facingMode` changes (the camera is then requested again), and as soon as a
513
+ frame is captured. A permission prompt answered after unmount or disable is
514
+ discarded and its stream released. The lifecycle lives in the framework-free
515
+ `createCameraSession()` (exported with `classifyGetUserMediaError()` and
516
+ `isCameraApiSupported()`), so it can be tested against a fake `MediaDevices`.
517
+
518
+ `fileInputFallback` is opt-in and off by default. With it on, browsers without
519
+ `getUserMedia` render an `<input type="file" accept="image/*" capture>` that
520
+ carries `name` itself (`disabled` blocks it with `aria-disabled` rather than
521
+ the native attribute, so a committed photo keeps posting). It opens the
522
+ operating system's picker, which has no
523
+ live preview and can offer the gallery, so it is not a substitute for the
524
+ camera flow. With it off, those browsers render the `unsupported` state.
525
+
526
+ **SignaturePad** draws on its own canvas. "Use signature" stays disabled until
527
+ an accepted stroke exists, then returns a PNG through `onCapture({ blob,
528
+ dataUrl })` and fills the named field (`signature.png` by default). A committed
529
+ pad is locked until "Clear", which empties the field and calls `onClear`.
530
+ `stylusOnly` is a prop the caller resolves; with it on, only `pointerType ===
531
+ 'pen'` draws. `isAcceptedPointerType()` and `mapPointerToCanvasPoint()` are
532
+ exported as pure functions. The pad is dark ink on white paper in every theme
533
+ and colour scheme, and the exported PNG is opaque white, so it reads the same
534
+ wherever it is shown.
535
+
536
+ **Native form posting.** The named field is a hidden `<input type="file">`
537
+ filled through `DataTransfer` (Chrome 60+, Firefox 62+, Safari 14.1+). Where
538
+ `DataTransfer` cannot be constructed or assigned, the field drops its `name`
539
+ and a capture-phase `formdata` listener appends the file while the browser
540
+ builds the request, which covers native navigation submits as well as
541
+ `new FormData(form)`. Where neither API exists, nothing is posted and
542
+ `onCapture` is the only channel. The root's `data-smrt-file-field` attribute
543
+ reports `data-transfer`, `formdata-event`, `file-input`, or `none`. Before a
544
+ commit the field posts what an empty native file input posts. `disabled`
545
+ freezes the controls but keeps a committed file in the submission; unmount the
546
+ component to drop it, or put it in a disabled `<fieldset>`, which leaves the
547
+ field out of the submission (committed or empty) in every posting strategy.
548
+
549
+ A reset of the owning form (`form.reset()`, a reset button, or SvelteKit
550
+ `enhance`'s `update()` after a success, including `createFormRetry()`'s
551
+ conditional reset) empties the field as it empties a native file input, in
552
+ every posting strategy. A committed photo or signature is discarded as
553
+ "Retake" or "Clear" would and `onClear` is called. A photo under review or
554
+ ink not yet committed is discarded too, without `onClear`, so the next entry
555
+ cannot attach the previous one's capture. `CameraCapture` returns to the live
556
+ camera (`off` while `disabled`, the emptied picker with `fileInputFallback`)
557
+ and supersedes a frame still encoding; with nothing held, a reset changes
558
+ nothing and never re-prompts for the camera. `SignaturePad` returns to blank,
559
+ unlocked paper. A reset clears both even while `disabled`, as it clears a
560
+ disabled native input, and a reset a later listener cancels still leaves the
561
+ component and the posted field both empty.
562
+
563
+ Text comes from the `ui.camera_capture.*` and `ui.signature_pad.*` i18n keys
564
+ and can be overridden per instance with `labels`. Capture is a touch flow, so
565
+ its actions (and the fallback picker) are touch targets at every density: at
566
+ least `--smrt-touch-target-min` (48px by default; see
567
+ [Touch density](#touch-density)), which ThemeProvider `overrides` can raise.
568
+ Both register with an enclosing `Form`'s interaction registry as non-readable,
569
+ non-writable `file` controls.
570
+
181
571
  ## DataTable controller
182
572
 
183
573
  `DataTable` can share one headless `DataTableController` between rendered
@@ -437,6 +827,34 @@ registry.register({
437
827
  });
438
828
  ```
439
829
 
830
+ ### Navigation and step surfaces
831
+
832
+ Two ready-made surfaces cover interactions a view intent cannot reach on its
833
+ own (an intent only dispatches a registry command):
834
+
835
+ - `registerLinkSurface({ registry, surfaceId, label, description, links,
836
+ navigate })` mounts a menu, tab row, or section list. `state.links`
837
+ publishes each link's id, label, description, and group — never its href —
838
+ and the default `open` control resolves `{ target }` (an id, a label, or a
839
+ unique label prefix) to one of those links and calls `navigate(href)`.
840
+ Nothing an agent sends can become a URL.
841
+ Return the navigation's promise from `navigate`: the command answers at
842
+ once, and `whenSurfaceNavigationSettled(registry)` resolves once it
843
+ finished and the new page's surfaces registered (a bespoke surface that
844
+ navigates calls `trackSurfaceNavigation(registry, promise)`).
845
+ - `registerStepSurface({ registry, surfaceId, label, description, steps,
846
+ current, next, back, goTo, nextWrites, showNext })` mounts a wizard.
847
+ `state` carries the steps with their status, the current step, and
848
+ `nextWrites`. `next`/`back`/`go-to` run the page's own handlers (return
849
+ `false` to refuse, e.g. when validation fails). When the current step's
850
+ forward button saves or creates something (`nextWrites`), `next` never
851
+ presses it: it calls `showNext` to reveal and highlight the button and
852
+ publishes `awaitingPerson: true` until the flow moves on.
853
+
854
+ smrt-svelte's `useLinkSurface` / `useStepSurface` / `useListSurface`
855
+ (`@happyvertical/smrt-svelte/web`) bind these to a component's lifetime on
856
+ the Provider's registry.
857
+
440
858
  `inspect()` and command results are deterministic `{ version, descriptor,
441
859
  revision, state, selection }` envelopes; neither includes a timestamp, rows,
442
860
  functions, authority fields, tenant/principal data, SQL, or a transport handle.
@@ -529,6 +947,46 @@ hand-authored, and every text pairing clears WCAG AA.
529
947
  Run the shared playground to inspect the full catalog under every preset and
530
948
  light/dark scheme.
531
949
 
950
+ ### Card look tokens
951
+
952
+ `Card` and `CollectionList` rows read these custom properties, so an app can
953
+ change the card look once in its own theme layer instead of per page. Unset,
954
+ they keep the stock look.
955
+
956
+ | Property | Default | Used for |
957
+ | --- | --- | --- |
958
+ | `--smrt-card-border` | `1px solid var(--smrt-color-outline-variant)` | edge of `default` and `elevated` cards, CollectionList rows |
959
+ | `--smrt-card-background` | `var(--smrt-color-surface)` | card and row fill |
960
+ | `--smrt-card-shadow` | `none` | shadow of `default` cards (`elevated` keeps its elevation) |
961
+ | `--smrt-card-divider` | `1px solid var(--smrt-color-outline-variant)` | rule under a card header / above its footer |
962
+
963
+ The `outlined` variant always draws its outline. For borderless cards on a
964
+ `surface` page, pair `--smrt-card-border: none` with a tinted background such
965
+ as `var(--smrt-color-surface-variant)` so cards stay distinct in both schemes:
966
+
967
+ ```css
968
+ :root {
969
+ --smrt-card-border: none;
970
+ --smrt-card-background: var(--smrt-color-surface-variant);
971
+ }
972
+ ```
973
+
974
+ ### Component override hooks
975
+
976
+ Hooks an app sets once in its own theme layer instead of restyling a component's
977
+ classes. Unset, each keeps its stock look.
978
+
979
+ | Property | Component | Used for |
980
+ | --- | --- | --- |
981
+ | `--smrt-popover-panel-width` | `Popover` | the panel's width (never wider than the viewport minus 2rem) |
982
+ | `--smrt-popover-panel-padding` | `Popover` | the panel's padding |
983
+ | `--smrt-admin-shell-background` | `AdminShell` (smrt-svelte) | the page background behind the cards |
984
+ | `--smrt-admin-shell-scrollbar-track` / `-thumb` / `-thumb-hover` | `AdminShell` (smrt-svelte) | the themed thin scrollbars inside the shell |
985
+ | `--smrt-shell-title-font-family` | `ShellTitle` (smrt-svelte) | the workspace name's font family |
986
+
987
+ `IconToggle` takes a `tone` prop (a CSS colour such as `var(--status-draft)`) for
988
+ a toggle that is neutral until pressed and then wears its colour.
989
+
532
990
  ## Development
533
991
 
534
992
  ```bash
@@ -537,3 +995,95 @@ pnpm test
537
995
  pnpm build
538
996
  pnpm verify:pack
539
997
  ```
998
+
999
+ ### Touch density
1000
+
1001
+ Set `<ThemeProvider density="touch">` for floor tablets and phones, or set
1002
+ `density="touch"` on Input, Select, Textarea, Checkbox, Switch, RadioGroup,
1003
+ Button (including links), FilterChips, SegmentedControl, or DataTable. DataTable
1004
+ applies density to its sort buttons. Density is independent of existing `size`
1005
+ props, including native Input/Select sizes. Omitted density inherits; explicitly
1006
+ setting `density="comfortable"` opts that control out and keeps its usual size.
1007
+ RadioGroup expands each option's clickable label, and Checkbox/Switch expand
1008
+ the label hit area while preserving the visual mark.
1009
+
1010
+ `--smrt-touch-target-min` defaults to `48px` across all presets and works without
1011
+ a provider. Customize it globally with ThemeProvider's `overrides`, for example
1012
+ `overrides={{ '--smrt-touch-target-min': '56px' }}`. Touch targets grow with larger
1013
+ content; normal density keeps existing component sizing. `CameraCapture` and
1014
+ `SignaturePad` actions use `--smrt-touch-target-min` at every density, since
1015
+ capture is a touch flow. TenantNav sizing is
1016
+ tracked separately in [#3246](https://github.com/happyvertical/smrt/issues/3246).
1017
+
1018
+ ### Narrow DataTable
1019
+
1020
+ Set `responsiveMode="hide-columns"` to adapt to the table's container width.
1021
+ The default `responsiveBreakpoint={800}` includes both 768px tablets and 390px
1022
+ phones. Narrow mode budgets one column per `responsiveColumnMinWidth={160}`
1023
+ pixels, after reserving space for selection and expansion controls. Higher
1024
+ `column.responsive.priority` values survive first (missing/nonfinite values are
1025
+ zero); ties preserve declared display order. `responsive.keepVisible` columns
1026
+ always survive responsive collapse, while explicitly hidden columns stay hidden.
1027
+
1028
+ Retained cells wrap, and their desktop width and pinning settings resume when
1029
+ the container widens. Responsive presentation never changes controller state,
1030
+ sorting, selection, or persisted column visibility. Header groups and structural
1031
+ row colspans follow the retained columns. If keepVisible columns exceed the
1032
+ budget, all remain visible and share the available width. Custom cell/header
1033
+ snippets should fit their cells. Default `responsiveMode="scroll"` preserves
1034
+ existing horizontal scrolling.
1035
+
1036
+ ConfirmDialog opens a native modal dialog above existing Modal and Drawer surfaces.
1037
+ Its `message` accepts plain text or a Svelte snippet (including lists and emphasis);
1038
+ each instance owns its accessible title and description ids. Escape and backdrop
1039
+ clicks request `oncancel`; the parent controls `open`. The opener regains focus
1040
+ on close. Buttons remain disabled while `loading`, and Escape stays available.
1041
+ The native top layer replaces the previous fixed div; confirmation now stacks
1042
+ above an already open modal and makes its background inert. Escape is scoped to
1043
+ the active dialog rather than handled globally. Existing `open`, `loading` and
1044
+ action callbacks retain their controlled-state contracts.
1045
+
1046
+ Browser feedback contracts run with `pnpm --filter @happyvertical/smrt-ui test:e2e`
1047
+ (after installing Playwright Chromium, or setting
1048
+ `PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH` to a local Chromium executable).
1049
+
1050
+ ToastViewport follows the topmost native modal dialog and restores its original
1051
+ host when all modals close. Its live region, dismiss buttons and actions stay
1052
+ interactive inside Modal, Drawer and ConfirmDialog. Hover and focus independently
1053
+ pause auto-dismiss, then resume the remaining duration. Configure
1054
+ `createToaster({ successDuration: 0 })` to keep success records until dismissal;
1055
+ per-toast `duration: 0` remains supported. `inset` accepts a CSS length, `anchor`
1056
+ accepts a content-region element, and `top-center`/`bottom-center` positions center
1057
+ the viewport within that region (or the window). Anchor geometry follows resize
1058
+ and scroll. Custom injected Toaster implementations may optionally implement
1059
+ `pause(id, reason)` and `resume(id, reason)` to support interaction pausing.
1060
+
1061
+ ### SegmentedControl forms
1062
+
1063
+ SegmentedControl renders native radio inputs. Set `name` to post the selected
1064
+ option value with ordinary form submission or FormData; numeric values post as
1065
+ strings while bound values retain their declared type. Unselected, unknown,
1066
+ disabled-option, disabled-control and disabled-fieldset values are omitted.
1067
+ `required` uses native form validation and accepts numeric zero. Nameless controls
1068
+ validate without posting a generated field. Arrow keys cycle enabled options;
1069
+ Home/End select the first/last enabled option. Touch density applies to the full
1070
+ clickable segment.
1071
+
1072
+ Native form reset restores the initial bound value if that option is still
1073
+ available and enabled; otherwise it clears the selection. Canceling the reset
1074
+ event preserves the current selection. Reset does not fire `onvaluechange`,
1075
+ matching native controls; bind:value reflects it.
1076
+
1077
+ ### Application icons
1078
+
1079
+ Icon includes the original menu/search/chevron/action glyphs plus `alert`,
1080
+ `warning`, `info`, `home`, `user`, `settings`, `trash`, `edit`, `calendar`,
1081
+ `clock`, `camera`, `upload`, and `download`. Import `registerIcons` from
1082
+ `@happyvertical/smrt-ui` to install an application SVG path map at startup.
1083
+ Registrations update mounted icons; the returned cleanup function removes that
1084
+ registration and restores the previous active set. Last active registration wins,
1085
+ and an explicit Icon `path` prop wins over every named set. Unknown names retain
1086
+ their empty shape. Names and paths must be nonempty strings; invalid mixed sets
1087
+ are rejected atomically. Sets are snapshotted, so later caller mutations do not
1088
+ change glyphs. Register static application assets, never request or identity data,
1089
+ and install the same application set for SSR and client hydration.