@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
@@ -8,5 +8,6 @@ export { default as Grid } from './Grid.svelte';
8
8
  export { default as Header } from './Header.svelte';
9
9
  export { default as Masthead } from './Masthead.svelte';
10
10
  export { default as PageHeader } from './PageHeader.svelte';
11
+ export { getPageHeaderContext, type PageHeaderContext, type PageHeaderCrumb, type PageHeaderReport, setPageHeaderContext, } from './page-header-context.js';
11
12
  export { default as SummaryCard } from './SummaryCard.svelte';
12
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/layout/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/layout/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EACL,oBAAoB,EACpB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,oBAAoB,GACrB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC"}
@@ -8,4 +8,5 @@ export { default as Grid } from './Grid.svelte';
8
8
  export { default as Header } from './Header.svelte';
9
9
  export { default as Masthead } from './Masthead.svelte';
10
10
  export { default as PageHeader } from './PageHeader.svelte';
11
+ export { getPageHeaderContext, setPageHeaderContext, } from './page-header-context.js';
11
12
  export { default as SummaryCard } from './SummaryCard.svelte';
@@ -0,0 +1,25 @@
1
+ /** One ancestor of a page. */
2
+ export interface PageHeaderCrumb {
3
+ label: string;
4
+ href: string;
5
+ }
6
+ /** What a page reports to its shell. */
7
+ export interface PageHeaderReport {
8
+ title: string;
9
+ parents: readonly PageHeaderCrumb[];
10
+ }
11
+ export interface PageHeaderContext {
12
+ /**
13
+ * The page's ancestors (never the page itself), given the ancestors it
14
+ * knows beyond the shell's nav. Called inside a reactive derivation, so
15
+ * the shell may read reactive state (the current path, its nav).
16
+ */
17
+ crumbs(parents: readonly PageHeaderCrumb[]): PageHeaderCrumb[];
18
+ /** Hear the page's title and parents; return a cleanup for when it goes. */
19
+ report?(page: PageHeaderReport): (() => void) | undefined;
20
+ }
21
+ /** Called by a shell layout; applies to every `PageHeader` below it. */
22
+ export declare function setPageHeaderContext(context: PageHeaderContext): void;
23
+ /** The enclosing shell's page-header context, or null. */
24
+ export declare function getPageHeaderContext(): PageHeaderContext | null;
25
+ //# sourceMappingURL=page-header-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-header-context.d.ts","sourceRoot":"","sources":["../../../src/components/layout/page-header-context.ts"],"names":[],"mappings":"AAQA,8BAA8B;AAC9B,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wCAAwC;AACxC,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,SAAS,eAAe,EAAE,CAAC;CACrC;AAED,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,MAAM,CAAC,OAAO,EAAE,SAAS,eAAe,EAAE,GAAG,eAAe,EAAE,CAAC;IAC/D,4EAA4E;IAC5E,MAAM,CAAC,CAAC,IAAI,EAAE,gBAAgB,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC;CAC3D;AAID,wEAAwE;AACxE,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAErE;AAED,0DAA0D;AAC1D,wBAAgB,oBAAoB,IAAI,iBAAiB,GAAG,IAAI,CAE/D"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * How a shell (an app's workspace layout) takes part in every `PageHeader`
3
+ * below it, without the pages or the packages that render them knowing the
4
+ * shell: it turns a page's own `parents` into the full ancestor trail (from
5
+ * its nav) and hears each page's title, e.g. for a phone top bar.
6
+ */
7
+ import { getContext, setContext } from 'svelte';
8
+ const PAGE_HEADER_CONTEXT = Symbol.for('smrt-ui.page-header');
9
+ /** Called by a shell layout; applies to every `PageHeader` below it. */
10
+ export function setPageHeaderContext(context) {
11
+ setContext(PAGE_HEADER_CONTEXT, context);
12
+ }
13
+ /** The enclosing shell's page-header context, or null. */
14
+ export function getPageHeaderContext() {
15
+ return getContext(PAGE_HEADER_CONTEXT) ?? null;
16
+ }
@@ -11,6 +11,8 @@ import { Icon } from '../display/index.js';
11
11
  import type { FilterOption } from './types.js';
12
12
 
13
13
  export interface Props {
14
+ /** Target density; omit to inherit the ThemeProvider setting. */
15
+ density?: 'comfortable' | 'touch';
14
16
  /** Available filter options */
15
17
  options: FilterOption[];
16
18
  /** Currently selected value */
@@ -32,6 +34,7 @@ const {
32
34
  size = 'md',
33
35
  showAll = false,
34
36
  allLabel = 'All',
37
+ density,
35
38
  }: Props = $props();
36
39
 
37
40
  // Build full options list with optional "all"
@@ -54,7 +57,7 @@ function handleClick(value: string) {
54
57
  }
55
58
  </script>
56
59
 
57
- <div class="filter-chips" class:sm={size === 'sm'} role="radiogroup">
60
+ <div data-density={density} class="filter-chips" class:sm={size === 'sm'} role="radiogroup">
58
61
  {#each allOptions as option (option.value)}
59
62
  {@const isActive = option.value === selected}
60
63
  <button
@@ -149,4 +152,8 @@ function handleClick(value: string) {
149
152
  .active .chip-count {
150
153
  color: var(--smrt-color-on-secondary-container);
151
154
  }
155
+
156
+ .filter-chips[data-density='touch'] { --smrt-control-target-min: var(--smrt-touch-target-min, 48px); }
157
+ .filter-chips[data-density='comfortable'] { --smrt-control-target-min: 0px; }
158
+ .filter-chip { min-height: var(--smrt-control-target-min, 0px); min-width: var(--smrt-control-target-min, 0px); }
152
159
  </style>
@@ -1,5 +1,7 @@
1
1
  import type { FilterOption } from './types.js';
2
2
  export interface Props {
3
+ /** Target density; omit to inherit the ThemeProvider setting. */
4
+ density?: 'comfortable' | 'touch';
3
5
  /** Available filter options */
4
6
  options: FilterOption[];
5
7
  /** Currently selected value */
@@ -1 +1 @@
1
- {"version":3,"file":"FilterChips.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/nav/FilterChips.svelte.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG/C,MAAM,WAAW,KAAK;IACpB,+BAA+B;IAC/B,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,mBAAmB;IACnB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACnB,wBAAwB;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,uBAAuB;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAyDD,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"FilterChips.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/nav/FilterChips.svelte.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG/C,MAAM,WAAW,KAAK;IACpB,iEAAiE;IACjE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC;IAClC,+BAA+B;IAC/B,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,mBAAmB;IACnB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACnB,wBAAwB;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,uBAAuB;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AA0DD,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -10,9 +10,19 @@
10
10
  * - Proper ARIA roles: tablist, tab, tabpanel
11
11
  * - aria-selected indicates active tab
12
12
  * - aria-controls links tab to panel
13
+ *
14
+ * Link tabs: give every tab an `href` and the row renders as navigation — a
15
+ * `<nav>` of links with `aria-current="page"` on the active one, each tab its
16
+ * own URL (a content item's Article / Images / Review pages). With
17
+ * `maxVisible`, extra link tabs move to a "More" menu while the active tab
18
+ * always stays in the row; `badge` marks a tab needing attention (and puts a
19
+ * dot on "More" when a hidden tab has one). The row scrolls sideways on
20
+ * phones and keeps the active tab scrolled into view. It carries
21
+ * `data-shell-tabs`, so AdminShell keeps it sticky under its phone top bar.
13
22
  */
14
23
  import type { Snippet } from 'svelte';
15
24
  import { ripple } from '../../actions/ripple.js';
25
+ import { splitTabs } from './tabs-overflow.js';
16
26
  import type { Tab } from './types.js';
17
27
 
18
28
  export interface Props {
@@ -30,6 +40,12 @@ export interface Props {
30
40
  children?: Snippet;
31
41
  /** Accessible label for the tablist */
32
42
  'aria-label'?: string;
43
+ /** Link tabs only: most tabs shown in the row; the rest go under "More". */
44
+ maxVisible?: number;
45
+ /** Link tabs only: label of the overflow menu (default "More"). */
46
+ moreLabel?: string;
47
+ /** Link tabs only: screen-reader text for the "More" attention dot. */
48
+ moreBadgeLabel?: string;
33
49
  }
34
50
 
35
51
  const {
@@ -40,8 +56,39 @@ const {
40
56
  variant = 'primary',
41
57
  children,
42
58
  'aria-label': ariaLabel,
59
+ maxVisible,
60
+ moreLabel = 'More',
61
+ moreBadgeLabel = 'has updates',
43
62
  }: Props = $props();
44
63
 
64
+ /** Link tabs: every tab has an href, so the row is navigation, not a tablist. */
65
+ const linkMode = $derived(tabs.length > 0 && tabs.every((tab) => !!tab.href));
66
+ const split = $derived(
67
+ linkMode
68
+ ? splitTabs(tabs, active, maxVisible)
69
+ : { visible: tabs, overflow: [] as Tab[] },
70
+ );
71
+ // Same rule as the tab's own badge, so an empty badge dots neither.
72
+ const overflowHasBadge = $derived(split.overflow.some((tab) => hasBadge(tab)));
73
+ let moreMenu = $state<HTMLDetailsElement | null>(null);
74
+ let linkRow = $state<HTMLElement | null>(null);
75
+
76
+ // Keep the active link tab scrolled into view in a sideways-scrolling row,
77
+ // and close "More" once the active tab changes (a link was followed).
78
+ $effect(() => {
79
+ const current = active;
80
+ if (!linkMode || !linkRow) return;
81
+ if (moreMenu) moreMenu.open = false;
82
+ const activeLink = linkRow.querySelector<HTMLElement>(
83
+ `[data-tab-id="${CSS.escape(current)}"]`,
84
+ );
85
+ activeLink?.scrollIntoView?.({ block: 'nearest', inline: 'nearest' });
86
+ });
87
+
88
+ function hasBadge(tab: Tab): boolean {
89
+ return tab.badge !== undefined && tab.badge !== null && tab.badge !== '';
90
+ }
91
+
45
92
  /** Get enabled tabs only */
46
93
  const enabledTabs = $derived(tabs.filter((t) => !t.disabled));
47
94
 
@@ -51,6 +98,9 @@ let tablistEl: HTMLElement | null = $state(null);
51
98
  const instanceId = $props.id();
52
99
 
53
100
  function handleClick(id: string) {
101
+ // A disabled tab (a link one has no href but is still clickable) never
102
+ // changes the active tab.
103
+ if (tabs.find((tab) => tab.id === id)?.disabled) return;
54
104
  if (id !== active) {
55
105
  onchange?.(id);
56
106
  }
@@ -101,6 +151,70 @@ function handleKeydown(event: KeyboardEvent, tabId: string) {
101
151
  }
102
152
  </script>
103
153
 
154
+ {#snippet linkTab(tab: Tab)}
155
+ <a
156
+ class="tab-link"
157
+ class:active={tab.id === active}
158
+ href={tab.disabled ? undefined : tab.href}
159
+ aria-current={tab.id === active ? 'page' : undefined}
160
+ aria-disabled={tab.disabled ? 'true' : undefined}
161
+ data-tab-id={tab.id}
162
+ onclick={(event) => {
163
+ if (tab.disabled) {
164
+ event.preventDefault();
165
+ return;
166
+ }
167
+ handleClick(tab.id);
168
+ }}
169
+ >
170
+ <span class="tab-label">{tab.label}</span>
171
+ {#if tab.count !== undefined}
172
+ <span class="tab-count">{tab.count}</span>
173
+ {/if}
174
+ {#if hasBadge(tab)}
175
+ <span class="tab-badge">{tab.badge}</span>
176
+ {/if}
177
+ </a>
178
+ {/snippet}
179
+
180
+ {#if linkMode}
181
+ <div class="tabs-container">
182
+ <nav
183
+ class="tabs-links"
184
+ class:sm={size === 'sm'}
185
+ class:lg={size === 'lg'}
186
+ aria-label={ariaLabel}
187
+ data-shell-tabs
188
+ >
189
+ <div class="tabs-links-row" bind:this={linkRow}>
190
+ {#each split.visible as tab (tab.id)}
191
+ {@render linkTab(tab)}
192
+ {/each}
193
+ </div>
194
+ {#if split.overflow.length > 0}
195
+ <details class="tabs-more" bind:this={moreMenu}>
196
+ <summary class="tab-link">
197
+ <span class="tab-label">{moreLabel}</span>
198
+ {#if overflowHasBadge}
199
+ <span class="tab-dot" aria-hidden="true"></span>
200
+ <span class="visually-hidden">({moreBadgeLabel})</span>
201
+ {/if}
202
+ </summary>
203
+ <div class="tabs-more-menu">
204
+ {#each split.overflow as tab (tab.id)}
205
+ {@render linkTab(tab)}
206
+ {/each}
207
+ </div>
208
+ </details>
209
+ {/if}
210
+ </nav>
211
+ {#if children}
212
+ <div class="tab-panel">
213
+ {@render children()}
214
+ </div>
215
+ {/if}
216
+ </div>
217
+ {:else}
104
218
  <div class="tabs-container">
105
219
  <div
106
220
  class="tabs-nav"
@@ -132,6 +246,9 @@ function handleKeydown(event: KeyboardEvent, tabId: string) {
132
246
  {#if tab.count !== undefined}
133
247
  <span class="tab-count">{tab.count}</span>
134
248
  {/if}
249
+ {#if hasBadge(tab)}
250
+ <span class="tab-badge">{tab.badge}</span>
251
+ {/if}
135
252
  </span>
136
253
  <div class="active-indicator"></div>
137
254
  </button>
@@ -149,6 +266,7 @@ function handleKeydown(event: KeyboardEvent, tabId: string) {
149
266
  </div>
150
267
  {/if}
151
268
  </div>
269
+ {/if}
152
270
 
153
271
  <style>
154
272
  .tabs-container {
@@ -249,4 +367,127 @@ function handleKeydown(event: KeyboardEvent, tabId: string) {
249
367
  .tab-panel {
250
368
  padding-top: 1.5rem;
251
369
  }
370
+
371
+ .tab-badge {
372
+ min-width: 1.25rem;
373
+ padding: 0.05rem 0.4rem;
374
+ border-radius: 999px;
375
+ background: var(--smrt-color-surface-container-high);
376
+ color: var(--smrt-color-on-surface);
377
+ font: var(--smrt-typography-label-small-font);
378
+ font-weight: var(--smrt-typography-weight-bold, 700);
379
+ text-align: center;
380
+ }
381
+
382
+ /* Link tabs: a navigation row, one URL per tab. */
383
+ .tabs-links {
384
+ display: flex;
385
+ align-items: stretch;
386
+ gap: var(--smrt-spacing-1, 4px);
387
+ width: 100%;
388
+ border-bottom: 1px solid var(--smrt-color-outline-variant);
389
+ }
390
+
391
+ .tabs-links-row {
392
+ display: flex;
393
+ flex: 1 1 auto;
394
+ min-width: 0;
395
+ gap: var(--smrt-spacing-1, 4px);
396
+ overflow-x: auto;
397
+ scrollbar-width: thin;
398
+ }
399
+
400
+ .tab-link {
401
+ display: inline-flex;
402
+ flex: 0 0 auto;
403
+ align-items: center;
404
+ gap: var(--smrt-spacing-2, 8px);
405
+ min-height: 44px;
406
+ padding: 0 var(--smrt-spacing-4, 16px);
407
+ margin-bottom: -1px;
408
+ border-bottom: 2px solid transparent;
409
+ color: var(--smrt-color-on-surface-variant);
410
+ font: var(--smrt-typography-title-small-font);
411
+ font-weight: var(--smrt-typography-weight-semibold, 600);
412
+ text-decoration: none;
413
+ white-space: nowrap;
414
+ cursor: pointer;
415
+ }
416
+
417
+ .tabs-links.sm .tab-link {
418
+ min-height: 40px;
419
+ font: var(--smrt-typography-label-large-font);
420
+ }
421
+
422
+ .tabs-links.lg .tab-link {
423
+ min-height: 56px;
424
+ font: var(--smrt-typography-title-medium-font);
425
+ }
426
+
427
+ .tab-link:hover {
428
+ color: var(--smrt-color-on-surface);
429
+ }
430
+
431
+ .tab-link.active {
432
+ border-bottom-color: var(--smrt-color-primary);
433
+ color: var(--smrt-color-primary);
434
+ }
435
+
436
+ .tab-link[aria-disabled='true'] {
437
+ opacity: 0.38;
438
+ cursor: not-allowed;
439
+ }
440
+
441
+ .tab-link:focus-visible {
442
+ outline: 2px solid var(--smrt-color-primary);
443
+ outline-offset: -2px;
444
+ }
445
+
446
+ .tab-dot {
447
+ width: 0.5rem;
448
+ height: 0.5rem;
449
+ border-radius: 999px;
450
+ background: var(--smrt-color-primary);
451
+ }
452
+
453
+ .tabs-more {
454
+ position: relative;
455
+ flex: 0 0 auto;
456
+ }
457
+
458
+ .tabs-more summary {
459
+ list-style: none;
460
+ }
461
+
462
+ .tabs-more summary::-webkit-details-marker {
463
+ display: none;
464
+ }
465
+
466
+ .tabs-more-menu {
467
+ position: absolute;
468
+ inset-inline-end: 0;
469
+ z-index: var(--smrt-z-index-dropdown, 20);
470
+ display: grid;
471
+ min-width: 12rem;
472
+ padding: var(--smrt-spacing-1, 4px);
473
+ border: 1px solid var(--smrt-color-outline-variant);
474
+ border-radius: var(--smrt-radius-small, 0.5rem);
475
+ background: var(--smrt-color-surface);
476
+ box-shadow: var(--smrt-elevation-2);
477
+ }
478
+
479
+ .tabs-more-menu .tab-link {
480
+ margin: 0;
481
+ border-bottom: 0;
482
+ border-radius: var(--smrt-radius-extra-small, 0.35rem);
483
+ }
484
+
485
+ .visually-hidden {
486
+ position: absolute;
487
+ width: 1px;
488
+ height: 1px;
489
+ overflow: hidden;
490
+ clip: rect(0 0 0 0);
491
+ white-space: nowrap;
492
+ }
252
493
  </style>
@@ -9,6 +9,15 @@
9
9
  * - Proper ARIA roles: tablist, tab, tabpanel
10
10
  * - aria-selected indicates active tab
11
11
  * - aria-controls links tab to panel
12
+ *
13
+ * Link tabs: give every tab an `href` and the row renders as navigation — a
14
+ * `<nav>` of links with `aria-current="page"` on the active one, each tab its
15
+ * own URL (a content item's Article / Images / Review pages). With
16
+ * `maxVisible`, extra link tabs move to a "More" menu while the active tab
17
+ * always stays in the row; `badge` marks a tab needing attention (and puts a
18
+ * dot on "More" when a hidden tab has one). The row scrolls sideways on
19
+ * phones and keeps the active tab scrolled into view. It carries
20
+ * `data-shell-tabs`, so AdminShell keeps it sticky under its phone top bar.
12
21
  */
13
22
  import type { Snippet } from 'svelte';
14
23
  import type { Tab } from './types.js';
@@ -27,6 +36,12 @@ export interface Props {
27
36
  children?: Snippet;
28
37
  /** Accessible label for the tablist */
29
38
  'aria-label'?: string;
39
+ /** Link tabs only: most tabs shown in the row; the rest go under "More". */
40
+ maxVisible?: number;
41
+ /** Link tabs only: label of the overflow menu (default "More"). */
42
+ moreLabel?: string;
43
+ /** Link tabs only: screen-reader text for the "More" attention dot. */
44
+ moreBadgeLabel?: string;
30
45
  }
31
46
  declare const Tabs: import("svelte").Component<Props, {}, "">;
32
47
  type Tabs = ReturnType<typeof Tabs>;
@@ -1 +1 @@
1
- {"version":3,"file":"Tabs.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/nav/Tabs.svelte.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAGtC,MAAM,WAAW,KAAK;IACpB,qBAAqB;IACrB,IAAI,EAAE,GAAG,EAAE,CAAC;IACZ,8BAA8B;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,8BAA8B;IAC9B,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,mBAAmB;IACnB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B,qBAAqB;IACrB,OAAO,CAAC,EAAE,SAAS,GAAG,WAAW,CAAC;IAClC,uBAAuB;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAsGD,QAAA,MAAM,IAAI,2CAAwC,CAAC;AACnD,KAAK,IAAI,GAAG,UAAU,CAAC,OAAO,IAAI,CAAC,CAAC;AACpC,eAAe,IAAI,CAAC"}
1
+ {"version":3,"file":"Tabs.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/nav/Tabs.svelte.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAGtC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAGtC,MAAM,WAAW,KAAK;IACpB,qBAAqB;IACrB,IAAI,EAAE,GAAG,EAAE,CAAC;IACZ,8BAA8B;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,8BAA8B;IAC9B,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,mBAAmB;IACnB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B,qBAAqB;IACrB,OAAO,CAAC,EAAE,SAAS,GAAG,WAAW,CAAC;IAClC,uBAAuB;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AA+LD,QAAA,MAAM,IAAI,2CAAwC,CAAC;AACnD,KAAK,IAAI,GAAG,UAAU,CAAC,OAAO,IAAI,CAAC,CAAC;AACpC,eAAe,IAAI,CAAC"}
@@ -0,0 +1,117 @@
1
+ /** Link tabs: a navigation row of URLs with overflow, badges, active-in-view. */
2
+ import { render, screen, within } from '@testing-library/svelte';
3
+ import userEvent from '@testing-library/user-event';
4
+ import { describe, expect, it, vi } from 'vitest';
5
+ import { expectNoA11yViolations } from '../../../test-support/a11y';
6
+ import Tabs from '../Tabs.svelte';
7
+ import { splitTabs } from '../tabs-overflow.js';
8
+ const TABS = [
9
+ { id: 'article', label: 'Article', href: '/c/1' },
10
+ { id: 'images', label: 'Images', href: '/c/1/images', badge: 3 },
11
+ { id: 'videos', label: 'Videos', href: '/c/1/videos' },
12
+ { id: 'social', label: 'Social', href: '/c/1/social' },
13
+ { id: 'review', label: 'Review', href: '/c/1/review', badge: '!' },
14
+ { id: 'jobs', label: 'Background work', href: '/c/1/jobs' },
15
+ ];
16
+ describe('splitTabs', () => {
17
+ it('keeps everything in the row under the limit', () => {
18
+ expect(splitTabs(TABS, 'article', 10).overflow).toEqual([]);
19
+ expect(splitTabs(TABS, 'article', undefined).overflow).toEqual([]);
20
+ });
21
+ it('promotes the active tab into the row', () => {
22
+ const { visible, overflow } = splitTabs(TABS, 'jobs', 4);
23
+ expect(visible.map((tab) => tab.id)).toEqual([
24
+ 'article',
25
+ 'images',
26
+ 'videos',
27
+ 'jobs',
28
+ ]);
29
+ expect(overflow.map((tab) => tab.id)).toEqual(['social', 'review']);
30
+ });
31
+ });
32
+ describe('Tabs with hrefs', () => {
33
+ it('renders navigation links, not a tablist, with aria-current on the active tab', () => {
34
+ render(Tabs, {
35
+ props: { tabs: TABS, active: 'images', 'aria-label': 'Content sections' },
36
+ });
37
+ const nav = screen.getByRole('navigation', { name: 'Content sections' });
38
+ expect(nav).toHaveAttribute('data-shell-tabs');
39
+ expect(screen.queryByRole('tablist')).not.toBeInTheDocument();
40
+ const active = within(nav).getByRole('link', { name: /Images/ });
41
+ expect(active).toHaveAttribute('href', '/c/1/images');
42
+ expect(active).toHaveAttribute('aria-current', 'page');
43
+ expect(active).toHaveTextContent('3');
44
+ expect(within(nav).getByRole('link', { name: 'Article' })).not.toHaveAttribute('aria-current');
45
+ });
46
+ it('moves extra tabs under "More", keeps the active one visible, and dots the menu for a hidden badge', async () => {
47
+ const { container } = render(Tabs, {
48
+ props: {
49
+ tabs: TABS,
50
+ active: 'jobs',
51
+ maxVisible: 4,
52
+ 'aria-label': 'Content sections',
53
+ },
54
+ });
55
+ const row = container.querySelector('.tabs-links-row');
56
+ expect(within(row)
57
+ .getAllByRole('link')
58
+ .map((link) => link.dataset.tabId)).toEqual(['article', 'images', 'videos', 'jobs']);
59
+ const more = container.querySelector('details.tabs-more');
60
+ expect(within(more).getByText('More')).toBeInTheDocument();
61
+ expect(within(more).getByText('(has updates)')).toBeInTheDocument();
62
+ await userEvent.click(within(more).getByText('More'));
63
+ expect(more.open).toBe(true);
64
+ expect(within(more).getByRole('link', { name: /Review/ })).toHaveAttribute('href', '/c/1/review');
65
+ });
66
+ it('a disabled link tab never fires onchange', async () => {
67
+ const onchange = vi.fn();
68
+ render(Tabs, {
69
+ props: {
70
+ tabs: [
71
+ { id: 'article', label: 'Article', href: '/c/1' },
72
+ {
73
+ id: 'locked',
74
+ label: 'Locked',
75
+ href: '/c/1/locked',
76
+ disabled: true,
77
+ },
78
+ ],
79
+ active: 'article',
80
+ onchange,
81
+ 'aria-label': 'Content sections',
82
+ },
83
+ });
84
+ const locked = screen.getByText('Locked').closest('a');
85
+ await userEvent.click(locked);
86
+ expect(onchange).not.toHaveBeenCalled();
87
+ });
88
+ it('does not dot "More" for an empty-string badge, matching the tab itself', () => {
89
+ const { container } = render(Tabs, {
90
+ props: {
91
+ tabs: [
92
+ { id: 'a', label: 'A', href: '/a' },
93
+ { id: 'b', label: 'B', href: '/b' },
94
+ { id: 'c', label: 'C', href: '/c', badge: '' },
95
+ ],
96
+ active: 'a',
97
+ maxVisible: 2,
98
+ 'aria-label': 'Sections',
99
+ },
100
+ });
101
+ const more = container.querySelector('details.tabs-more');
102
+ expect(more).not.toBeNull();
103
+ expect(more.querySelector('.tab-dot')).toBeNull();
104
+ expect(more.querySelector('.tab-badge')).toBeNull();
105
+ });
106
+ it('is axe-clean', async () => {
107
+ const { container } = render(Tabs, {
108
+ props: {
109
+ tabs: TABS,
110
+ active: 'article',
111
+ maxVisible: 4,
112
+ 'aria-label': 'Content sections',
113
+ },
114
+ });
115
+ await expectNoA11yViolations(container);
116
+ });
117
+ });
@@ -3,5 +3,6 @@
3
3
  */
4
4
  export { default as FilterChips } from './FilterChips.svelte';
5
5
  export { default as Tabs } from './Tabs.svelte';
6
+ export { splitTabs } from './tabs-overflow.js';
6
7
  export type { FilterOption, Tab } from './types.js';
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/nav/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,eAAe,CAAC;AAEhD,YAAY,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/nav/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C,YAAY,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC"}
@@ -4,3 +4,4 @@
4
4
  // Export components
5
5
  export { default as FilterChips } from './FilterChips.svelte';
6
6
  export { default as Tabs } from './Tabs.svelte';
7
+ export { splitTabs } from './tabs-overflow.js';
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Split tabs into the row and a "More" menu, keeping the active tab in the
3
+ * row: with more tabs than `maxVisible`, the first `maxVisible - 1` show, then
4
+ * the active tab (or the next one), and the rest go to the menu.
5
+ */
6
+ export declare function splitTabs<T extends {
7
+ id: string;
8
+ }>(tabs: readonly T[], activeId: string | null | undefined, maxVisible: number | undefined): {
9
+ visible: T[];
10
+ overflow: T[];
11
+ };
12
+ //# sourceMappingURL=tabs-overflow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tabs-overflow.d.ts","sourceRoot":"","sources":["../../../src/components/nav/tabs-overflow.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAChD,IAAI,EAAE,SAAS,CAAC,EAAE,EAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACnC,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B;IAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IAAC,QAAQ,EAAE,CAAC,EAAE,CAAA;CAAE,CAWjC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Split tabs into the row and a "More" menu, keeping the active tab in the
3
+ * row: with more tabs than `maxVisible`, the first `maxVisible - 1` show, then
4
+ * the active tab (or the next one), and the rest go to the menu.
5
+ */
6
+ export function splitTabs(tabs, activeId, maxVisible) {
7
+ if (!maxVisible || maxVisible < 1 || tabs.length <= maxVisible) {
8
+ return { visible: [...tabs], overflow: [] };
9
+ }
10
+ const visible = tabs.slice(0, maxVisible - 1);
11
+ const rest = tabs.slice(maxVisible - 1);
12
+ const promoted = rest.find((tab) => tab.id === activeId) ?? rest[0];
13
+ return {
14
+ visible: promoted ? [...visible, promoted] : visible,
15
+ overflow: rest.filter((tab) => tab !== promoted),
16
+ };
17
+ }
@@ -20,5 +20,13 @@ export interface Tab {
20
20
  count?: number;
21
21
  /** Disabled state */
22
22
  disabled?: boolean;
23
+ /**
24
+ * Makes this a link tab: the tab row renders as navigation (`<nav>` of
25
+ * links, `aria-current="page"` on the active one) instead of a tablist.
26
+ * Give every tab an `href` or none.
27
+ */
28
+ href?: string;
29
+ /** Short attention badge (e.g. `3` pending, `!`); `null`/absent shows none. */
30
+ badge?: string | number | null;
23
31
  }
24
32
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/nav/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,YAAY;IAC3B,mBAAmB;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,oBAAoB;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,GAAG;IAClB,qBAAqB;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,oBAAoB;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/nav/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,YAAY;IAC3B,mBAAmB;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,oBAAoB;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,GAAG;IAClB,qBAAqB;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,oBAAoB;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+EAA+E;IAC/E,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;CAChC"}