@happyvertical/smrt-ui 0.51.39 → 0.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (392) hide show
  1. package/AGENTS.md +25 -3
  2. package/README.md +558 -8
  3. package/dist/actions/__tests__/swipe-dismiss.test.js +69 -0
  4. package/dist/actions/swipe-dismiss.d.ts +42 -0
  5. package/dist/actions/swipe-dismiss.d.ts.map +1 -0
  6. package/dist/actions/swipe-dismiss.js +69 -0
  7. package/dist/components/calendar/Calendar.svelte +5 -0
  8. package/dist/components/calendar/Calendar.svelte.d.ts.map +1 -1
  9. package/dist/components/calendar/CalendarView.svelte +1250 -0
  10. package/dist/components/calendar/CalendarView.svelte.d.ts +66 -0
  11. package/dist/components/calendar/CalendarView.svelte.d.ts.map +1 -0
  12. package/dist/components/calendar/DayView.svelte +5 -0
  13. package/dist/components/calendar/DayView.svelte.d.ts.map +1 -1
  14. package/dist/components/calendar/__tests__/CalendarView.test.js +226 -0
  15. package/dist/components/calendar/__tests__/calendar-model.test.js +138 -0
  16. package/dist/components/calendar/calendar-model.d.ts +178 -0
  17. package/dist/components/calendar/calendar-model.d.ts.map +1 -0
  18. package/dist/components/calendar/calendar-model.js +362 -0
  19. package/dist/components/calendar/index.d.ts +12 -1
  20. package/dist/components/calendar/index.d.ts.map +1 -1
  21. package/dist/components/calendar/index.js +11 -1
  22. package/dist/components/data/CollectionList.svelte +23 -5
  23. package/dist/components/data/CollectionList.svelte.d.ts +8 -2
  24. package/dist/components/data/CollectionList.svelte.d.ts.map +1 -1
  25. package/dist/components/data/DataTable.svelte +178 -55
  26. package/dist/components/data/DataTable.svelte.d.ts.map +1 -1
  27. package/dist/components/data/DataTableController.d.ts +4 -0
  28. package/dist/components/data/DataTableController.d.ts.map +1 -1
  29. package/dist/components/data/DataTableController.js +11 -4
  30. package/dist/components/data/DataTableResponsive.d.ts +4 -0
  31. package/dist/components/data/DataTableResponsive.d.ts.map +1 -0
  32. package/dist/components/data/DataTableResponsive.js +22 -0
  33. package/dist/components/data/ListSortSelect.svelte +121 -0
  34. package/dist/components/data/ListSortSelect.svelte.d.ts +22 -0
  35. package/dist/components/data/ListSortSelect.svelte.d.ts.map +1 -0
  36. package/dist/components/data/SortableHeader.svelte +169 -0
  37. package/dist/components/data/SortableHeader.svelte.d.ts +30 -0
  38. package/dist/components/data/SortableHeader.svelte.d.ts.map +1 -0
  39. package/dist/components/data/__tests__/CollectionList.test.js +37 -0
  40. package/dist/components/data/__tests__/DataTable.test.js +76 -0
  41. package/dist/components/data/__tests__/DataTableController.test.js +35 -0
  42. package/dist/components/data/__tests__/DataTableResponsive.test.js +80 -0
  43. package/dist/components/data/__tests__/SortableHeader.test.js +101 -0
  44. package/dist/components/data/__tests__/link-step-surface.test.js +172 -0
  45. package/dist/components/data/__tests__/list-sort-state.svelte.test.js +71 -0
  46. package/dist/components/data/__tests__/list-sort.test.js +91 -0
  47. package/dist/components/data/__tests__/responsive-table.fixture.svelte +29 -0
  48. package/dist/components/data/__tests__/responsive-table.fixture.svelte.d.ts +7 -0
  49. package/dist/components/data/__tests__/responsive-table.fixture.svelte.d.ts.map +1 -0
  50. package/dist/components/data/__tests__/surface-navigation.test.js +121 -0
  51. package/dist/components/data/data-surface.d.ts +1 -0
  52. package/dist/components/data/data-surface.d.ts.map +1 -1
  53. package/dist/components/data/data-surface.js +1 -0
  54. package/dist/components/data/index.d.ts +6 -0
  55. package/dist/components/data/index.d.ts.map +1 -1
  56. package/dist/components/data/index.js +6 -0
  57. package/dist/components/data/link-surface.d.ts +79 -0
  58. package/dist/components/data/link-surface.d.ts.map +1 -0
  59. package/dist/components/data/link-surface.js +155 -0
  60. package/dist/components/data/list-sort-state.svelte.d.ts +39 -0
  61. package/dist/components/data/list-sort-state.svelte.d.ts.map +1 -0
  62. package/dist/components/data/list-sort-state.svelte.js +47 -0
  63. package/dist/components/data/list-sort.d.ts +94 -0
  64. package/dist/components/data/list-sort.d.ts.map +1 -0
  65. package/dist/components/data/list-sort.js +182 -0
  66. package/dist/components/data/step-surface.d.ts +73 -0
  67. package/dist/components/data/step-surface.d.ts.map +1 -0
  68. package/dist/components/data/step-surface.js +177 -0
  69. package/dist/components/data/surface-navigation.d.ts +49 -0
  70. package/dist/components/data/surface-navigation.d.ts.map +1 -0
  71. package/dist/components/data/surface-navigation.js +100 -0
  72. package/dist/components/data/types.d.ts +27 -0
  73. package/dist/components/data/types.d.ts.map +1 -1
  74. package/dist/components/display/DateDisplay.svelte +76 -14
  75. package/dist/components/display/DateDisplay.svelte.d.ts +2 -0
  76. package/dist/components/display/DateDisplay.svelte.d.ts.map +1 -1
  77. package/dist/components/display/Icon.svelte +3 -16
  78. package/dist/components/display/Icon.svelte.d.ts +0 -9
  79. package/dist/components/display/Icon.svelte.d.ts.map +1 -1
  80. package/dist/components/display/StatusBadge.svelte +29 -2
  81. package/dist/components/display/StatusBadge.svelte.d.ts +4 -2
  82. package/dist/components/display/StatusBadge.svelte.d.ts.map +1 -1
  83. package/dist/components/display/__tests__/DateDisplay.test.js +82 -1
  84. package/dist/components/display/__tests__/Icon.test.js +132 -2
  85. package/dist/components/display/__tests__/StatusBadge.test.js +78 -0
  86. package/dist/components/display/icons.svelte.d.ts +11 -0
  87. package/dist/components/display/icons.svelte.d.ts.map +1 -0
  88. package/dist/components/display/icons.svelte.js +60 -0
  89. package/dist/components/display/index.d.ts +3 -1
  90. package/dist/components/display/index.d.ts.map +1 -1
  91. package/dist/components/display/index.js +1 -0
  92. package/dist/components/display/types.d.ts +2 -0
  93. package/dist/components/display/types.d.ts.map +1 -1
  94. package/dist/components/feedback/ConfirmDialog.svelte +59 -48
  95. package/dist/components/feedback/ConfirmDialog.svelte.d.ts +14 -2
  96. package/dist/components/feedback/ConfirmDialog.svelte.d.ts.map +1 -1
  97. package/dist/components/feedback/PhoneSheet.svelte +224 -0
  98. package/dist/components/feedback/PhoneSheet.svelte.d.ts +35 -0
  99. package/dist/components/feedback/PhoneSheet.svelte.d.ts.map +1 -0
  100. package/dist/components/feedback/ToastViewport.svelte +156 -8
  101. package/dist/components/feedback/ToastViewport.svelte.d.ts +5 -1
  102. package/dist/components/feedback/ToastViewport.svelte.d.ts.map +1 -1
  103. package/dist/components/feedback/WorkingStrip.svelte +426 -0
  104. package/dist/components/feedback/WorkingStrip.svelte.d.ts +37 -0
  105. package/dist/components/feedback/WorkingStrip.svelte.d.ts.map +1 -0
  106. package/dist/components/feedback/__tests__/ConfirmDialog.test.js +38 -20
  107. package/dist/components/feedback/__tests__/PhoneSheet.test.js +101 -0
  108. package/dist/components/feedback/__tests__/WorkingStrip.test.js +172 -0
  109. package/dist/components/feedback/__tests__/confirm-message.fixture.svelte +9 -0
  110. package/dist/components/feedback/__tests__/confirm-message.fixture.svelte.d.ts +19 -0
  111. package/dist/components/feedback/__tests__/confirm-message.fixture.svelte.d.ts.map +1 -0
  112. package/dist/components/feedback/__tests__/toast.test.js +142 -1
  113. package/dist/components/feedback/index.d.ts +5 -1
  114. package/dist/components/feedback/index.d.ts.map +1 -1
  115. package/dist/components/feedback/index.js +5 -0
  116. package/dist/components/feedback/toast.d.ts +13 -1
  117. package/dist/components/feedback/toast.d.ts.map +1 -1
  118. package/dist/components/feedback/toast.js +51 -10
  119. package/dist/components/feedback/working-status.d.ts +34 -0
  120. package/dist/components/feedback/working-status.d.ts.map +1 -0
  121. package/dist/components/feedback/working-status.js +5 -0
  122. package/dist/components/forms/CameraCapture.svelte +619 -0
  123. package/dist/components/forms/CameraCapture.svelte.d.ts +42 -0
  124. package/dist/components/forms/CameraCapture.svelte.d.ts.map +1 -0
  125. package/dist/components/forms/Checkbox.svelte +34 -3
  126. package/dist/components/forms/Checkbox.svelte.d.ts +2 -0
  127. package/dist/components/forms/Checkbox.svelte.d.ts.map +1 -1
  128. package/dist/components/forms/Combobox.svelte +108 -12
  129. package/dist/components/forms/Combobox.svelte.d.ts +6 -0
  130. package/dist/components/forms/Combobox.svelte.d.ts.map +1 -1
  131. package/dist/components/forms/DictationButton.svelte +128 -0
  132. package/dist/components/forms/DictationButton.svelte.d.ts +19 -0
  133. package/dist/components/forms/DictationButton.svelte.d.ts.map +1 -0
  134. package/dist/components/forms/DictationStatus.svelte +164 -0
  135. package/dist/components/forms/DictationStatus.svelte.d.ts +11 -0
  136. package/dist/components/forms/DictationStatus.svelte.d.ts.map +1 -0
  137. package/dist/components/forms/Fieldset.svelte +5 -1
  138. package/dist/components/forms/Fieldset.svelte.d.ts +2 -0
  139. package/dist/components/forms/Fieldset.svelte.d.ts.map +1 -1
  140. package/dist/components/forms/Form.svelte +5 -0
  141. package/dist/components/forms/Form.svelte.d.ts +1 -0
  142. package/dist/components/forms/Form.svelte.d.ts.map +1 -1
  143. package/dist/components/forms/FormActionBar.svelte +84 -0
  144. package/dist/components/forms/FormActionBar.svelte.d.ts +23 -0
  145. package/dist/components/forms/FormActionBar.svelte.d.ts.map +1 -0
  146. package/dist/components/forms/Input.svelte +17 -1
  147. package/dist/components/forms/Input.svelte.d.ts +2 -0
  148. package/dist/components/forms/Input.svelte.d.ts.map +1 -1
  149. package/dist/components/forms/Listbox.svelte +39 -1
  150. package/dist/components/forms/Listbox.svelte.d.ts.map +1 -1
  151. package/dist/components/forms/MultiSelect.svelte +41 -1
  152. package/dist/components/forms/MultiSelect.svelte.d.ts.map +1 -1
  153. package/dist/components/forms/Radio.svelte +16 -4
  154. package/dist/components/forms/RadioGroup.svelte +7 -1
  155. package/dist/components/forms/RadioGroup.svelte.d.ts +2 -0
  156. package/dist/components/forms/RadioGroup.svelte.d.ts.map +1 -1
  157. package/dist/components/forms/SearchInput.svelte +207 -0
  158. package/dist/components/forms/SearchInput.svelte.d.ts +22 -0
  159. package/dist/components/forms/SearchInput.svelte.d.ts.map +1 -0
  160. package/dist/components/forms/SegmentedControl.svelte +152 -11
  161. package/dist/components/forms/SegmentedControl.svelte.d.ts +4 -0
  162. package/dist/components/forms/SegmentedControl.svelte.d.ts.map +1 -1
  163. package/dist/components/forms/Select.svelte +17 -1
  164. package/dist/components/forms/Select.svelte.d.ts +2 -0
  165. package/dist/components/forms/Select.svelte.d.ts.map +1 -1
  166. package/dist/components/forms/SignaturePad.svelte +436 -0
  167. package/dist/components/forms/SignaturePad.svelte.d.ts +32 -0
  168. package/dist/components/forms/SignaturePad.svelte.d.ts.map +1 -0
  169. package/dist/components/forms/StagedControlReview.svelte +21 -12
  170. package/dist/components/forms/StagedControlReview.svelte.d.ts.map +1 -1
  171. package/dist/components/forms/Switch.svelte +18 -5
  172. package/dist/components/forms/Switch.svelte.d.ts +2 -0
  173. package/dist/components/forms/Switch.svelte.d.ts.map +1 -1
  174. package/dist/components/forms/TagsInput.svelte +8 -1
  175. package/dist/components/forms/Textarea.svelte +16 -1
  176. package/dist/components/forms/Textarea.svelte.d.ts +2 -0
  177. package/dist/components/forms/Textarea.svelte.d.ts.map +1 -1
  178. package/dist/components/forms/__tests__/CameraCapture.test.js +504 -0
  179. package/dist/components/forms/__tests__/Combobox.test.js +354 -0
  180. package/dist/components/forms/__tests__/ControlHitArea.test.js +73 -0
  181. package/dist/components/forms/__tests__/DictationButton.test.js +176 -0
  182. package/dist/components/forms/__tests__/Fieldset.test.js +37 -0
  183. package/dist/components/forms/__tests__/Form.test.js +46 -0
  184. package/dist/components/forms/__tests__/FormActionBar.test.js +22 -0
  185. package/dist/components/forms/__tests__/Listbox.test.js +303 -0
  186. package/dist/components/forms/__tests__/MultiSelect.test.js +281 -0
  187. package/dist/components/forms/__tests__/SearchInput.test.js +92 -0
  188. package/dist/components/forms/__tests__/SignaturePad.test.js +338 -0
  189. package/dist/components/forms/__tests__/StagedControlReview.test.js +37 -3
  190. package/dist/components/forms/__tests__/camera-capture-session.test.js +207 -0
  191. package/dist/components/forms/__tests__/capture-form-retry.test.js +166 -0
  192. package/dist/components/forms/__tests__/capture-form.fixture.svelte +38 -0
  193. package/dist/components/forms/__tests__/capture-form.fixture.svelte.d.ts +10 -0
  194. package/dist/components/forms/__tests__/capture-form.fixture.svelte.d.ts.map +1 -0
  195. package/dist/components/forms/__tests__/capture-test-env.d.ts +70 -0
  196. package/dist/components/forms/__tests__/capture-test-env.d.ts.map +1 -0
  197. package/dist/components/forms/__tests__/capture-test-env.js +122 -0
  198. package/dist/components/forms/__tests__/capture-touch.fixture.svelte +15 -0
  199. package/dist/components/forms/__tests__/capture-touch.fixture.svelte.d.ts +8 -0
  200. package/dist/components/forms/__tests__/capture-touch.fixture.svelte.d.ts.map +1 -0
  201. package/dist/components/forms/__tests__/combobox-form.fixture.svelte +29 -0
  202. package/dist/components/forms/__tests__/combobox-form.fixture.svelte.d.ts +14 -0
  203. package/dist/components/forms/__tests__/combobox-form.fixture.svelte.d.ts.map +1 -0
  204. package/dist/components/forms/__tests__/combobox-labels.fixture.svelte +19 -0
  205. package/dist/components/forms/__tests__/combobox-labels.fixture.svelte.d.ts +11 -0
  206. package/dist/components/forms/__tests__/combobox-labels.fixture.svelte.d.ts.map +1 -0
  207. package/dist/components/forms/__tests__/composite-registration.fixture.svelte +29 -0
  208. package/dist/components/forms/__tests__/composite-registration.fixture.svelte.d.ts +8 -0
  209. package/dist/components/forms/__tests__/composite-registration.fixture.svelte.d.ts.map +1 -0
  210. package/dist/components/forms/__tests__/control-interaction.test.js +19 -1
  211. package/dist/components/forms/__tests__/control-proposals.test.js +63 -0
  212. package/dist/components/forms/__tests__/dictation-recording.test.js +582 -0
  213. package/dist/components/forms/__tests__/dictation.test.js +486 -0
  214. package/dist/components/forms/__tests__/form-attachment.fixture.svelte +28 -0
  215. package/dist/components/forms/__tests__/form-attachment.fixture.svelte.d.ts +11 -0
  216. package/dist/components/forms/__tests__/form-attachment.fixture.svelte.d.ts.map +1 -0
  217. package/dist/components/forms/__tests__/form-retry-form.fixture.svelte +31 -0
  218. package/dist/components/forms/__tests__/form-retry-form.fixture.svelte.d.ts +13 -0
  219. package/dist/components/forms/__tests__/form-retry-form.fixture.svelte.d.ts.map +1 -0
  220. package/dist/components/forms/__tests__/form-retry-form.test.js +107 -0
  221. package/dist/components/forms/__tests__/hidden-controls-form-retry.test.js +69 -0
  222. package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte +29 -0
  223. package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte.d.ts +9 -0
  224. package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte.d.ts.map +1 -0
  225. package/dist/components/forms/__tests__/listbox-form.fixture.svelte +27 -0
  226. package/dist/components/forms/__tests__/listbox-form.fixture.svelte.d.ts +13 -0
  227. package/dist/components/forms/__tests__/listbox-form.fixture.svelte.d.ts.map +1 -0
  228. package/dist/components/forms/__tests__/long-press.test.js +175 -0
  229. package/dist/components/forms/__tests__/multiselect-form.fixture.svelte +27 -0
  230. package/dist/components/forms/__tests__/multiselect-form.fixture.svelte.d.ts +13 -0
  231. package/dist/components/forms/__tests__/multiselect-form.fixture.svelte.d.ts.map +1 -0
  232. package/dist/components/forms/__tests__/native-file-field.test.js +177 -0
  233. package/dist/components/forms/__tests__/place-picker.fixture.svelte +33 -0
  234. package/dist/components/forms/__tests__/place-picker.fixture.svelte.d.ts +12 -0
  235. package/dist/components/forms/__tests__/place-picker.fixture.svelte.d.ts.map +1 -0
  236. package/dist/components/forms/__tests__/segmented-posting.fixture.svelte +28 -0
  237. package/dist/components/forms/__tests__/segmented-posting.fixture.svelte.d.ts +12 -0
  238. package/dist/components/forms/__tests__/segmented-posting.fixture.svelte.d.ts.map +1 -0
  239. package/dist/components/forms/__tests__/segmented-posting.test.js +92 -0
  240. package/dist/components/forms/__tests__/signature-pad-logic.test.js +54 -0
  241. package/dist/components/forms/__tests__/touch-density.fixture.svelte +34 -0
  242. package/dist/components/forms/__tests__/touch-density.fixture.svelte.d.ts +8 -0
  243. package/dist/components/forms/__tests__/touch-density.fixture.svelte.d.ts.map +1 -0
  244. package/dist/components/forms/__tests__/touch-density.test.js +66 -0
  245. package/dist/components/forms/audio-capture.d.ts +69 -0
  246. package/dist/components/forms/audio-capture.d.ts.map +1 -0
  247. package/dist/components/forms/audio-capture.js +193 -0
  248. package/dist/components/forms/camera-capture-session.d.ts +81 -0
  249. package/dist/components/forms/camera-capture-session.d.ts.map +1 -0
  250. package/dist/components/forms/camera-capture-session.js +130 -0
  251. package/dist/components/forms/control-interaction.d.ts +6 -0
  252. package/dist/components/forms/control-interaction.d.ts.map +1 -1
  253. package/dist/components/forms/control-interaction.js +12 -1
  254. package/dist/components/forms/control-proposals.d.ts +55 -0
  255. package/dist/components/forms/control-proposals.d.ts.map +1 -0
  256. package/dist/components/forms/control-proposals.js +188 -0
  257. package/dist/components/forms/dictation-transcribe.d.ts +36 -0
  258. package/dist/components/forms/dictation-transcribe.d.ts.map +1 -0
  259. package/dist/components/forms/dictation-transcribe.js +65 -0
  260. package/dist/components/forms/dictation.svelte.d.ts +200 -0
  261. package/dist/components/forms/dictation.svelte.d.ts.map +1 -0
  262. package/dist/components/forms/dictation.svelte.js +686 -0
  263. package/dist/components/forms/form-retry/__tests__/boundaries.test.js +23 -0
  264. package/dist/components/forms/form-retry/__tests__/content-digest.test.js +70 -0
  265. package/dist/components/forms/form-retry/__tests__/controller.test.js +802 -0
  266. package/dist/components/forms/form-retry/__tests__/fake-kit.d.ts +82 -0
  267. package/dist/components/forms/form-retry/__tests__/fake-kit.d.ts.map +1 -0
  268. package/dist/components/forms/form-retry/__tests__/fake-kit.js +180 -0
  269. package/dist/components/forms/form-retry/__tests__/form-draft.test.js +169 -0
  270. package/dist/components/forms/form-retry/__tests__/ssr.test.js +37 -0
  271. package/dist/components/forms/form-retry/__tests__/storage.d.ts +26 -0
  272. package/dist/components/forms/form-retry/__tests__/storage.d.ts.map +1 -0
  273. package/dist/components/forms/form-retry/__tests__/storage.js +63 -0
  274. package/dist/components/forms/form-retry/__tests__/submission-key.test.js +236 -0
  275. package/dist/components/forms/form-retry/__tests__/submitted-draft.test.js +334 -0
  276. package/dist/components/forms/form-retry/content-digest.d.ts +54 -0
  277. package/dist/components/forms/form-retry/content-digest.d.ts.map +1 -0
  278. package/dist/components/forms/form-retry/content-digest.js +169 -0
  279. package/dist/components/forms/form-retry/controller.d.ts +214 -0
  280. package/dist/components/forms/form-retry/controller.d.ts.map +1 -0
  281. package/dist/components/forms/form-retry/controller.js +404 -0
  282. package/dist/components/forms/form-retry/form-draft.d.ts +68 -0
  283. package/dist/components/forms/form-retry/form-draft.d.ts.map +1 -0
  284. package/dist/components/forms/form-retry/form-draft.js +161 -0
  285. package/dist/components/forms/form-retry/index.d.ts +19 -0
  286. package/dist/components/forms/form-retry/index.d.ts.map +1 -0
  287. package/dist/components/forms/form-retry/index.js +16 -0
  288. package/dist/components/forms/form-retry/submission-key.d.ts +244 -0
  289. package/dist/components/forms/form-retry/submission-key.d.ts.map +1 -0
  290. package/dist/components/forms/form-retry/submission-key.js +342 -0
  291. package/dist/components/forms/form-retry/submitted-draft.d.ts +112 -0
  292. package/dist/components/forms/form-retry/submitted-draft.d.ts.map +1 -0
  293. package/dist/components/forms/form-retry/submitted-draft.js +270 -0
  294. package/dist/components/forms/form-retry/types.d.ts +84 -0
  295. package/dist/components/forms/form-retry/types.d.ts.map +1 -0
  296. package/dist/components/forms/form-retry/types.js +13 -0
  297. package/dist/components/forms/index.d.ts +21 -2
  298. package/dist/components/forms/index.d.ts.map +1 -1
  299. package/dist/components/forms/index.js +22 -1
  300. package/dist/components/forms/insert-text.d.ts +12 -0
  301. package/dist/components/forms/insert-text.d.ts.map +1 -0
  302. package/dist/components/forms/insert-text.js +38 -0
  303. package/dist/components/forms/long-press.d.ts +88 -0
  304. package/dist/components/forms/long-press.d.ts.map +1 -0
  305. package/dist/components/forms/long-press.js +213 -0
  306. package/dist/components/forms/native-file-field.d.ts +94 -0
  307. package/dist/components/forms/native-file-field.d.ts.map +1 -0
  308. package/dist/components/forms/native-file-field.js +147 -0
  309. package/dist/components/forms/ready-beep.d.ts +24 -0
  310. package/dist/components/forms/ready-beep.d.ts.map +1 -0
  311. package/dist/components/forms/ready-beep.js +82 -0
  312. package/dist/components/forms/signature-pad-logic.d.ts +50 -0
  313. package/dist/components/forms/signature-pad-logic.d.ts.map +1 -0
  314. package/dist/components/forms/signature-pad-logic.js +48 -0
  315. package/dist/components/forms/types.d.ts +41 -0
  316. package/dist/components/forms/types.d.ts.map +1 -1
  317. package/dist/components/layout/PageHeader.svelte +151 -17
  318. package/dist/components/layout/PageHeader.svelte.d.ts +47 -7
  319. package/dist/components/layout/PageHeader.svelte.d.ts.map +1 -1
  320. package/dist/components/layout/__tests__/PageHeader.test.js +87 -1
  321. package/dist/components/layout/index.d.ts +1 -0
  322. package/dist/components/layout/index.d.ts.map +1 -1
  323. package/dist/components/layout/index.js +1 -0
  324. package/dist/components/layout/page-header-context.d.ts +25 -0
  325. package/dist/components/layout/page-header-context.d.ts.map +1 -0
  326. package/dist/components/layout/page-header-context.js +16 -0
  327. package/dist/components/nav/FilterChips.svelte +8 -1
  328. package/dist/components/nav/FilterChips.svelte.d.ts +2 -0
  329. package/dist/components/nav/FilterChips.svelte.d.ts.map +1 -1
  330. package/dist/components/nav/Tabs.svelte +241 -0
  331. package/dist/components/nav/Tabs.svelte.d.ts +15 -0
  332. package/dist/components/nav/Tabs.svelte.d.ts.map +1 -1
  333. package/dist/components/nav/__tests__/LinkTabs.test.js +117 -0
  334. package/dist/components/nav/index.d.ts +1 -0
  335. package/dist/components/nav/index.d.ts.map +1 -1
  336. package/dist/components/nav/index.js +1 -0
  337. package/dist/components/nav/tabs-overflow.d.ts +12 -0
  338. package/dist/components/nav/tabs-overflow.d.ts.map +1 -0
  339. package/dist/components/nav/tabs-overflow.js +17 -0
  340. package/dist/components/nav/types.d.ts +8 -0
  341. package/dist/components/nav/types.d.ts.map +1 -1
  342. package/dist/components/ui/Button.svelte +16 -2
  343. package/dist/components/ui/Button.svelte.d.ts +2 -0
  344. package/dist/components/ui/Button.svelte.d.ts.map +1 -1
  345. package/dist/components/ui/Card.svelte +14 -5
  346. package/dist/components/ui/Dropdown.svelte +55 -1
  347. package/dist/components/ui/Dropdown.svelte.d.ts +8 -0
  348. package/dist/components/ui/Dropdown.svelte.d.ts.map +1 -1
  349. package/dist/components/ui/IconToggle.svelte +139 -0
  350. package/dist/components/ui/IconToggle.svelte.d.ts +32 -0
  351. package/dist/components/ui/IconToggle.svelte.d.ts.map +1 -0
  352. package/dist/components/ui/IconToggleGroup.svelte +56 -0
  353. package/dist/components/ui/IconToggleGroup.svelte.d.ts +21 -0
  354. package/dist/components/ui/IconToggleGroup.svelte.d.ts.map +1 -0
  355. package/dist/components/ui/Pagination.svelte +1 -1
  356. package/dist/components/ui/Popover.svelte +17 -1
  357. package/dist/components/ui/Popover.svelte.d.ts +8 -0
  358. package/dist/components/ui/Popover.svelte.d.ts.map +1 -1
  359. package/dist/components/ui/__tests__/IconToggle.test.js +59 -0
  360. package/dist/components/ui/__tests__/IconToggleGroup.test.js +30 -0
  361. package/dist/components/ui/__tests__/Popover.test.js +44 -0
  362. package/dist/components/ui/__tests__/dropdown-popover-icon.test.js +125 -0
  363. package/dist/components/ui/__tests__/phone-touch-targets.test.js +39 -0
  364. package/dist/components/ui/index.d.ts +2 -0
  365. package/dist/components/ui/index.d.ts.map +1 -1
  366. package/dist/components/ui/index.js +2 -0
  367. package/dist/i18n/strings.ui.d.ts +65 -0
  368. package/dist/i18n/strings.ui.d.ts.map +1 -1
  369. package/dist/i18n/strings.ui.js +72 -0
  370. package/dist/styles/tokens.css +2 -0
  371. package/dist/svelte/__tests__/playground.test.js +3 -1
  372. package/dist/svelte/playground/CalendarPreview.svelte +140 -0
  373. package/dist/svelte/playground/CalendarPreview.svelte.d.ts +4 -0
  374. package/dist/svelte/playground/CalendarPreview.svelte.d.ts.map +1 -0
  375. package/dist/svelte/playground/CapturePreview.svelte +56 -0
  376. package/dist/svelte/playground/CapturePreview.svelte.d.ts +4 -0
  377. package/dist/svelte/playground/CapturePreview.svelte.d.ts.map +1 -0
  378. package/dist/svelte/playground.d.ts.map +1 -1
  379. package/dist/svelte/playground.js +20 -0
  380. package/dist/themes/ThemeProvider.svelte +9 -0
  381. package/dist/themes/ThemeProvider.svelte.d.ts +2 -0
  382. package/dist/themes/ThemeProvider.svelte.d.ts.map +1 -1
  383. package/dist/themes/css-generator.d.ts.map +1 -1
  384. package/dist/themes/css-generator.js +3 -0
  385. package/dist/themes/styles/glass.css +4 -0
  386. package/dist/themes/styles/happyvertical.css +4 -0
  387. package/dist/themes/styles/material.css +4 -0
  388. package/dist/themes/styles/smrt.css +4 -0
  389. package/dist/themes/styles/studio.css +4 -0
  390. package/dist/types-generic.d.ts +2 -0
  391. package/dist/types-generic.d.ts.map +1 -1
  392. package/package.json +9 -3
@@ -0,0 +1,82 @@
1
+ /**
2
+ * A short "ready" beep, made with the Web Audio API (no sound files).
3
+ *
4
+ * Browsers only start audio after a user gesture. A long press fires from a
5
+ * timer while the finger is still down, which on touch screens is not a
6
+ * gesture yet, so `playReadyBeep` resolves `false` when the audio could not
7
+ * start; the host plays it again from the release (`pointerup` is a gesture).
8
+ * `primeReadyBeep` creates the audio context early, from a gesture, so later
9
+ * beeps start immediately.
10
+ */
11
+ let context = null;
12
+ function audioContextCtor() {
13
+ if (typeof window === 'undefined')
14
+ return null;
15
+ const w = window;
16
+ return (w.AudioContext ??
17
+ w.webkitAudioContext ??
18
+ null);
19
+ }
20
+ function getContext() {
21
+ if (context)
22
+ return context;
23
+ const Ctor = audioContextCtor();
24
+ if (!Ctor)
25
+ return null;
26
+ try {
27
+ context = new Ctor();
28
+ }
29
+ catch {
30
+ context = null;
31
+ }
32
+ return context;
33
+ }
34
+ /** Create (or wake) the audio context; call from a user gesture. */
35
+ export function primeReadyBeep() {
36
+ const ctx = getContext();
37
+ if (ctx && ctx.state === 'suspended') {
38
+ void ctx.resume().catch(() => undefined);
39
+ }
40
+ }
41
+ /**
42
+ * Play two quick rising tones (about 0.2s). Resolves `true` when it played,
43
+ * `false` when audio is unavailable or not allowed yet.
44
+ */
45
+ export async function playReadyBeep(options = {}) {
46
+ const ctx = getContext();
47
+ if (!ctx)
48
+ return false;
49
+ try {
50
+ if (ctx.state === 'suspended')
51
+ await ctx.resume();
52
+ }
53
+ catch {
54
+ return false;
55
+ }
56
+ if (ctx.state !== 'running')
57
+ return false;
58
+ const volume = Math.max(0, Math.min(1, options.volume ?? 0.12));
59
+ const start = ctx.currentTime + 0.01;
60
+ const tones = [
61
+ [660, 0],
62
+ [990, 0.1],
63
+ ];
64
+ for (const [frequency, at] of tones) {
65
+ const oscillator = ctx.createOscillator();
66
+ const gain = ctx.createGain();
67
+ oscillator.type = 'sine';
68
+ oscillator.frequency.value = frequency;
69
+ gain.gain.setValueAtTime(0, start + at);
70
+ gain.gain.linearRampToValueAtTime(volume, start + at + 0.01);
71
+ gain.gain.exponentialRampToValueAtTime(0.0001, start + at + 0.09);
72
+ oscillator.connect(gain);
73
+ gain.connect(ctx.destination);
74
+ oscillator.start(start + at);
75
+ oscillator.stop(start + at + 0.1);
76
+ }
77
+ return true;
78
+ }
79
+ /** Tests: forget the shared audio context. */
80
+ export function resetReadyBeepForTests() {
81
+ context = null;
82
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Pure input logic for `SignaturePad.svelte`: the pointer-type gate and the
3
+ * pointer-to-canvas coordinate mapping. Free of DOM and settings dependencies
4
+ * so both are testable without a browser. The component takes `stylusOnly` as a
5
+ * prop the caller resolves (from a tenant setting, a device profile, …) and
6
+ * never decides it itself.
7
+ *
8
+ * Ported from teamworks-os `signature-pad-logic.ts`, smrt#3290.
9
+ */
10
+ /** `PointerEvent.pointerType`; browsers may report values beyond the three known ones. */
11
+ export type SignaturePointerType = 'pen' | 'touch' | 'mouse' | (string & {});
12
+ /**
13
+ * Paper and ink colours for the signature bitmap. A signature is document
14
+ * content, not chrome: it stays dark ink on white paper in every theme and
15
+ * colour scheme, so the exported PNG reads the same wherever it is shown.
16
+ */
17
+ export declare const SIGNATURE_PAPER_COLOR = "#ffffff";
18
+ export declare const SIGNATURE_INK_COLOR = "#111111";
19
+ /**
20
+ * Whether a pointer of `pointerType` may draw a stroke.
21
+ *
22
+ * With `stylusOnly`, only `pen` is accepted: finger and mouse input are
23
+ * ignored outright, since nothing at the pointer-event level can tell a
24
+ * gloved finger from a stylus. Otherwise pen, touch and mouse are accepted.
25
+ * Unknown pointer types are rejected in both modes.
26
+ */
27
+ export declare function isAcceptedPointerType(pointerType: SignaturePointerType, stylusOnly: boolean): boolean;
28
+ /** The subset of `DOMRect` the coordinate mapping needs. */
29
+ export interface CanvasDisplayRect {
30
+ left: number;
31
+ top: number;
32
+ width: number;
33
+ height: number;
34
+ }
35
+ /**
36
+ * Maps viewport `clientX`/`clientY` (CSS pixels) to the canvas's backing-pixel
37
+ * space.
38
+ *
39
+ * The canvas has a fixed backing resolution but is displayed at `width: 100%`,
40
+ * so the raw `clientX - rect.left` offset is only right when the displayed size
41
+ * happens to equal the backing size; anywhere else the ink drifts from the
42
+ * stylus and clips near the edges. Scaling by `backing / displayed` keeps them
43
+ * aligned. A zero-size rect (not laid out yet) falls back to 1:1 instead of
44
+ * dividing by zero.
45
+ */
46
+ export declare function mapPointerToCanvasPoint(clientX: number, clientY: number, rect: CanvasDisplayRect, canvasWidth: number, canvasHeight: number): {
47
+ x: number;
48
+ y: number;
49
+ };
50
+ //# sourceMappingURL=signature-pad-logic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signature-pad-logic.d.ts","sourceRoot":"","sources":["../../../src/components/forms/signature-pad-logic.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,0FAA0F;AAC1F,MAAM,MAAM,oBAAoB,GAAG,KAAK,GAAG,OAAO,GAAG,OAAO,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAE7E;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,YAAY,CAAC;AAC/C,eAAO,MAAM,mBAAmB,YAAY,CAAC;AAE7C;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,oBAAoB,EACjC,UAAU,EAAE,OAAO,GAClB,OAAO,CAKT;AAED,4DAA4D;AAC5D,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,iBAAiB,EACvB,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,GACnB;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAO1B"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Pure input logic for `SignaturePad.svelte`: the pointer-type gate and the
3
+ * pointer-to-canvas coordinate mapping. Free of DOM and settings dependencies
4
+ * so both are testable without a browser. The component takes `stylusOnly` as a
5
+ * prop the caller resolves (from a tenant setting, a device profile, …) and
6
+ * never decides it itself.
7
+ *
8
+ * Ported from teamworks-os `signature-pad-logic.ts`, smrt#3290.
9
+ */
10
+ /**
11
+ * Paper and ink colours for the signature bitmap. A signature is document
12
+ * content, not chrome: it stays dark ink on white paper in every theme and
13
+ * colour scheme, so the exported PNG reads the same wherever it is shown.
14
+ */
15
+ export const SIGNATURE_PAPER_COLOR = '#ffffff';
16
+ export const SIGNATURE_INK_COLOR = '#111111';
17
+ /**
18
+ * Whether a pointer of `pointerType` may draw a stroke.
19
+ *
20
+ * With `stylusOnly`, only `pen` is accepted: finger and mouse input are
21
+ * ignored outright, since nothing at the pointer-event level can tell a
22
+ * gloved finger from a stylus. Otherwise pen, touch and mouse are accepted.
23
+ * Unknown pointer types are rejected in both modes.
24
+ */
25
+ export function isAcceptedPointerType(pointerType, stylusOnly) {
26
+ if (stylusOnly)
27
+ return pointerType === 'pen';
28
+ return (pointerType === 'pen' || pointerType === 'touch' || pointerType === 'mouse');
29
+ }
30
+ /**
31
+ * Maps viewport `clientX`/`clientY` (CSS pixels) to the canvas's backing-pixel
32
+ * space.
33
+ *
34
+ * The canvas has a fixed backing resolution but is displayed at `width: 100%`,
35
+ * so the raw `clientX - rect.left` offset is only right when the displayed size
36
+ * happens to equal the backing size; anywhere else the ink drifts from the
37
+ * stylus and clips near the edges. Scaling by `backing / displayed` keeps them
38
+ * aligned. A zero-size rect (not laid out yet) falls back to 1:1 instead of
39
+ * dividing by zero.
40
+ */
41
+ export function mapPointerToCanvasPoint(clientX, clientY, rect, canvasWidth, canvasHeight) {
42
+ const scaleX = rect.width > 0 ? canvasWidth / rect.width : 1;
43
+ const scaleY = rect.height > 0 ? canvasHeight / rect.height : 1;
44
+ return {
45
+ x: (clientX - rect.left) * scaleX,
46
+ y: (clientY - rect.top) * scaleY,
47
+ };
48
+ }
@@ -11,4 +11,45 @@ export interface RangeSliderValue {
11
11
  export interface SegmentedControlOption extends ControlOption {
12
12
  icon?: string;
13
13
  }
14
+ /** What `CameraCapture` and `SignaturePad` hand to `onCapture`. */
15
+ export interface CaptureResult {
16
+ /** The encoded image (JPEG/PNG/WebP for photos, PNG for signatures). */
17
+ blob: Blob;
18
+ /** The same image as a `data:` URL, convenient for an inline preview. */
19
+ dataUrl: string;
20
+ }
21
+ /** Rendered states of `CameraCapture`, exposed as its `data-state` attribute. */
22
+ export type CameraCaptureState = 'off' | 'starting' | 'streaming' | 'reviewing' | 'committed' | 'permission-denied' | 'no-camera' | 'unsupported' | 'error' | 'fallback';
23
+ /** Text overrides for `CameraCapture`; defaults come from the `ui` i18n catalog. */
24
+ export interface CameraCaptureLabels {
25
+ region: string;
26
+ preview: string;
27
+ starting: string;
28
+ off: string;
29
+ unsupported: string;
30
+ permissionDenied: string;
31
+ noCamera: string;
32
+ error: string;
33
+ retry: string;
34
+ capture: string;
35
+ retake: string;
36
+ usePhoto: string;
37
+ reviewAlt: string;
38
+ committedAlt: string;
39
+ committed: string;
40
+ choosePhoto: string;
41
+ }
42
+ /** Rendered states of `SignaturePad`, exposed as its `data-state` attribute. */
43
+ export type SignaturePadState = 'empty' | 'signed' | 'committed';
44
+ /** Text overrides for `SignaturePad`; defaults come from the `ui` i18n catalog. */
45
+ export interface SignaturePadLabels {
46
+ region: string;
47
+ canvasEmpty: string;
48
+ canvasSigned: string;
49
+ hintAny: string;
50
+ hintStylus: string;
51
+ clear: string;
52
+ useSignature: string;
53
+ committed: string;
54
+ }
14
55
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/forms/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,sBAAuB,SAAQ,aAAa;IAC3D,IAAI,CAAC,EAAE,MAAM,CAAC;CACf"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/forms/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,sBAAuB,SAAQ,aAAa;IAC3D,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,mEAAmE;AACnE,MAAM,WAAW,aAAa;IAC5B,wEAAwE;IACxE,IAAI,EAAE,IAAI,CAAC;IACX,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,iFAAiF;AACjF,MAAM,MAAM,kBAAkB,GAC1B,KAAK,GACL,UAAU,GACV,WAAW,GACX,WAAW,GACX,WAAW,GACX,mBAAmB,GACnB,WAAW,GACX,aAAa,GACb,OAAO,GACP,UAAU,CAAC;AAEf,oFAAoF;AACpF,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,gFAAgF;AAChF,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,QAAQ,GAAG,WAAW,CAAC;AAEjE,mFAAmF;AACnF,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB"}
@@ -1,41 +1,107 @@
1
1
  <script lang="ts">
2
2
  /**
3
- * PageHeader - Standard page header with optional back navigation
4
- * refactored for Material 3
3
+ * PageHeader - one header per page: an ancestors-only breadcrumb row, the
4
+ * page title (`<h1>`), and the page's own actions, right-aligned.
5
5
  *
6
- * Provides consistent page header layout with title, optional back link,
7
- * and slot for action buttons.
6
+ * Page-header contract (shared with AdminShell in smrt-svelte/workspace):
7
+ * - `crumbs` are the page's ancestors only, never the page itself; none on
8
+ * section homes. Inside a shell that sets `setPageHeaderContext()`, pages
9
+ * pass only `parents` (ancestors the nav doesn't know, e.g. the article
10
+ * above its video) and the shell adds the rest; the shell also hears the
11
+ * title (for its phone top bar). smrt-svelte's `shellPageTrailFor()`
12
+ * computes such a trail from a nav.
13
+ * - The crumb row carries `data-shell-breadcrumbs` and the title
14
+ * `data-shell-page-title`: on phones AdminShell hides the crumbs and
15
+ * visually hides the title, because its phone top bar shows the back arrow
16
+ * and the title instead.
17
+ * - No in-page back link: the crumbs (and the phone top bar) are the way
18
+ * back. `backHref` is kept only for pages outside such a shell.
19
+ * - An editable title (`titleField`, e.g. an article's headline) renders
20
+ * inside the `<h1>`, so the heading is still the page's title in the
21
+ * outline and is named by the field's value, while the field keeps its own
22
+ * label. Such a heading has no `data-shell-page-title`: it stays visible on
23
+ * phones, because it is the input. Pass the live value as `title` so the
24
+ * shell (the phone top bar) follows along as the person types.
8
25
  */
9
26
  import type { Snippet } from 'svelte';
10
27
  import { ripple } from '../../actions/ripple.js';
11
28
  import { Icon } from '../display/index.js';
29
+ import {
30
+ getPageHeaderContext,
31
+ type PageHeaderCrumb,
32
+ } from './page-header-context.js';
12
33
 
13
34
  export interface Props {
14
35
  /** Page title */
15
36
  title: string;
16
37
  /** Optional subtitle/description */
17
38
  subtitle?: string;
18
- /** Back navigation URL */
39
+ /**
40
+ * All ancestors of this page, outermost first (never the page itself).
41
+ * Overrides the shell's trail; usually pass `parents` instead.
42
+ */
43
+ crumbs?: readonly PageHeaderCrumb[];
44
+ /** Ancestors past the shell's nav, outermost first. */
45
+ parents?: readonly PageHeaderCrumb[];
46
+ /** Shorter title for the shell (e.g. its phone top bar); default `title`. */
47
+ shortTitle?: string;
48
+ /** Accessible name of the breadcrumb nav. */
49
+ crumbsLabel?: string;
50
+ /**
51
+ * Back navigation URL.
52
+ * @deprecated Use `crumbs`; inside AdminShell the phone top bar goes back.
53
+ */
19
54
  backHref?: string;
20
55
  /** Back link label */
21
56
  backLabel?: string;
22
- /** Slot for action buttons */
57
+ /**
58
+ * An editable title rendered inside the `<h1>` in place of `title`'s text
59
+ * (a labelled text field styled as the heading). `title` still names the
60
+ * page for the shell; keep it in sync with the field's value.
61
+ */
62
+ titleField?: Snippet;
63
+ /** Slot for action buttons (right-aligned beside the title) */
23
64
  actions?: Snippet;
24
- /** Slot for additional content below title */
65
+ /** Small line under the title (status, dates, counts) */
66
+ meta?: Snippet;
67
+ /** Slot for additional content below the title row (e.g. page tabs) */
25
68
  children?: Snippet;
26
69
  }
27
70
 
28
71
  const {
29
72
  title,
30
73
  subtitle,
74
+ crumbs,
75
+ parents = [],
76
+ shortTitle,
77
+ crumbsLabel = 'Breadcrumb',
31
78
  backHref,
32
79
  backLabel = 'Back',
80
+ titleField,
33
81
  actions,
82
+ meta,
34
83
  children,
35
84
  }: Props = $props();
85
+
86
+ const shell = getPageHeaderContext();
87
+ const trail = $derived<readonly PageHeaderCrumb[]>(
88
+ crumbs ?? (shell ? shell.crumbs(parents) : parents),
89
+ );
90
+
91
+ $effect(() => shell?.report?.({ title: shortTitle ?? title, parents }));
36
92
  </script>
37
93
 
38
- <header class="page-header">
94
+ <header class="page-header" data-page-header>
95
+ {#if trail.length > 0}
96
+ <nav class="page-crumbs" aria-label={crumbsLabel} data-shell-breadcrumbs>
97
+ <ol>
98
+ {#each trail as crumb, index (`${index}:${crumb.href}`)}
99
+ <li><a href={crumb.href}>{crumb.label}</a></li>
100
+ {/each}
101
+ </ol>
102
+ </nav>
103
+ {/if}
104
+
39
105
  <div class="header-main">
40
106
  <div class="header-content">
41
107
  {#if backHref}
@@ -44,10 +110,19 @@ const {
44
110
  <span>{backLabel}</span>
45
111
  </a>
46
112
  {/if}
47
- <h1 class="page-title">{title}</h1>
113
+ {#if titleField}
114
+ <h1 class="page-title page-title-field" data-page-title-field>
115
+ {@render titleField()}
116
+ </h1>
117
+ {:else}
118
+ <h1 class="page-title" data-shell-page-title>{title}</h1>
119
+ {/if}
48
120
  {#if subtitle}
49
121
  <p class="page-subtitle">{subtitle}</p>
50
122
  {/if}
123
+ {#if meta}
124
+ <div class="page-meta">{@render meta()}</div>
125
+ {/if}
51
126
  </div>
52
127
 
53
128
  {#if actions}
@@ -58,7 +133,7 @@ const {
58
133
  </div>
59
134
 
60
135
  {#if children}
61
- <div class="header-extra">
136
+ <div class="header-extra" data-page-header-extra>
62
137
  {@render children()}
63
138
  </div>
64
139
  {/if}
@@ -66,15 +141,60 @@ const {
66
141
 
67
142
  <style>
68
143
  .page-header {
69
- margin-bottom: 2rem;
70
- padding-top: 1rem;
144
+ margin-bottom: 1.5rem;
145
+ }
146
+
147
+ .page-crumbs {
148
+ margin-bottom: var(--smrt-spacing-1, 4px);
149
+ color: var(--smrt-color-on-surface-variant);
150
+ font: var(--smrt-typography-body-small-font);
151
+ font-size: 0.84rem;
152
+ }
153
+
154
+ .page-crumbs ol {
155
+ display: flex;
156
+ flex-wrap: wrap;
157
+ align-items: center;
158
+ gap: 0.35rem;
159
+ margin: 0;
160
+ padding: 0;
161
+ list-style: none;
162
+ }
163
+
164
+ .page-crumbs li {
165
+ display: flex;
166
+ align-items: center;
167
+ min-width: 0;
168
+ }
169
+
170
+ .page-crumbs li + li::before {
171
+ content: '/';
172
+ margin-inline-end: 0.35rem;
173
+ color: var(--smrt-color-outline);
174
+ }
175
+
176
+ .page-crumbs a {
177
+ color: inherit;
178
+ text-decoration: none;
179
+ overflow-wrap: anywhere;
180
+ }
181
+
182
+ .page-crumbs a:hover {
183
+ color: var(--smrt-color-primary);
184
+ text-decoration: underline;
185
+ }
186
+
187
+ .page-crumbs a:focus-visible {
188
+ outline: 2px solid var(--smrt-color-primary);
189
+ outline-offset: 2px;
190
+ border-radius: var(--smrt-radius-sm, 4px);
71
191
  }
72
192
 
73
193
  .header-main {
74
194
  display: flex;
75
195
  justify-content: space-between;
76
196
  align-items: flex-start;
77
- gap: 1.5rem;
197
+ gap: 0.75rem 1.5rem;
78
198
  flex-wrap: wrap;
79
199
  }
80
200
 
@@ -83,7 +203,7 @@ const {
83
203
  flex-direction: column;
84
204
  gap: var(--smrt-spacing-1, 4px);
85
205
  flex: 1;
86
- min-width: 200px;
206
+ min-width: min(100%, 200px);
87
207
  }
88
208
 
89
209
  .back-link {
@@ -109,13 +229,26 @@ const {
109
229
  color: var(--smrt-color-on-surface);
110
230
  margin: 0;
111
231
  letter-spacing: -0.5px;
232
+ overflow-wrap: anywhere;
233
+ }
234
+
235
+ .page-title-field {
236
+ min-width: 0;
112
237
  }
113
238
 
114
239
  .page-subtitle {
115
240
  font: var(--smrt-typography-body-medium-font);
116
241
  color: var(--smrt-color-on-surface-variant);
117
242
  margin: 0;
118
- margin-top: var(--smrt-spacing-1, 4px);
243
+ }
244
+
245
+ .page-meta {
246
+ display: flex;
247
+ flex-wrap: wrap;
248
+ align-items: center;
249
+ gap: 0.5rem;
250
+ font: var(--smrt-typography-body-small-font);
251
+ color: var(--smrt-color-on-surface-variant);
119
252
  }
120
253
 
121
254
  .header-actions {
@@ -123,9 +256,10 @@ const {
123
256
  gap: 0.75rem;
124
257
  align-items: center;
125
258
  flex-wrap: wrap;
259
+ margin-inline-start: auto;
126
260
  }
127
261
 
128
262
  .header-extra {
129
- margin-top: 1.5rem;
263
+ margin-top: 1rem;
130
264
  }
131
- </style>
265
+ </style>
@@ -1,23 +1,63 @@
1
1
  /**
2
- * PageHeader - Standard page header with optional back navigation
3
- * refactored for Material 3
2
+ * PageHeader - one header per page: an ancestors-only breadcrumb row, the
3
+ * page title (`<h1>`), and the page's own actions, right-aligned.
4
4
  *
5
- * Provides consistent page header layout with title, optional back link,
6
- * and slot for action buttons.
5
+ * Page-header contract (shared with AdminShell in smrt-svelte/workspace):
6
+ * - `crumbs` are the page's ancestors only, never the page itself; none on
7
+ * section homes. Inside a shell that sets `setPageHeaderContext()`, pages
8
+ * pass only `parents` (ancestors the nav doesn't know, e.g. the article
9
+ * above its video) and the shell adds the rest; the shell also hears the
10
+ * title (for its phone top bar). smrt-svelte's `shellPageTrailFor()`
11
+ * computes such a trail from a nav.
12
+ * - The crumb row carries `data-shell-breadcrumbs` and the title
13
+ * `data-shell-page-title`: on phones AdminShell hides the crumbs and
14
+ * visually hides the title, because its phone top bar shows the back arrow
15
+ * and the title instead.
16
+ * - No in-page back link: the crumbs (and the phone top bar) are the way
17
+ * back. `backHref` is kept only for pages outside such a shell.
18
+ * - An editable title (`titleField`, e.g. an article's headline) renders
19
+ * inside the `<h1>`, so the heading is still the page's title in the
20
+ * outline and is named by the field's value, while the field keeps its own
21
+ * label. Such a heading has no `data-shell-page-title`: it stays visible on
22
+ * phones, because it is the input. Pass the live value as `title` so the
23
+ * shell (the phone top bar) follows along as the person types.
7
24
  */
8
25
  import type { Snippet } from 'svelte';
26
+ import { type PageHeaderCrumb } from './page-header-context.js';
9
27
  export interface Props {
10
28
  /** Page title */
11
29
  title: string;
12
30
  /** Optional subtitle/description */
13
31
  subtitle?: string;
14
- /** Back navigation URL */
32
+ /**
33
+ * All ancestors of this page, outermost first (never the page itself).
34
+ * Overrides the shell's trail; usually pass `parents` instead.
35
+ */
36
+ crumbs?: readonly PageHeaderCrumb[];
37
+ /** Ancestors past the shell's nav, outermost first. */
38
+ parents?: readonly PageHeaderCrumb[];
39
+ /** Shorter title for the shell (e.g. its phone top bar); default `title`. */
40
+ shortTitle?: string;
41
+ /** Accessible name of the breadcrumb nav. */
42
+ crumbsLabel?: string;
43
+ /**
44
+ * Back navigation URL.
45
+ * @deprecated Use `crumbs`; inside AdminShell the phone top bar goes back.
46
+ */
15
47
  backHref?: string;
16
48
  /** Back link label */
17
49
  backLabel?: string;
18
- /** Slot for action buttons */
50
+ /**
51
+ * An editable title rendered inside the `<h1>` in place of `title`'s text
52
+ * (a labelled text field styled as the heading). `title` still names the
53
+ * page for the shell; keep it in sync with the field's value.
54
+ */
55
+ titleField?: Snippet;
56
+ /** Slot for action buttons (right-aligned beside the title) */
19
57
  actions?: Snippet;
20
- /** Slot for additional content below title */
58
+ /** Small line under the title (status, dates, counts) */
59
+ meta?: Snippet;
60
+ /** Slot for additional content below the title row (e.g. page tabs) */
21
61
  children?: Snippet;
22
62
  }
23
63
  declare const PageHeader: import("svelte").Component<Props, {}, "">;
@@ -1 +1 @@
1
- {"version":3,"file":"PageHeader.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/layout/PageHeader.svelte.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAKtC,MAAM,WAAW,KAAK;IACpB,iBAAiB;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0BAA0B;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sBAAsB;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8BAA8B;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAkDD,QAAA,MAAM,UAAU,2CAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"PageHeader.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/layout/PageHeader.svelte.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAGtC,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,0BAA0B,CAAC;AAGlC,MAAM,WAAW,KAAK;IACpB,iBAAiB;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,MAAM,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IACpC,uDAAuD;IACvD,OAAO,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IACrC,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6CAA6C;IAC7C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sBAAsB;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,+DAA+D;IAC/D,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,yDAAyD;IACzD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,uEAAuE;IACvE,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAoFD,QAAA,MAAM,UAAU,2CAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
@@ -5,7 +5,7 @@
5
5
  * title, optional subtitle, optional back link, and actions/children snippets.
6
6
  */
7
7
  import { render, screen } from '@testing-library/svelte';
8
- import { createRawSnippet } from 'svelte';
8
+ import { createRawSnippet, flushSync } from 'svelte';
9
9
  import { describe, expect, it } from 'vitest';
10
10
  import { expectNoA11yViolations } from '../../../test-support/a11y';
11
11
  import PageHeader from '../PageHeader.svelte';
@@ -66,6 +66,92 @@ describe('PageHeader', () => {
66
66
  render(PageHeader, { props: { title: 'Detail' } });
67
67
  expect(screen.queryByRole('link')).not.toBeInTheDocument();
68
68
  });
69
+ it('renders ancestors-only crumbs above the title', () => {
70
+ render(PageHeader, {
71
+ props: {
72
+ title: 'Fair opens Friday',
73
+ crumbs: [
74
+ { label: 'Alpha Times', href: '/sites/alpha' },
75
+ { label: 'Content', href: '/sites/alpha/articles' },
76
+ ],
77
+ },
78
+ });
79
+ const nav = screen.getByRole('navigation', { name: 'Breadcrumb' });
80
+ expect(nav).toHaveAttribute('data-shell-breadcrumbs');
81
+ const links = nav.querySelectorAll('a');
82
+ expect([...links].map((a) => a.textContent)).toEqual([
83
+ 'Alpha Times',
84
+ 'Content',
85
+ ]);
86
+ // The current page is the heading, not a crumb.
87
+ expect(nav).not.toHaveTextContent('Fair opens Friday');
88
+ expect(screen.getByRole('heading', { level: 1, name: 'Fair opens Friday' })).toHaveAttribute('data-shell-page-title');
89
+ });
90
+ it('renders no crumb row without ancestors', () => {
91
+ render(PageHeader, { props: { title: 'Content', crumbs: [] } });
92
+ expect(screen.queryByRole('navigation')).not.toBeInTheDocument();
93
+ });
94
+ it('renders the meta snippet under the title', () => {
95
+ render(PageHeader, {
96
+ props: { title: 'Detail', meta: snippet('<span>Draft</span>') },
97
+ });
98
+ expect(screen.getByText('Draft')).toBeInTheDocument();
99
+ });
100
+ it('takes its trail from the shell and reports its title', () => {
101
+ const reports = [];
102
+ const context = new Map([
103
+ [
104
+ Symbol.for('smrt-ui.page-header'),
105
+ {
106
+ crumbs: (parents) => [
107
+ { label: 'Alpha Times', href: '/sites/alpha' },
108
+ ...parents,
109
+ ],
110
+ report: (page) => {
111
+ reports.push(page);
112
+ return undefined;
113
+ },
114
+ },
115
+ ],
116
+ ]);
117
+ render(PageHeader, {
118
+ props: {
119
+ title: 'Create video',
120
+ parents: [{ label: 'Videos', href: '/sites/alpha/articles/a1/videos' }],
121
+ },
122
+ context,
123
+ });
124
+ const nav = screen.getByRole('navigation', { name: 'Breadcrumb' });
125
+ expect([...nav.querySelectorAll('a')].map((a) => a.textContent)).toEqual([
126
+ 'Alpha Times',
127
+ 'Videos',
128
+ ]);
129
+ flushSync();
130
+ expect(reports).toEqual([
131
+ {
132
+ title: 'Create video',
133
+ parents: [{ label: 'Videos', href: '/sites/alpha/articles/a1/videos' }],
134
+ },
135
+ ]);
136
+ });
137
+ it('renders an editable title inside the h1 and keeps it visible on phones', async () => {
138
+ const { container } = render(PageHeader, {
139
+ props: {
140
+ title: 'Council passes budget',
141
+ titleField: snippet('<input name="title" aria-label="Title" value="Council passes budget" />'),
142
+ },
143
+ });
144
+ const heading = screen.getByRole('heading', { level: 1 });
145
+ const field = screen.getByRole('textbox', { name: 'Title' });
146
+ expect(heading).toContainElement(field);
147
+ // The heading is named by the field's value; the field keeps its label.
148
+ expect(heading).toHaveAccessibleName('Council passes budget');
149
+ // No data-shell-page-title: the shell must not hide the input on phones.
150
+ expect(heading).not.toHaveAttribute('data-shell-page-title');
151
+ expect(heading).toHaveAttribute('data-page-title-field');
152
+ expect(container.querySelectorAll('h1')).toHaveLength(1);
153
+ await expectNoA11yViolations(container);
154
+ });
69
155
  it('is axe-clean with subtitle, back link, and actions', async () => {
70
156
  const { container } = render(PageHeader, {
71
157
  props: {