@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
@@ -6,13 +6,11 @@
6
6
  * Provides a consistent confirmation dialog for destructive actions
7
7
  * or important decisions.
8
8
  *
9
- * Accessibility (#1586): the dialog is a `role="dialog" aria-modal="true"`
10
- * surface with managed focus — on open it records the previously-focused element
11
- * and moves focus to the confirm button; Tab/Shift+Tab are trapped within the
12
- * dialog; Escape (caught at the document level while open) cancels; and focus is
13
- * restored to the opener when the dialog closes.
9
+ * Native showModal() puts confirmations above existing Modal/Drawer surfaces,
10
+ * traps focus and makes the background inert. Escape is handled locally so
11
+ * only the active confirmation requests cancellation.
14
12
  */
15
- import { tick } from 'svelte';
13
+ import { type Snippet, tick } from 'svelte';
16
14
  import { ripple } from '../../actions/ripple.js';
17
15
 
18
16
  /** Props for ConfirmDialog component */
@@ -21,8 +19,8 @@ export interface Props {
21
19
  open: boolean;
22
20
  /** Dialog title */
23
21
  title: string;
24
- /** Dialog message */
25
- message: string;
22
+ /** Plain text or rich message snippet */
23
+ message: string | Snippet;
26
24
  /** Confirm button label */
27
25
  confirmLabel?: string;
28
26
  /** Cancel button label */
@@ -49,7 +47,8 @@ const {
49
47
  oncancel,
50
48
  }: Props = $props();
51
49
 
52
- let backdropEl = $state<HTMLElement | null>(null);
50
+ const instanceId = $props.id();
51
+ let backdropEl = $state<HTMLDialogElement | null>(null);
53
52
  let confirmBtnEl = $state<HTMLButtonElement | null>(null);
54
53
  // The element focused before the dialog opened, restored on close.
55
54
  let previouslyFocused: HTMLElement | null = null;
@@ -63,38 +62,43 @@ function focusableEls(): HTMLElement[] {
63
62
  );
64
63
  }
65
64
 
66
- // Move focus into the dialog on open; restore it to the opener on close.
65
+ // Keep the parent's open prop authoritative; native cancellation requests it.
67
66
  $effect(() => {
68
- if (typeof document === 'undefined') return;
69
-
70
- if (open) {
71
- previouslyFocused = document.activeElement as HTMLElement | null;
72
- // Wait for the dialog DOM to mount, then move focus into the dialog: the
73
- // confirm action by default, the first focusable if confirm is disabled, or
74
- // the dialog container itself when every control is disabled (loading) so
75
- // focus is still contained and Escape stays reachable.
76
- void tick().then(() => {
77
- if (!open) return;
78
- const target = confirmBtnEl?.disabled
79
- ? (focusableEls()[0] ?? backdropEl)
80
- : confirmBtnEl;
81
- target?.focus();
82
- });
83
- } else if (previouslyFocused) {
84
- previouslyFocused.focus();
67
+ const dialog = backdropEl;
68
+ if (!open || !dialog) return;
69
+ previouslyFocused = document.activeElement as HTMLElement | null;
70
+ dialog.showModal();
71
+ void tick().then(() => {
72
+ if (!dialog.open) return;
73
+ const target = confirmBtnEl?.disabled
74
+ ? (focusableEls()[0] ?? dialog)
75
+ : confirmBtnEl;
76
+ target?.focus();
77
+ });
78
+ return () => {
79
+ if (dialog.open) dialog.close();
80
+ previouslyFocused?.focus();
85
81
  previouslyFocused = null;
86
- }
82
+ };
87
83
  });
88
84
 
85
+ function handleCancel(event: Event) {
86
+ event.preventDefault();
87
+ event.stopPropagation();
88
+ oncancel?.();
89
+ }
90
+
89
91
  function handleBackdropClick(e: MouseEvent) {
90
92
  if (e.target === e.currentTarget) {
91
93
  oncancel?.();
92
94
  }
93
95
  }
94
96
 
95
- // Escape handling lives at the document level (below) so it fires regardless of
96
- // where focus currently is. This handler, bound to the dialog, only traps Tab.
97
97
  function handleKeydown(e: KeyboardEvent) {
98
+ if (e.key === 'Escape') {
99
+ handleCancel(e);
100
+ return;
101
+ }
98
102
  if (e.key !== 'Tab') return;
99
103
 
100
104
  // Trap focus within the dialog so Tab can't escape to the page behind.
@@ -117,33 +121,30 @@ function handleKeydown(e: KeyboardEvent) {
117
121
  first.focus();
118
122
  }
119
123
  }
120
-
121
- function handleWindowKeydown(e: KeyboardEvent) {
122
- if (open && e.key === 'Escape') {
123
- e.preventDefault();
124
- oncancel?.();
125
- }
126
- }
127
124
  </script>
128
125
 
129
- <svelte:window onkeydown={handleWindowKeydown} />
130
-
131
126
  {#if open}
132
127
  <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
133
- <div
128
+ <dialog
134
129
  bind:this={backdropEl}
135
130
  class="dialog-backdrop"
136
- role="dialog"
137
131
  aria-modal="true"
138
- aria-labelledby="dialog-title"
139
- aria-describedby="dialog-message"
132
+ aria-labelledby={`${instanceId}-title`}
133
+ aria-describedby={`${instanceId}-message`}
140
134
  tabindex="-1"
141
135
  onclick={handleBackdropClick}
142
136
  onkeydown={handleKeydown}
137
+ oncancel={handleCancel}
143
138
  >
144
139
  <div class="dialog-content">
145
- <h2 id="dialog-title" class="dialog-title">{title}</h2>
146
- <p id="dialog-message" class="dialog-message">{message}</p>
140
+ <h2 id={`${instanceId}-title`} class="dialog-title">{title}</h2>
141
+ <div id={`${instanceId}-message`} class="dialog-message">
142
+ {#if typeof message === 'string'}
143
+ {message}
144
+ {:else}
145
+ {@render message()}
146
+ {/if}
147
+ </div>
147
148
 
148
149
  <div class="dialog-actions">
149
150
  <button
@@ -171,7 +172,7 @@ function handleWindowKeydown(e: KeyboardEvent) {
171
172
  </button>
172
173
  </div>
173
174
  </div>
174
- </div>
175
+ </dialog>
175
176
  {/if}
176
177
 
177
178
  <style>
@@ -181,9 +182,19 @@ function handleWindowKeydown(e: KeyboardEvent) {
181
182
  display: flex;
182
183
  align-items: center;
183
184
  justify-content: center;
184
- background-color: var(--smrt-color-scrim, rgba(0, 0, 0, 0.4));
185
- z-index: var(--smrt-z-index-dialog, 1000);
185
+ width: 100%;
186
+ height: 100%;
187
+ max-width: 100%;
188
+ max-height: 100%;
189
+ margin: 0;
190
+ border: none;
191
+ box-sizing: border-box;
192
+ background: transparent;
186
193
  padding: 1rem;
194
+ }
195
+
196
+ .dialog-backdrop::backdrop {
197
+ background-color: var(--smrt-color-scrim, rgba(0, 0, 0, 0.4));
187
198
  backdrop-filter: blur(2px);
188
199
  }
189
200
 
@@ -1,11 +1,23 @@
1
+ /**
2
+ * ConfirmDialog - Modal confirmation dialog
3
+ * refactored for Material 3
4
+ *
5
+ * Provides a consistent confirmation dialog for destructive actions
6
+ * or important decisions.
7
+ *
8
+ * Native showModal() puts confirmations above existing Modal/Drawer surfaces,
9
+ * traps focus and makes the background inert. Escape is handled locally so
10
+ * only the active confirmation requests cancellation.
11
+ */
12
+ import { type Snippet } from 'svelte';
1
13
  /** Props for ConfirmDialog component */
2
14
  export interface Props {
3
15
  /** Whether the dialog is open */
4
16
  open: boolean;
5
17
  /** Dialog title */
6
18
  title: string;
7
- /** Dialog message */
8
- message: string;
19
+ /** Plain text or rich message snippet */
20
+ message: string | Snippet;
9
21
  /** Confirm button label */
10
22
  confirmLabel?: string;
11
23
  /** Cancel button label */
@@ -1 +1 @@
1
- {"version":3,"file":"ConfirmDialog.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/feedback/ConfirmDialog.svelte.ts"],"names":[],"mappings":"AAoBA,wCAAwC;AACxC,MAAM,WAAW,KAAK;IACpB,iCAAiC;IACjC,IAAI,EAAE,OAAO,CAAC;IACd,mBAAmB;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,qBAAqB;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,2BAA2B;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0BAA0B;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gDAAgD;IAChD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,oCAAoC;IACpC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AA2HD,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"ConfirmDialog.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/feedback/ConfirmDialog.svelte.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,KAAK,OAAO,EAAQ,MAAM,QAAQ,CAAC;AAI5C,wCAAwC;AACxC,MAAM,WAAW,KAAK;IACpB,iCAAiC;IACjC,IAAI,EAAE,OAAO,CAAC;IACd,mBAAmB;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1B,2BAA2B;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0BAA0B;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gDAAgD;IAChD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,oCAAoC;IACpC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AA8HD,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
@@ -0,0 +1,224 @@
1
+ <script lang="ts">
2
+ /**
3
+ * PhoneSheet — the phone replacement for drawers and centered modals.
4
+ *
5
+ * - `variant="page"`: a full-screen page (a notifications list, a picker);
6
+ * - `variant="sheet"`: slides up from the bottom with a grabber; a swipe down
7
+ * on its header closes it.
8
+ *
9
+ * It closes on its close button and Escape, moves focus into itself when it
10
+ * opens, and returns focus to the opener when it closes. It is positioned
11
+ * `absolute; inset: 0`, so it covers its nearest positioned ancestor — inside
12
+ * an `AdminShell`, render it in the `overlays` snippet so it covers the
13
+ * screen above the phone bottom bar. The host may keep it mounted while
14
+ * closed (it is hidden and inert), so a chat inside keeps its draft.
15
+ */
16
+ import type { Snippet } from 'svelte';
17
+ import { tick } from 'svelte';
18
+ import { swipeDismiss } from '../../actions/swipe-dismiss.js';
19
+ import { M } from '../../i18n/strings.ui.js';
20
+ import { useI18n } from '../../i18n/use-i18n.js';
21
+
22
+ export interface Props {
23
+ /** Whether the sheet is shown. */
24
+ open: boolean;
25
+ /** Heading shown in the sheet header; also names the dialog. */
26
+ title: string;
27
+ /** Called on the close button, Escape, or a swipe down (sheet). */
28
+ onclose: () => void;
29
+ /** `page` covers the area; `sheet` slides up with rounded corners. */
30
+ variant?: 'page' | 'sheet';
31
+ /** Element id, for `aria-controls` on the button that opens it. */
32
+ id?: string;
33
+ /** Accessible name of the close button (default "Close {title}"). */
34
+ closeLabel?: string;
35
+ /** Sheet body. */
36
+ children: Snippet;
37
+ }
38
+
39
+ let {
40
+ open,
41
+ title,
42
+ onclose,
43
+ variant = 'page',
44
+ id,
45
+ closeLabel,
46
+ children,
47
+ }: Props = $props();
48
+
49
+ const { t } = useI18n();
50
+ const instanceId = $props.id();
51
+ const headingId = `phone-sheet-${instanceId}`;
52
+ let root: HTMLElement | undefined = $state();
53
+ let returnFocus: HTMLElement | null = null;
54
+
55
+ $effect(() => {
56
+ if (!open) return;
57
+ returnFocus =
58
+ document.activeElement instanceof HTMLElement
59
+ ? document.activeElement
60
+ : null;
61
+ void tick().then(() => root?.focus({ preventScroll: true }));
62
+ return () => {
63
+ const target = returnFocus;
64
+ returnFocus = null;
65
+ if (target && document.contains(target)) {
66
+ target.focus({ preventScroll: true });
67
+ }
68
+ };
69
+ });
70
+
71
+ function onkeydown(event: KeyboardEvent): void {
72
+ if (event.key !== 'Escape' || event.defaultPrevented) return;
73
+ event.preventDefault();
74
+ event.stopPropagation();
75
+ onclose();
76
+ }
77
+ </script>
78
+
79
+ <div
80
+ bind:this={root}
81
+ {id}
82
+ class="phone-sheet phone-sheet--{variant}"
83
+ class:open
84
+ role="dialog"
85
+ aria-modal="false"
86
+ aria-labelledby={headingId}
87
+ aria-hidden={!open}
88
+ inert={!open}
89
+ tabindex="-1"
90
+ data-escape-scope
91
+ data-testid="phone-sheet"
92
+ {onkeydown}
93
+ >
94
+ <header
95
+ class="phone-sheet__header"
96
+ use:swipeDismiss={{
97
+ direction: 'down',
98
+ onDismiss: onclose,
99
+ enabled: variant === 'sheet',
100
+ }}
101
+ >
102
+ {#if variant === 'sheet'}
103
+ <span class="phone-sheet__grabber" aria-hidden="true"></span>
104
+ {/if}
105
+ <h2 id={headingId}>{title}</h2>
106
+ <button
107
+ type="button"
108
+ class="phone-sheet__close"
109
+ aria-label={closeLabel ?? t(M['ui.phone_sheet.close'], { title })}
110
+ onclick={onclose}
111
+ >
112
+ <svg
113
+ viewBox="0 0 24 24"
114
+ width="22"
115
+ height="22"
116
+ aria-hidden="true"
117
+ fill="none"
118
+ stroke="currentColor"
119
+ stroke-width="2"
120
+ stroke-linecap="round"
121
+ >
122
+ <path d="M18 6 6 18M6 6l12 12" />
123
+ </svg>
124
+ </button>
125
+ </header>
126
+ <div class="phone-sheet__body">
127
+ {@render children()}
128
+ </div>
129
+ </div>
130
+
131
+ <style>
132
+ .phone-sheet {
133
+ position: absolute;
134
+ inset: 0;
135
+ z-index: 30;
136
+ display: grid;
137
+ grid-template-rows: auto minmax(0, 1fr);
138
+ background: var(--smrt-color-surface);
139
+ color: var(--smrt-color-on-surface);
140
+ transform: translateY(100%);
141
+ visibility: hidden;
142
+ transition:
143
+ transform var(--smrt-duration-medium1, 250ms) var(--smrt-easing-standard, ease),
144
+ visibility 0s linear var(--smrt-duration-medium1, 250ms);
145
+ outline: none;
146
+ }
147
+
148
+ .phone-sheet.open {
149
+ transform: none;
150
+ visibility: visible;
151
+ transition: transform var(--smrt-duration-medium1, 250ms)
152
+ var(--smrt-easing-standard, ease);
153
+ }
154
+
155
+ .phone-sheet--sheet {
156
+ border-start-start-radius: var(--smrt-radius-lg);
157
+ border-start-end-radius: var(--smrt-radius-lg);
158
+ box-shadow: var(--smrt-elevation-3);
159
+ }
160
+
161
+ .phone-sheet__header {
162
+ position: relative;
163
+ display: flex;
164
+ align-items: center;
165
+ justify-content: space-between;
166
+ gap: var(--smrt-spacing-2);
167
+ min-block-size: 3.5rem;
168
+ padding-block: 0;
169
+ padding-inline: var(--smrt-spacing-4) var(--smrt-spacing-2);
170
+ border-block-end: 1px solid var(--smrt-color-outline-variant);
171
+ touch-action: pan-x;
172
+ }
173
+
174
+ .phone-sheet__grabber {
175
+ position: absolute;
176
+ inset-block-start: var(--smrt-spacing-1);
177
+ inset-inline-start: 50%;
178
+ inline-size: 2.5rem;
179
+ block-size: 0.25rem;
180
+ border-radius: var(--smrt-radius-full);
181
+ background: var(--smrt-color-outline-variant);
182
+ transform: translateX(-50%);
183
+ }
184
+
185
+ .phone-sheet__header h2 {
186
+ margin: 0;
187
+ font: var(--smrt-typography-title-medium-font);
188
+ }
189
+
190
+ .phone-sheet__close {
191
+ display: inline-grid;
192
+ place-items: center;
193
+ inline-size: 2.75rem;
194
+ block-size: 2.75rem;
195
+ border: 0;
196
+ border-radius: var(--smrt-radius-full);
197
+ background: transparent;
198
+ color: inherit;
199
+ cursor: pointer;
200
+ }
201
+
202
+ .phone-sheet__close:hover,
203
+ .phone-sheet__close:focus-visible {
204
+ background: var(--smrt-color-surface-container-high);
205
+ }
206
+
207
+ .phone-sheet__close:focus-visible {
208
+ outline: 2px solid var(--smrt-color-primary);
209
+ outline-offset: -2px;
210
+ }
211
+
212
+ .phone-sheet__body {
213
+ min-block-size: 0;
214
+ overflow: auto;
215
+ overscroll-behavior: contain;
216
+ }
217
+
218
+ @media (prefers-reduced-motion: reduce) {
219
+ .phone-sheet,
220
+ .phone-sheet.open {
221
+ transition: none;
222
+ }
223
+ }
224
+ </style>
@@ -0,0 +1,35 @@
1
+ /**
2
+ * PhoneSheet — the phone replacement for drawers and centered modals.
3
+ *
4
+ * - `variant="page"`: a full-screen page (a notifications list, a picker);
5
+ * - `variant="sheet"`: slides up from the bottom with a grabber; a swipe down
6
+ * on its header closes it.
7
+ *
8
+ * It closes on its close button and Escape, moves focus into itself when it
9
+ * opens, and returns focus to the opener when it closes. It is positioned
10
+ * `absolute; inset: 0`, so it covers its nearest positioned ancestor — inside
11
+ * an `AdminShell`, render it in the `overlays` snippet so it covers the
12
+ * screen above the phone bottom bar. The host may keep it mounted while
13
+ * closed (it is hidden and inert), so a chat inside keeps its draft.
14
+ */
15
+ import type { Snippet } from 'svelte';
16
+ export interface Props {
17
+ /** Whether the sheet is shown. */
18
+ open: boolean;
19
+ /** Heading shown in the sheet header; also names the dialog. */
20
+ title: string;
21
+ /** Called on the close button, Escape, or a swipe down (sheet). */
22
+ onclose: () => void;
23
+ /** `page` covers the area; `sheet` slides up with rounded corners. */
24
+ variant?: 'page' | 'sheet';
25
+ /** Element id, for `aria-controls` on the button that opens it. */
26
+ id?: string;
27
+ /** Accessible name of the close button (default "Close {title}"). */
28
+ closeLabel?: string;
29
+ /** Sheet body. */
30
+ children: Snippet;
31
+ }
32
+ declare const PhoneSheet: import("svelte").Component<Props, {}, "">;
33
+ type PhoneSheet = ReturnType<typeof PhoneSheet>;
34
+ export default PhoneSheet;
35
+ //# sourceMappingURL=PhoneSheet.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PhoneSheet.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/feedback/PhoneSheet.svelte.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAOtC,MAAM,WAAW,KAAK;IACpB,kCAAkC;IAClC,IAAI,EAAE,OAAO,CAAC;IACd,gEAAgE;IAChE,KAAK,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,mEAAmE;IACnE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kBAAkB;IAClB,QAAQ,EAAE,OAAO,CAAC;CACnB;AA2ED,QAAA,MAAM,UAAU,2CAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
@@ -10,7 +10,17 @@ export interface Props {
10
10
  /** Toaster instance that manages the toast queue. */
11
11
  toaster?: Toaster;
12
12
  /** Screen corner where toasts appear. */
13
- position?: 'top-start' | 'top-end' | 'bottom-start' | 'bottom-end';
13
+ position?:
14
+ | 'top-start'
15
+ | 'top-center'
16
+ | 'top-end'
17
+ | 'bottom-start'
18
+ | 'bottom-center'
19
+ | 'bottom-end';
20
+ /** CSS length inset from the window or anchor region. */
21
+ inset?: string;
22
+ /** Optional content region used for toast placement. */
23
+ anchor?: HTMLElement | null;
14
24
  /** CSS class to apply to the viewport container. */
15
25
  class?: string;
16
26
  }
@@ -18,10 +28,131 @@ let {
18
28
  toaster = defaultToaster,
19
29
  position = 'bottom-end',
20
30
  class: className = '',
31
+ inset = 'var(--smrt-spacing-4, 1rem)',
32
+ anchor = null,
21
33
  }: Props = $props();
34
+ const instanceId = $props.id();
35
+ const hoverReason = `${instanceId}-hover`;
36
+ const focusReason = `${instanceId}-focus`;
22
37
  let toasts = $state<Toast[]>([]);
38
+ let bounds = $state<{
39
+ top: number;
40
+ right: number;
41
+ bottom: number;
42
+ left: number;
43
+ width: number;
44
+ } | null>(null);
23
45
 
24
- $effect(() => toaster.subscribe((next) => (toasts = next)));
46
+ $effect(() => {
47
+ if (!anchor) {
48
+ bounds = null;
49
+ return;
50
+ }
51
+ const region = anchor;
52
+ function measure() {
53
+ const rect = region.getBoundingClientRect();
54
+ bounds = {
55
+ top: rect.top,
56
+ right: window.innerWidth - rect.right,
57
+ bottom: window.innerHeight - rect.bottom,
58
+ left: rect.left,
59
+ width: rect.width,
60
+ };
61
+ }
62
+ measure();
63
+ const observer = new ResizeObserver(measure);
64
+ observer.observe(region);
65
+ window.addEventListener('resize', measure);
66
+ document.addEventListener('scroll', measure, true);
67
+ return () => {
68
+ observer.disconnect();
69
+ window.removeEventListener('resize', measure);
70
+ document.removeEventListener('scroll', measure, true);
71
+ };
72
+ });
73
+
74
+ // A popover outside the topmost modal remains inert. Keep the live region and
75
+ // its controls inside the active dialog instead, preserving their Svelte owner.
76
+ function followModal(node: HTMLElement) {
77
+ const marker = document.createComment('toast viewport host');
78
+ node.before(marker);
79
+ let stack: HTMLDialogElement[] = [];
80
+ function update(records: MutationRecord[] = []) {
81
+ const dialogs = Array.from(
82
+ document.querySelectorAll<HTMLDialogElement>('dialog[open]'),
83
+ ).filter((dialog) => dialog.matches(':modal'));
84
+ stack = stack.filter((dialog) => dialogs.includes(dialog));
85
+ for (const dialog of dialogs)
86
+ if (!stack.includes(dialog)) stack.push(dialog);
87
+ for (const record of records) {
88
+ if (record.type !== 'attributes' || record.attributeName !== 'open')
89
+ continue;
90
+ const dialog = record.target as HTMLDialogElement;
91
+ if (!dialogs.includes(dialog)) continue;
92
+ stack = [...stack.filter((item) => item !== dialog), dialog];
93
+ }
94
+ const focused = dialogs
95
+ .filter((dialog) => dialog.contains(document.activeElement))
96
+ .at(-1);
97
+ if (focused)
98
+ stack = [...stack.filter((dialog) => dialog !== focused), focused];
99
+ const host = stack.at(-1) ?? marker.parentElement;
100
+ if (!host || node.parentElement === host) return;
101
+ const active = node.contains(document.activeElement)
102
+ ? (document.activeElement as HTMLElement)
103
+ : null;
104
+ if (host === marker.parentElement) marker.after(node);
105
+ else host.append(node);
106
+ active?.focus();
107
+ if (!node.contains(document.activeElement)) {
108
+ for (const toast of toasts) toaster.resume?.(toast.id, focusReason);
109
+ }
110
+ }
111
+ update();
112
+ const observer = new MutationObserver(update);
113
+ observer.observe(document.documentElement, {
114
+ subtree: true,
115
+ childList: true,
116
+ attributes: true,
117
+ attributeFilter: ['open'],
118
+ });
119
+ const focus = () => update();
120
+ document.addEventListener('focusin', focus);
121
+ return {
122
+ destroy() {
123
+ observer.disconnect();
124
+ document.removeEventListener('focusin', focus);
125
+ marker.remove();
126
+ node.remove();
127
+ },
128
+ };
129
+ }
130
+
131
+ function handleFocusOut(event: FocusEvent, id: string) {
132
+ if (
133
+ event.currentTarget instanceof HTMLElement &&
134
+ event.relatedTarget instanceof Node &&
135
+ event.currentTarget.contains(event.relatedTarget)
136
+ )
137
+ return;
138
+ toaster.resume?.(id, focusReason);
139
+ }
140
+
141
+ $effect(() => {
142
+ const queue = toaster;
143
+ let ids: string[] = [];
144
+ const unsubscribe = queue.subscribe((next) => {
145
+ ids = next.map((toast) => toast.id);
146
+ toasts = next;
147
+ });
148
+ return () => {
149
+ unsubscribe();
150
+ for (const id of ids) {
151
+ queue.resume?.(id, hoverReason);
152
+ queue.resume?.(id, focusReason);
153
+ }
154
+ };
155
+ });
25
156
 
26
157
  async function runAction(toast: Toast) {
27
158
  await toast.action?.run();
@@ -29,23 +160,40 @@ async function runAction(toast: Toast) {
29
160
  }
30
161
  </script>
31
162
 
32
- <section class="viewport viewport--{position} {className}" aria-label="Notifications" aria-live="polite">
163
+ <section use:followModal class="viewport viewport--{position} {className}" aria-label="Notifications" aria-live="polite"
164
+ style:--toast-inset={inset}
165
+ style:--toast-top={bounds ? `${bounds.top}px` : undefined}
166
+ style:--toast-right={bounds ? `${bounds.right}px` : undefined}
167
+ style:--toast-bottom={bounds ? `${bounds.bottom}px` : undefined}
168
+ style:--toast-left={bounds ? `${bounds.left}px` : undefined}
169
+ style:--toast-region-width={bounds ? `${bounds.width}px` : undefined}
170
+ >
33
171
  {#each toasts as toast (toast.id)}
172
+ <div role="group" aria-label={toast.title ?? 'Notification'}
173
+ onmouseenter={() => toaster.pause?.(toast.id, hoverReason)}
174
+ onmouseleave={() => toaster.resume?.(toast.id, hoverReason)}
175
+ onfocusin={() => toaster.pause?.(toast.id, focusReason)}
176
+ onfocusout={(event) => handleFocusOut(event, toast.id)}
177
+ >
34
178
  <Alert variant={toast.variant} title={toast.title} dismissible ondismiss={() => toaster.dismiss(toast.id)}>
35
179
  {toast.message}
36
180
  {#snippet action()}
37
181
  {#if toast.action}<button type="button" onclick={() => runAction(toast)}>{toast.action.label}</button>{/if}
38
182
  {/snippet}
39
183
  </Alert>
184
+ </div>
40
185
  {/each}
41
186
  </section>
42
187
 
43
188
  <style>
44
- .viewport { position: fixed; z-index: var(--smrt-z-index-toast, 1200); display: grid; width: min(24rem, calc(100vw - 2 * var(--smrt-spacing-4))); gap: var(--smrt-spacing-2); pointer-events: none; }
189
+ .viewport { position: fixed; z-index: var(--smrt-z-index-toast, 1200); display: grid; width: min(24rem, calc(var(--toast-region-width, 100vw) - 2 * var(--toast-inset))); gap: var(--smrt-spacing-2); pointer-events: none; }
45
190
  .viewport :global(.alert) { pointer-events: auto; box-shadow: var(--smrt-elevation-3); }
46
- .viewport--top-start { top: var(--smrt-spacing-4); left: var(--smrt-spacing-4); }
47
- .viewport--top-end { top: var(--smrt-spacing-4); right: var(--smrt-spacing-4); }
48
- .viewport--bottom-start { bottom: var(--smrt-spacing-4); left: var(--smrt-spacing-4); }
49
- .viewport--bottom-end { right: var(--smrt-spacing-4); bottom: var(--smrt-spacing-4); }
191
+ .viewport--top-start { top: calc(var(--toast-top, 0px) + var(--toast-inset)); left: calc(var(--toast-left, 0px) + var(--toast-inset)); }
192
+ .viewport--top-end { top: calc(var(--toast-top, 0px) + var(--toast-inset)); right: calc(var(--toast-right, 0px) + var(--toast-inset)); }
193
+ .viewport--bottom-start { bottom: calc(var(--toast-bottom, 0px) + var(--toast-inset)); left: calc(var(--toast-left, 0px) + var(--toast-inset)); }
194
+ .viewport--bottom-end { right: calc(var(--toast-right, 0px) + var(--toast-inset)); bottom: calc(var(--toast-bottom, 0px) + var(--toast-inset)); }
195
+ .viewport--top-center, .viewport--bottom-center { left: calc(var(--toast-left, 0px) + var(--toast-region-width, 100vw) / 2); transform: translateX(-50%); }
196
+ .viewport--top-center { top: calc(var(--toast-top, 0px) + var(--toast-inset)); }
197
+ .viewport--bottom-center { bottom: calc(var(--toast-bottom, 0px) + var(--toast-inset)); }
50
198
  button { padding: var(--smrt-spacing-1) var(--smrt-spacing-2); border: 1px solid currentColor; border-radius: var(--smrt-radius-small); background: transparent; color: inherit; font: inherit; cursor: pointer; }
51
199
  </style>