@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,1250 @@
1
+ <script lang="ts">
2
+ /**
3
+ * CalendarView — a generic month calendar with a phone agenda.
4
+ *
5
+ * - `month` mode: a month grid (`role="grid"`, roving focus, arrow keys).
6
+ * All-day and multi-day items render as bands across the days they cover;
7
+ * a day that has more than fits shows "+N more", which opens the day in a
8
+ * panel under the grid (never a modal) or follows `dayHref`.
9
+ * - `agenda` mode: a scrollable strip of the month's days above a day-by-day
10
+ * list. `auto` (default) uses the agenda below 48rem, the grid above.
11
+ *
12
+ * Every day is computed in `timeZone` (IANA) and every name comes from
13
+ * `Intl` in `locale`, so the browser's own zone and language never leak in.
14
+ * The visible month and selected day can be controlled (`year`/`month`,
15
+ * `selectedDate`) and are reported through `onNavigate` / `onSelectDate`,
16
+ * so a page can keep them in its URL.
17
+ */
18
+ import type { Snippet } from 'svelte';
19
+ import { tick } from 'svelte';
20
+ import { M } from '../../i18n/strings.ui.js';
21
+ import { useI18n } from '../../i18n/use-i18n.js';
22
+ import {
23
+ addDays,
24
+ addMonthsToKey,
25
+ type CalendarEntry,
26
+ type CalendarItem,
27
+ type CalendarMode,
28
+ type CalendarMonth,
29
+ defaultTimeZone,
30
+ defaultWeekStart,
31
+ entriesOnDay,
32
+ formatKey,
33
+ layoutMonth,
34
+ monthKeys,
35
+ monthOfKey,
36
+ monthWeeks,
37
+ parseKey,
38
+ resolveTimeZone,
39
+ shiftMonth,
40
+ todayKey,
41
+ toEntries,
42
+ toneFor,
43
+ weekdayOfKey,
44
+ } from './calendar-model.js';
45
+
46
+ export interface Props {
47
+ /** Items to show. */
48
+ items?: CalendarItem[];
49
+ /** Visible year (controlled together with `month`). */
50
+ year?: number;
51
+ /** Visible month, 1-12 (controlled together with `year`). */
52
+ month?: number;
53
+ /** Selected day as `YYYY-MM-DD` (controlled). */
54
+ selectedDate?: string | null;
55
+ /** IANA time zone every day is computed in. Defaults to the browser's. */
56
+ timeZone?: string;
57
+ /** BCP 47 locale for month/day names and times. Defaults to the i18n locale. */
58
+ locale?: string;
59
+ /** First weekday column, 0 = Sunday. Defaults to the locale's. */
60
+ weekStartsOn?: number;
61
+ /** `auto` switches to the agenda below 48rem. */
62
+ mode?: CalendarMode;
63
+ /** Rows per day in the month grid, including band rows. */
64
+ maxPerDay?: number;
65
+ /** Heading level of the month title. */
66
+ headingLevel?: 2 | 3 | 4;
67
+ /**
68
+ * "Now", for today highlighting (tests, server rendering). Without it the
69
+ * calendar reads the clock after mount and moves "today" at midnight in
70
+ * `timeZone`; before mount (server rendering) nothing is highlighted as
71
+ * today. Server-rendered pages should pass `timeZone` and `year`/`month`
72
+ * (or `now`) so the server and the browser pick the same month.
73
+ */
74
+ now?: Date;
75
+ /** Called when the visible month changes (prev/next/today/keyboard). */
76
+ onNavigate?: (month: CalendarMonth) => void;
77
+ /** Called when a day is selected. */
78
+ onSelectDate?: (date: string) => void;
79
+ /** Called when an item is activated (also for items with `href`). */
80
+ onItemSelect?: (item: CalendarItem) => void;
81
+ /** Link for a day; days become links and "+N more" follows it. */
82
+ dayHref?: (date: string) => string | undefined;
83
+ /** Custom row content for an item in the day panel and agenda. */
84
+ itemContent?: Snippet<[CalendarItem, { time: string }]>;
85
+ /** Extra class on the root. */
86
+ class?: string;
87
+ }
88
+
89
+ let {
90
+ items = [],
91
+ year,
92
+ month,
93
+ selectedDate = null,
94
+ timeZone: timeZoneProp,
95
+ locale: localeProp,
96
+ weekStartsOn: weekStartsOnProp,
97
+ mode = 'auto',
98
+ maxPerDay = 3,
99
+ headingLevel = 2,
100
+ now,
101
+ onNavigate,
102
+ onSelectDate,
103
+ onItemSelect,
104
+ dayHref,
105
+ itemContent,
106
+ class: className = '',
107
+ }: Props = $props();
108
+
109
+ const i18n = useI18n();
110
+ const t = i18n.t;
111
+ const uid = $props.id();
112
+
113
+ // An invalid zone (a typo, one this runtime lacks) would throw a RangeError
114
+ // from every Intl call in the deriveds below; fall back and say so instead.
115
+ const timeZone = $derived(
116
+ resolveTimeZone(timeZoneProp, (invalid) =>
117
+ console.warn(
118
+ `[CalendarView] unknown time zone "${invalid}"; using ${defaultTimeZone()}`,
119
+ ),
120
+ ),
121
+ );
122
+ const locale = $derived(localeProp || i18n.locale || 'en');
123
+ const weekStartsOn = $derived(
124
+ weekStartsOnProp === undefined
125
+ ? defaultWeekStart(locale)
126
+ : ((Math.floor(weekStartsOnProp) % 7) + 7) % 7,
127
+ );
128
+ // The clock is read after mount, never during server rendering (the server's
129
+ // clock and zone are not the reader's), and re-read each minute so "today"
130
+ // moves at midnight in `timeZone`.
131
+ let clock = $state<Date | undefined>(undefined);
132
+ $effect(() => {
133
+ if (now) return;
134
+ clock = new Date();
135
+ const timer = setInterval(() => {
136
+ const next = new Date();
137
+ if (!clock || todayKey(timeZone, next) !== todayKey(timeZone, clock)) {
138
+ clock = next;
139
+ }
140
+ }, 60_000);
141
+ return () => clearInterval(timer);
142
+ });
143
+ const reference = $derived(now ?? clock);
144
+ /** Today's key, or '' before the clock is known (nothing is "today"). */
145
+ const today = $derived(reference ? todayKey(timeZone, reference) : '');
146
+ /** The month shown when nothing controls it: today's, else the render clock's. */
147
+ const fallbackKey = $derived(today || todayKey(timeZone));
148
+
149
+ // Overridable deriveds: follow the controlling props, and take local
150
+ // navigation until those props change again.
151
+ let current = $derived<CalendarMonth>(
152
+ year !== undefined && month !== undefined
153
+ ? { year, month }
154
+ : selectedDate
155
+ ? monthOfKey(selectedDate)
156
+ : monthOfKey(fallbackKey),
157
+ );
158
+ let selected = $derived<string | null>(selectedDate ?? null);
159
+ let focusKey = $derived(
160
+ selected && sameMonth(selected, current)
161
+ ? selected
162
+ : today && sameMonth(today, current)
163
+ ? today
164
+ : formatKey(current.year, current.month, 1),
165
+ );
166
+
167
+ let isPhone = $state(false);
168
+ $effect(() => {
169
+ if (mode !== 'auto' || typeof window.matchMedia !== 'function') return;
170
+ const query = window.matchMedia('(max-width: 48rem)');
171
+ isPhone = query.matches;
172
+ const onChange = (event: MediaQueryListEvent) => {
173
+ isPhone = event.matches;
174
+ };
175
+ query.addEventListener('change', onChange);
176
+ return () => query.removeEventListener('change', onChange);
177
+ });
178
+ const view = $derived(mode === 'auto' ? (isPhone ? 'agenda' : 'month') : mode);
179
+
180
+ let root: HTMLElement | undefined = $state();
181
+
182
+ const entries = $derived(
183
+ toEntries(items, timeZone, (item) =>
184
+ console.warn(
185
+ `[CalendarView] item "${item.id}" has an invalid start or end and was skipped`,
186
+ ),
187
+ ),
188
+ );
189
+ const weeks = $derived(
190
+ layoutMonth(current, entries, { weekStartsOn, maxPerDay }),
191
+ );
192
+ const daysOfMonth = $derived(monthKeys(current));
193
+ const agendaDays = $derived(
194
+ daysOfMonth
195
+ .map((key) => ({ key, entries: entriesOnDay(entries, key) }))
196
+ .filter((day) => day.entries.length > 0 || day.key === selected),
197
+ );
198
+ const selectedEntries = $derived(
199
+ selected ? entriesOnDay(entries, selected) : [],
200
+ );
201
+
202
+ // ---- Intl formatting (all in `timeZone` / `locale`) ----
203
+
204
+ const monthTitle = $derived(
205
+ new Intl.DateTimeFormat(locale, {
206
+ month: 'long',
207
+ year: 'numeric',
208
+ timeZone: 'UTC',
209
+ }).format(Date.UTC(current.year, current.month - 1, 15)),
210
+ );
211
+ const longDateFormat = $derived(
212
+ new Intl.DateTimeFormat(locale, {
213
+ weekday: 'long',
214
+ month: 'long',
215
+ day: 'numeric',
216
+ year: 'numeric',
217
+ timeZone: 'UTC',
218
+ }),
219
+ );
220
+ const shortDateFormat = $derived(
221
+ new Intl.DateTimeFormat(locale, {
222
+ month: 'short',
223
+ day: 'numeric',
224
+ timeZone: 'UTC',
225
+ }),
226
+ );
227
+ const timeFormat = $derived(
228
+ new Intl.DateTimeFormat(locale, {
229
+ hour: 'numeric',
230
+ minute: '2-digit',
231
+ timeZone,
232
+ }),
233
+ );
234
+ const plural = $derived(new Intl.PluralRules(locale));
235
+ const weekdays = $derived(
236
+ monthWeeks(current, weekStartsOn)[0].map((key) => {
237
+ const date = keyDate(key);
238
+ return {
239
+ short: new Intl.DateTimeFormat(locale, {
240
+ weekday: 'short',
241
+ timeZone: 'UTC',
242
+ }).format(date),
243
+ narrow: new Intl.DateTimeFormat(locale, {
244
+ weekday: 'narrow',
245
+ timeZone: 'UTC',
246
+ }).format(date),
247
+ long: new Intl.DateTimeFormat(locale, {
248
+ weekday: 'long',
249
+ timeZone: 'UTC',
250
+ }).format(date),
251
+ };
252
+ }),
253
+ );
254
+
255
+ function keyDate(key: string): Date {
256
+ const { year: y, month: m, day } = parseKey(key);
257
+ return new Date(Date.UTC(y, m - 1, day, 12));
258
+ }
259
+
260
+ function sameMonth(key: string, value: CalendarMonth): boolean {
261
+ const parts = parseKey(key);
262
+ return parts.year === value.year && parts.month === value.month;
263
+ }
264
+
265
+ function longDate(key: string): string {
266
+ return longDateFormat.format(keyDate(key));
267
+ }
268
+
269
+ function countLabel(count: number): string {
270
+ return plural.select(count) === 'one'
271
+ ? t(M['ui.calendar.items_one'])
272
+ : t(M['ui.calendar.items_other'], { count });
273
+ }
274
+
275
+ function dayLabel(key: string, count: number): string {
276
+ const base = longDate(key);
277
+ return count > 0 ? `${base}, ${countLabel(count)}` : base;
278
+ }
279
+
280
+ function timeLabel(entry: CalendarEntry, dayKey?: string): string {
281
+ if (entry.allDay || (dayKey && entry.startKey !== dayKey)) {
282
+ return t(M['ui.calendar.all_day']);
283
+ }
284
+ return timeFormat.format(entry.startMs);
285
+ }
286
+
287
+ function spanNote(entry: CalendarEntry): string | null {
288
+ if (entry.startKey === entry.endKey) return null;
289
+ return t(M['ui.calendar.until'], {
290
+ date: shortDateFormat.format(keyDate(entry.endKey)),
291
+ });
292
+ }
293
+
294
+ function itemStyle(item: CalendarItem): string {
295
+ return item.color ? `--cv-item: ${item.color};` : '';
296
+ }
297
+
298
+ // ---- Navigation and selection ----
299
+
300
+ function navigate(next: CalendarMonth): void {
301
+ current = next;
302
+ onNavigate?.(next);
303
+ }
304
+
305
+ function goToday(): void {
306
+ const key = today || todayKey(timeZone);
307
+ const target = monthOfKey(key);
308
+ if (target.year !== current.year || target.month !== current.month) {
309
+ navigate(target);
310
+ }
311
+ focusKey = key;
312
+ if (view === 'agenda') selectDay(key);
313
+ }
314
+
315
+ function selectDay(key: string): void {
316
+ selected = key;
317
+ focusKey = key;
318
+ onSelectDate?.(key);
319
+ if (view === 'agenda') void revealAgendaDay(key);
320
+ }
321
+
322
+ function closeDay(): void {
323
+ const key = selected;
324
+ selected = null;
325
+ if (key) void focusDay(key);
326
+ }
327
+
328
+ async function focusDay(key: string): Promise<void> {
329
+ await tick();
330
+ root
331
+ ?.querySelector<HTMLElement>(`[data-cv-day="${key}"]`)
332
+ ?.focus({ preventScroll: false });
333
+ }
334
+
335
+ function reducedMotion(): boolean {
336
+ return (
337
+ typeof window.matchMedia === 'function' &&
338
+ window.matchMedia('(prefers-reduced-motion: reduce)').matches
339
+ );
340
+ }
341
+
342
+ async function revealAgendaDay(key: string): Promise<void> {
343
+ await tick();
344
+ const behavior = reducedMotion() ? 'auto' : 'smooth';
345
+ root
346
+ ?.querySelector<HTMLElement>(`[data-cv-agenda-day="${key}"]`)
347
+ ?.scrollIntoView?.({ behavior, block: 'start' });
348
+ }
349
+
350
+ function moveFocus(key: string): void {
351
+ if (!sameMonth(key, current)) navigate(monthOfKey(key));
352
+ focusKey = key;
353
+ void focusDay(key);
354
+ }
355
+
356
+ function onGridKeydown(event: KeyboardEvent): void {
357
+ const target = event.target as HTMLElement | null;
358
+ const key = target?.dataset.cvDay;
359
+ if (!key) return;
360
+ const column = (weekdayOfKey(key) - weekStartsOn + 7) % 7;
361
+ let next: string | null = null;
362
+ switch (event.key) {
363
+ case 'ArrowLeft':
364
+ next = addDays(key, -1);
365
+ break;
366
+ case 'ArrowRight':
367
+ next = addDays(key, 1);
368
+ break;
369
+ case 'ArrowUp':
370
+ next = addDays(key, -7);
371
+ break;
372
+ case 'ArrowDown':
373
+ next = addDays(key, 7);
374
+ break;
375
+ case 'Home':
376
+ next = addDays(key, -column);
377
+ break;
378
+ case 'End':
379
+ next = addDays(key, 6 - column);
380
+ break;
381
+ case 'PageUp':
382
+ next = addMonthsToKey(key, -1);
383
+ break;
384
+ case 'PageDown':
385
+ next = addMonthsToKey(key, 1);
386
+ break;
387
+ default:
388
+ return;
389
+ }
390
+ event.preventDefault();
391
+ moveFocus(next);
392
+ }
393
+
394
+ function onStripKeydown(event: KeyboardEvent): void {
395
+ const key = (event.target as HTMLElement | null)?.dataset.cvDay;
396
+ if (!key) return;
397
+ const last = daysOfMonth[daysOfMonth.length - 1];
398
+ let next: string | null = null;
399
+ if (event.key === 'ArrowLeft') next = addDays(key, -1);
400
+ else if (event.key === 'ArrowRight') next = addDays(key, 1);
401
+ else if (event.key === 'Home') next = daysOfMonth[0];
402
+ else if (event.key === 'End') next = last;
403
+ if (!next) return;
404
+ event.preventDefault();
405
+ moveFocus(next);
406
+ }
407
+
408
+ function activateItem(item: CalendarItem): void {
409
+ onItemSelect?.(item);
410
+ }
411
+
412
+ $effect(() => {
413
+ // Keep the selected (or focused) day visible in the agenda strip.
414
+ if (view !== 'agenda' || !root) return;
415
+ const key = focusKey;
416
+ void tick().then(() => {
417
+ root
418
+ ?.querySelector<HTMLElement>(`.cv-strip [data-cv-day="${key}"]`)
419
+ ?.scrollIntoView?.({ block: 'nearest', inline: 'center' });
420
+ });
421
+ });
422
+ </script>
423
+
424
+ {#snippet itemRow(entry: CalendarEntry, dayKey: string)}
425
+ {@const item = entry.item}
426
+ {@const time = timeLabel(entry, dayKey)}
427
+ {@const note = spanNote(entry)}
428
+ {#if item.href}
429
+ <a
430
+ class="cv-row"
431
+ data-tone={toneFor(item)}
432
+ style={itemStyle(item)}
433
+ href={item.href}
434
+ onclick={() => activateItem(item)}
435
+ >
436
+ {#if itemContent}
437
+ {@render itemContent(item, { time })}
438
+ {:else}
439
+ <span class="cv-row-time">{time}</span>
440
+ <span class="cv-row-main">
441
+ <span class="cv-row-title">{item.title}</span>
442
+ {#if item.label || note}
443
+ <span class="cv-row-meta">
444
+ {#if item.label}<span class="cv-row-label">{item.label}</span>{/if}
445
+ {#if note}<span>{note}</span>{/if}
446
+ </span>
447
+ {/if}
448
+ </span>
449
+ {/if}
450
+ </a>
451
+ {:else}
452
+ <button
453
+ type="button"
454
+ class="cv-row"
455
+ data-tone={toneFor(item)}
456
+ style={itemStyle(item)}
457
+ onclick={() => activateItem(item)}
458
+ >
459
+ {#if itemContent}
460
+ {@render itemContent(item, { time })}
461
+ {:else}
462
+ <span class="cv-row-time">{time}</span>
463
+ <span class="cv-row-main">
464
+ <span class="cv-row-title">{item.title}</span>
465
+ {#if item.label || note}
466
+ <span class="cv-row-meta">
467
+ {#if item.label}<span class="cv-row-label">{item.label}</span>{/if}
468
+ {#if note}<span>{note}</span>{/if}
469
+ </span>
470
+ {/if}
471
+ </span>
472
+ {/if}
473
+ </button>
474
+ {/if}
475
+ {/snippet}
476
+
477
+ <div
478
+ bind:this={root}
479
+ class="cv {className}"
480
+ class:cv--agenda={view === 'agenda'}
481
+ data-view={view}
482
+ >
483
+ <header class="cv-header">
484
+ <svelte:element
485
+ this={`h${headingLevel}`}
486
+ class="cv-title"
487
+ id="cv-title-{uid}"
488
+ aria-live="polite"
489
+ >
490
+ {monthTitle}
491
+ </svelte:element>
492
+ <div class="cv-nav">
493
+ <button
494
+ type="button"
495
+ class="cv-icon-btn"
496
+ aria-label={t(M['ui.calendar.previous_month'])}
497
+ onclick={() => navigate(shiftMonth(current, -1))}
498
+ >
499
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none" aria-hidden="true">
500
+ <path d="M12 15L7 10L12 5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
501
+ </svg>
502
+ </button>
503
+ <button type="button" class="cv-today-btn" onclick={goToday}>
504
+ {t(M['ui.calendar.today'])}
505
+ </button>
506
+ <button
507
+ type="button"
508
+ class="cv-icon-btn"
509
+ aria-label={t(M['ui.calendar.next_month'])}
510
+ onclick={() => navigate(shiftMonth(current, 1))}
511
+ >
512
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none" aria-hidden="true">
513
+ <path d="M8 5L13 10L8 15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
514
+ </svg>
515
+ </button>
516
+ </div>
517
+ </header>
518
+
519
+ {#if view === 'month'}
520
+ <div
521
+ class="cv-grid"
522
+ role="grid"
523
+ aria-labelledby="cv-title-{uid}"
524
+ tabindex="-1"
525
+ onkeydown={onGridKeydown}
526
+ >
527
+ <div class="cv-weekdays" role="row">
528
+ {#each weekdays as weekday (weekday.long)}
529
+ <div class="cv-weekday" role="columnheader">
530
+ <span aria-hidden="true">{weekday.short}</span>
531
+ <span class="cv-sr">{weekday.long}</span>
532
+ </div>
533
+ {/each}
534
+ </div>
535
+ {#each weeks as week (week.days[0].key)}
536
+ <div
537
+ class="cv-week"
538
+ role="row"
539
+ style="grid-template-rows: var(--cv-daynum-h) {week.lanes > 0
540
+ ? `repeat(${week.lanes}, var(--cv-band-row))`
541
+ : ''} auto;"
542
+ >
543
+ {#each week.days as day, column (day.key)}
544
+ {@const href = dayHref?.(day.key)}
545
+ <div
546
+ class="cv-day"
547
+ class:cv-day--out={!day.inMonth}
548
+ class:cv-day--today={day.key === today}
549
+ class:cv-day--selected={day.key === selected}
550
+ role="gridcell"
551
+ aria-selected={day.key === selected}
552
+ style="grid-column: {column + 1};"
553
+ >
554
+ {#if href}
555
+ <a
556
+ class="cv-daynum"
557
+ {href}
558
+ data-cv-day={day.key}
559
+ tabindex={day.key === focusKey ? 0 : -1}
560
+ aria-label={dayLabel(day.key, day.all.length)}
561
+ aria-current={day.key === today ? 'date' : undefined}
562
+ onclick={() => {
563
+ focusKey = day.key;
564
+ onSelectDate?.(day.key);
565
+ }}
566
+ >
567
+ {day.day}
568
+ </a>
569
+ {:else}
570
+ <button
571
+ type="button"
572
+ class="cv-daynum"
573
+ data-cv-day={day.key}
574
+ tabindex={day.key === focusKey ? 0 : -1}
575
+ aria-label={dayLabel(day.key, day.all.length)}
576
+ aria-current={day.key === today ? 'date' : undefined}
577
+ aria-pressed={day.key === selected}
578
+ onclick={() => selectDay(day.key)}
579
+ >
580
+ {day.day}
581
+ </button>
582
+ {/if}
583
+ <div
584
+ class="cv-day-body"
585
+ style="padding-top: calc({week.lanes} * var(--cv-band-row));"
586
+ aria-hidden="true"
587
+ >
588
+ {#each day.visible as entry (entry.item.id)}
589
+ {#if entry.item.href}
590
+ <a
591
+ class="cv-chip"
592
+ data-tone={toneFor(entry.item)}
593
+ style={itemStyle(entry.item)}
594
+ href={entry.item.href}
595
+ tabindex="-1"
596
+ title={entry.item.title}
597
+ onclick={() => activateItem(entry.item)}
598
+ >
599
+ <span class="cv-chip-time">{timeFormat.format(entry.startMs)}</span>
600
+ <span class="cv-chip-title">{entry.item.title}</span>
601
+ </a>
602
+ {:else}
603
+ <button
604
+ type="button"
605
+ class="cv-chip"
606
+ data-tone={toneFor(entry.item)}
607
+ style={itemStyle(entry.item)}
608
+ tabindex="-1"
609
+ title={entry.item.title}
610
+ onclick={() => activateItem(entry.item)}
611
+ >
612
+ <span class="cv-chip-time">{timeFormat.format(entry.startMs)}</span>
613
+ <span class="cv-chip-title">{entry.item.title}</span>
614
+ </button>
615
+ {/if}
616
+ {/each}
617
+ {#if day.hiddenCount > 0}
618
+ {#if href}
619
+ <a class="cv-more" {href} tabindex="-1">
620
+ <span class="cv-more-long">{t(M['ui.calendar.more'], { count: day.hiddenCount })}</span>
621
+ <span class="cv-more-short">{t(M['ui.calendar.more_short'], { count: day.hiddenCount })}</span>
622
+ </a>
623
+ {:else}
624
+ <button
625
+ type="button"
626
+ class="cv-more"
627
+ tabindex="-1"
628
+ onclick={() => {
629
+ selectDay(day.key);
630
+ void focusDay(day.key);
631
+ }}
632
+ >
633
+ <span class="cv-more-long">{t(M['ui.calendar.more'], { count: day.hiddenCount })}</span>
634
+ <span class="cv-more-short">{t(M['ui.calendar.more_short'], { count: day.hiddenCount })}</span>
635
+ </button>
636
+ {/if}
637
+ {/if}
638
+ </div>
639
+ </div>
640
+ {/each}
641
+ {#each week.bands as band (band.entry.item.id)}
642
+ {@const item = band.entry.item}
643
+ {#if item.href}
644
+ <a
645
+ class="cv-band"
646
+ class:cv-band--before={band.continuesBefore}
647
+ class:cv-band--after={band.continuesAfter}
648
+ data-tone={toneFor(item)}
649
+ style="{itemStyle(item)} grid-column: {band.column + 1} / span {band.span}; grid-row: {band.lane + 2};"
650
+ href={item.href}
651
+ tabindex="-1"
652
+ aria-hidden="true"
653
+ title={item.title}
654
+ onclick={() => activateItem(item)}
655
+ >
656
+ {item.title}
657
+ </a>
658
+ {:else}
659
+ <button
660
+ type="button"
661
+ class="cv-band"
662
+ class:cv-band--before={band.continuesBefore}
663
+ class:cv-band--after={band.continuesAfter}
664
+ data-tone={toneFor(item)}
665
+ style="{itemStyle(item)} grid-column: {band.column + 1} / span {band.span}; grid-row: {band.lane + 2};"
666
+ tabindex="-1"
667
+ aria-hidden="true"
668
+ title={item.title}
669
+ onclick={() => activateItem(item)}
670
+ >
671
+ {item.title}
672
+ </button>
673
+ {/if}
674
+ {/each}
675
+ </div>
676
+ {/each}
677
+ </div>
678
+
679
+ {#if selected && !dayHref && sameMonth(selected, current)}
680
+ <section class="cv-day-panel" aria-labelledby="cv-day-{uid}">
681
+ <div class="cv-day-panel-head">
682
+ <h3 class="cv-day-title" id="cv-day-{uid}">{longDate(selected)}</h3>
683
+ <button
684
+ type="button"
685
+ class="cv-icon-btn"
686
+ aria-label={t(M['ui.calendar.close_day'], { date: longDate(selected) })}
687
+ onclick={closeDay}
688
+ >
689
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none" aria-hidden="true">
690
+ <path d="M5 5L15 15M15 5L5 15" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
691
+ </svg>
692
+ </button>
693
+ </div>
694
+ {#if selectedEntries.length === 0}
695
+ <p class="cv-empty">{t(M['ui.calendar.nothing_day'])}</p>
696
+ {:else}
697
+ <ul class="cv-rows">
698
+ {#each selectedEntries as entry (entry.item.id)}
699
+ <li>{@render itemRow(entry, selected)}</li>
700
+ {/each}
701
+ </ul>
702
+ {/if}
703
+ </section>
704
+ {/if}
705
+ {:else}
706
+ <div
707
+ class="cv-strip"
708
+ role="toolbar"
709
+ tabindex="-1"
710
+ aria-label={t(M['ui.calendar.days_in'], { month: monthTitle })}
711
+ onkeydown={onStripKeydown}
712
+ >
713
+ {#each daysOfMonth as key (key)}
714
+ {@const count = entriesOnDay(entries, key).length}
715
+ {@const weekday = weekdays[(weekdayOfKey(key) - weekStartsOn + 7) % 7]}
716
+ <button
717
+ type="button"
718
+ class="cv-strip-day"
719
+ class:cv-strip-day--today={key === today}
720
+ data-cv-day={key}
721
+ tabindex={key === focusKey ? 0 : -1}
722
+ aria-label={dayLabel(key, count)}
723
+ aria-pressed={key === selected}
724
+ aria-current={key === today ? 'date' : undefined}
725
+ onclick={() => selectDay(key)}
726
+ >
727
+ <span class="cv-strip-weekday" aria-hidden="true">{weekday.narrow}</span>
728
+ <span class="cv-strip-num" aria-hidden="true">{parseKey(key).day}</span>
729
+ <span class="cv-strip-dot" class:cv-strip-dot--on={count > 0} aria-hidden="true"></span>
730
+ </button>
731
+ {/each}
732
+ </div>
733
+
734
+ {#if agendaDays.length === 0}
735
+ <p class="cv-empty">{t(M['ui.calendar.nothing_month'])}</p>
736
+ {:else}
737
+ <ol class="cv-agenda">
738
+ {#each agendaDays as day (day.key)}
739
+ <li
740
+ class="cv-agenda-day"
741
+ class:cv-agenda-day--today={day.key === today}
742
+ data-cv-agenda-day={day.key}
743
+ aria-labelledby="cv-agenda-{uid}-{day.key}"
744
+ >
745
+ <h3 class="cv-agenda-date" id="cv-agenda-{uid}-{day.key}">
746
+ {#if dayHref?.(day.key)}
747
+ <a href={dayHref(day.key)}>{longDate(day.key)}</a>
748
+ {:else}
749
+ {longDate(day.key)}
750
+ {/if}
751
+ </h3>
752
+ {#if day.entries.length === 0}
753
+ <p class="cv-empty">{t(M['ui.calendar.nothing_day'])}</p>
754
+ {:else}
755
+ <ul class="cv-rows">
756
+ {#each day.entries as entry (entry.item.id)}
757
+ <li>{@render itemRow(entry, day.key)}</li>
758
+ {/each}
759
+ </ul>
760
+ {/if}
761
+ </li>
762
+ {/each}
763
+ </ol>
764
+ {/if}
765
+ {/if}
766
+ </div>
767
+
768
+ <style>
769
+ .cv {
770
+ --cv-border: var(--smrt-color-outline-variant, #c4c6d0);
771
+ --cv-surface: var(--smrt-color-surface, #ffffff);
772
+ --cv-muted: var(--smrt-color-on-surface-variant, #44474e);
773
+ --cv-text: var(--smrt-color-on-surface, #1b1b1f);
774
+ --cv-daynum-h: 2.75rem;
775
+ --cv-band-row: 1.625rem;
776
+ --cv-target: 2.75rem;
777
+ color: var(--cv-text);
778
+ background: var(--cv-surface);
779
+ border: 1px solid var(--cv-border);
780
+ border-radius: var(--smrt-radius-large, 12px);
781
+ overflow: hidden;
782
+ min-width: 0;
783
+ container-type: inline-size;
784
+ }
785
+
786
+ [data-tone='primary'] { --cv-tone: var(--smrt-color-primary, #005ac1); --cv-fill: var(--smrt-color-primary-container, #d8e2ff); --cv-ink: var(--smrt-color-on-primary-container, #001a41); }
787
+ [data-tone='secondary'] { --cv-tone: var(--smrt-color-secondary, #575e71); --cv-fill: var(--smrt-color-secondary-container, #dbe2f9); --cv-ink: var(--smrt-color-on-secondary-container, #141b2c); }
788
+ [data-tone='tertiary'] { --cv-tone: var(--smrt-color-tertiary, #715573); --cv-fill: var(--smrt-color-tertiary-container, #fbd7fc); --cv-ink: var(--smrt-color-on-tertiary-container, #29132d); }
789
+ [data-tone='success'] { --cv-tone: var(--smrt-color-success, #1b6d2f); --cv-fill: var(--smrt-color-success-container, #a4f5a9); --cv-ink: var(--smrt-color-on-success-container, #002108); }
790
+ [data-tone='warning'] { --cv-tone: var(--smrt-color-warning, #7a5900); --cv-fill: var(--smrt-color-warning-container, #ffdea3); --cv-ink: var(--smrt-color-on-warning-container, #261900); }
791
+ [data-tone='error'] { --cv-tone: var(--smrt-color-error, #ba1a1a); --cv-fill: var(--smrt-color-error-container, #ffdad6); --cv-ink: var(--smrt-color-on-error-container, #410002); }
792
+ [data-tone='neutral'] { --cv-tone: var(--smrt-color-outline, #74777f); --cv-fill: var(--smrt-color-surface-container-high, #e8e7ec); --cv-ink: var(--smrt-color-on-surface, #1b1b1f); }
793
+
794
+ /* An explicit item color wins over the tone. */
795
+ [style*='--cv-item'] { --cv-tone: var(--cv-item); --cv-fill: color-mix(in srgb, var(--cv-item) 22%, var(--cv-surface)); --cv-ink: var(--cv-text); }
796
+
797
+ .cv-header {
798
+ display: flex;
799
+ align-items: center;
800
+ justify-content: space-between;
801
+ gap: var(--smrt-spacing-2, 0.5rem);
802
+ padding: var(--smrt-spacing-2, 0.5rem) var(--smrt-spacing-3, 0.75rem);
803
+ background: var(--smrt-color-surface-container-low, #f5f3f7);
804
+ border-bottom: 1px solid var(--cv-border);
805
+ }
806
+
807
+ .cv-title {
808
+ margin: 0;
809
+ overflow-wrap: anywhere;
810
+ font-size: var(--smrt-typography-title-large-size, 1.25rem);
811
+ font-weight: var(--smrt-typography-title-large-weight, 600);
812
+ line-height: 1.2;
813
+ min-width: 0;
814
+ }
815
+
816
+ .cv-nav {
817
+ display: flex;
818
+ align-items: center;
819
+ gap: var(--smrt-spacing-2, 0.5rem);
820
+ flex-shrink: 0;
821
+ }
822
+
823
+ .cv-icon-btn,
824
+ .cv-today-btn {
825
+ display: inline-flex;
826
+ align-items: center;
827
+ justify-content: center;
828
+ min-width: var(--cv-target);
829
+ min-height: var(--cv-target);
830
+ border: 1px solid transparent;
831
+ border-radius: var(--smrt-radius-medium, 8px);
832
+ background: transparent;
833
+ color: var(--cv-muted);
834
+ font: inherit;
835
+ cursor: pointer;
836
+ }
837
+
838
+ .cv-today-btn {
839
+ padding: 0 var(--smrt-spacing-3, 0.75rem);
840
+ border-color: var(--cv-border);
841
+ color: var(--smrt-color-primary, #005ac1);
842
+ font-weight: 500;
843
+ }
844
+
845
+ .cv-icon-btn:hover,
846
+ .cv-today-btn:hover {
847
+ background: var(--smrt-color-surface-container-high, #e8e7ec);
848
+ color: var(--cv-text);
849
+ }
850
+
851
+ .cv-sr {
852
+ position: absolute;
853
+ width: 1px;
854
+ height: 1px;
855
+ margin: -1px;
856
+ padding: 0;
857
+ overflow: hidden;
858
+ clip: rect(0 0 0 0);
859
+ white-space: nowrap;
860
+ border: 0;
861
+ }
862
+
863
+ button:focus-visible,
864
+ a:focus-visible {
865
+ outline: 2px solid var(--smrt-color-primary, #005ac1);
866
+ outline-offset: 2px;
867
+ }
868
+
869
+ /* ---- Month grid ---- */
870
+
871
+ .cv-grid {
872
+ outline: none;
873
+ }
874
+
875
+ .cv-weekdays,
876
+ .cv-week {
877
+ display: grid;
878
+ grid-template-columns: repeat(7, minmax(0, 1fr));
879
+ }
880
+
881
+ .cv-weekday {
882
+ padding: var(--smrt-spacing-2, 0.5rem) 0;
883
+ text-align: center;
884
+ font-size: var(--smrt-typography-label-medium-size, 0.75rem);
885
+ font-weight: 500;
886
+ color: var(--cv-muted);
887
+ border-bottom: 1px solid var(--cv-border);
888
+ }
889
+
890
+ .cv-week {
891
+ position: relative;
892
+ border-bottom: 1px solid var(--cv-border);
893
+ }
894
+
895
+ .cv-week:last-child {
896
+ border-bottom: 0;
897
+ }
898
+
899
+ .cv-day {
900
+ grid-row: 1 / -1;
901
+ display: flex;
902
+ flex-direction: column;
903
+ min-width: 0;
904
+ min-height: 6.5rem;
905
+ border-right: 1px solid var(--cv-border);
906
+ }
907
+
908
+ .cv-day:nth-child(7) {
909
+ border-right: 0;
910
+ }
911
+
912
+ .cv-day--out .cv-daynum {
913
+ color: var(--cv-muted);
914
+ opacity: 0.7;
915
+ }
916
+
917
+ .cv-day--selected {
918
+ background: var(--smrt-color-surface-container, #efedf1);
919
+ box-shadow: inset 0 0 0 2px var(--smrt-color-primary, #005ac1);
920
+ }
921
+
922
+ .cv-daynum {
923
+ display: flex;
924
+ align-items: flex-start;
925
+ justify-content: flex-end;
926
+ height: var(--cv-daynum-h);
927
+ min-height: var(--cv-target);
928
+ padding: var(--smrt-spacing-2, 0.5rem);
929
+ border: 0;
930
+ background: transparent;
931
+ color: inherit;
932
+ font: inherit;
933
+ font-size: var(--smrt-typography-body-medium-size, 0.875rem);
934
+ text-decoration: none;
935
+ cursor: pointer;
936
+ width: 100%;
937
+ }
938
+
939
+ .cv-day--today .cv-daynum {
940
+ font-weight: 700;
941
+ color: var(--smrt-color-primary, #005ac1);
942
+ }
943
+
944
+ .cv-day-body {
945
+ display: flex;
946
+ flex-direction: column;
947
+ gap: 2px;
948
+ padding: 0 var(--smrt-spacing-1, 0.25rem) var(--smrt-spacing-1, 0.25rem);
949
+ min-width: 0;
950
+ }
951
+
952
+ .cv-chip,
953
+ .cv-more {
954
+ display: flex;
955
+ gap: 0.25rem;
956
+ align-items: baseline;
957
+ min-width: 0;
958
+ padding: 1px var(--smrt-spacing-1, 0.25rem);
959
+ border: 0;
960
+ border-radius: var(--smrt-radius-small, 4px);
961
+ background: transparent;
962
+ color: inherit;
963
+ font: inherit;
964
+ font-size: var(--smrt-typography-label-medium-size, 0.75rem);
965
+ line-height: 1.4;
966
+ text-align: start;
967
+ text-decoration: none;
968
+ cursor: pointer;
969
+ white-space: nowrap;
970
+ }
971
+
972
+ .cv-chip::before {
973
+ content: '';
974
+ flex-shrink: 0;
975
+ align-self: center;
976
+ width: 0.5rem;
977
+ height: 0.5rem;
978
+ border-radius: 50%;
979
+ background: var(--cv-tone);
980
+ }
981
+
982
+ .cv-chip:hover,
983
+ .cv-more:hover {
984
+ background: var(--smrt-color-surface-container-high, #e8e7ec);
985
+ }
986
+
987
+ .cv-chip,
988
+ .cv-more {
989
+ overflow: hidden;
990
+ }
991
+
992
+ .cv-chip-time {
993
+ color: var(--cv-muted);
994
+ flex-shrink: 0;
995
+ }
996
+
997
+ .cv-more-short {
998
+ display: none;
999
+ }
1000
+
1001
+ /* Narrow grids (a phone forced into month mode): dots and titles only. */
1002
+ @container (max-width: 40rem) {
1003
+ .cv-chip-time,
1004
+ .cv-more-long {
1005
+ display: none;
1006
+ }
1007
+
1008
+ .cv-more-short {
1009
+ display: inline;
1010
+ }
1011
+
1012
+ .cv-title {
1013
+ font-size: var(--smrt-typography-title-medium-size, 1rem);
1014
+ }
1015
+ }
1016
+
1017
+ .cv-chip-title {
1018
+ overflow: hidden;
1019
+ text-overflow: ellipsis;
1020
+ }
1021
+
1022
+ .cv-more {
1023
+ color: var(--smrt-color-primary, #005ac1);
1024
+ font-weight: 500;
1025
+ }
1026
+
1027
+ .cv-band {
1028
+ z-index: 1;
1029
+ align-self: center;
1030
+ margin: 0 var(--smrt-spacing-1, 0.25rem);
1031
+ height: calc(var(--cv-band-row) - 4px);
1032
+ padding: 0 var(--smrt-spacing-2, 0.5rem);
1033
+ border: 0;
1034
+ border-radius: var(--smrt-radius-small, 4px);
1035
+ background: var(--cv-fill);
1036
+ color: var(--cv-ink);
1037
+ box-shadow: inset 3px 0 0 var(--cv-tone);
1038
+ font: inherit;
1039
+ font-size: var(--smrt-typography-label-medium-size, 0.75rem);
1040
+ font-weight: 500;
1041
+ line-height: calc(var(--cv-band-row) - 4px);
1042
+ text-align: start;
1043
+ text-decoration: none;
1044
+ white-space: nowrap;
1045
+ overflow: hidden;
1046
+ text-overflow: ellipsis;
1047
+ cursor: pointer;
1048
+ min-width: 0;
1049
+ }
1050
+
1051
+ .cv-band--before {
1052
+ margin-inline-start: 0;
1053
+ border-start-start-radius: 0;
1054
+ border-end-start-radius: 0;
1055
+ box-shadow: none;
1056
+ }
1057
+
1058
+ .cv-band--after {
1059
+ margin-inline-end: 0;
1060
+ border-start-end-radius: 0;
1061
+ border-end-end-radius: 0;
1062
+ }
1063
+
1064
+ /* ---- Day panel and agenda rows ---- */
1065
+
1066
+ .cv-day-panel {
1067
+ border-top: 1px solid var(--cv-border);
1068
+ padding: var(--smrt-spacing-2, 0.5rem) var(--smrt-spacing-3, 0.75rem) var(--smrt-spacing-3, 0.75rem);
1069
+ }
1070
+
1071
+ .cv-day-panel-head {
1072
+ display: flex;
1073
+ align-items: center;
1074
+ justify-content: space-between;
1075
+ gap: var(--smrt-spacing-2, 0.5rem);
1076
+ }
1077
+
1078
+ .cv-day-title,
1079
+ .cv-agenda-date {
1080
+ margin: 0;
1081
+ font-size: var(--smrt-typography-title-small-size, 0.9375rem);
1082
+ font-weight: 600;
1083
+ }
1084
+
1085
+ .cv-agenda-date a {
1086
+ color: inherit;
1087
+ }
1088
+
1089
+ .cv-rows {
1090
+ list-style: none;
1091
+ margin: 0;
1092
+ padding: 0;
1093
+ display: flex;
1094
+ flex-direction: column;
1095
+ gap: var(--smrt-spacing-2, 0.5rem);
1096
+ }
1097
+
1098
+ .cv-row {
1099
+ display: flex;
1100
+ align-items: center;
1101
+ gap: var(--smrt-spacing-3, 0.75rem);
1102
+ width: 100%;
1103
+ min-height: var(--cv-target);
1104
+ padding: var(--smrt-spacing-2, 0.5rem) var(--smrt-spacing-3, 0.75rem);
1105
+ border: 1px solid var(--cv-border);
1106
+ border-inline-start: 4px solid var(--cv-tone);
1107
+ border-radius: var(--smrt-radius-medium, 8px);
1108
+ background: var(--cv-surface);
1109
+ color: inherit;
1110
+ font: inherit;
1111
+ text-align: start;
1112
+ text-decoration: none;
1113
+ cursor: pointer;
1114
+ }
1115
+
1116
+ .cv-row:hover {
1117
+ background: var(--smrt-color-surface-container-low, #f5f3f7);
1118
+ }
1119
+
1120
+ .cv-row-time {
1121
+ flex: 0 0 4.5rem;
1122
+ color: var(--cv-muted);
1123
+ font-size: var(--smrt-typography-body-small-size, 0.8125rem);
1124
+ }
1125
+
1126
+ .cv-row-main {
1127
+ display: flex;
1128
+ flex-direction: column;
1129
+ min-width: 0;
1130
+ }
1131
+
1132
+ .cv-row-title {
1133
+ font-weight: 500;
1134
+ overflow-wrap: anywhere;
1135
+ }
1136
+
1137
+ .cv-row-meta {
1138
+ display: flex;
1139
+ flex-wrap: wrap;
1140
+ gap: var(--smrt-spacing-2, 0.5rem);
1141
+ color: var(--cv-muted);
1142
+ font-size: var(--smrt-typography-body-small-size, 0.8125rem);
1143
+ }
1144
+
1145
+ /* Tone is a fill with its own ink, never a text color (brand rule). */
1146
+ .cv-row-label {
1147
+ padding: 0 0.375rem;
1148
+ border-radius: var(--smrt-radius-small, 4px);
1149
+ background: var(--cv-fill);
1150
+ color: var(--cv-ink);
1151
+ font-weight: 500;
1152
+ }
1153
+
1154
+ .cv-empty {
1155
+ margin: var(--smrt-spacing-2, 0.5rem) 0;
1156
+ color: var(--cv-muted);
1157
+ }
1158
+
1159
+ /* ---- Agenda ---- */
1160
+
1161
+ .cv-strip {
1162
+ display: flex;
1163
+ gap: var(--smrt-spacing-2, 0.5rem);
1164
+ overflow-x: auto;
1165
+ overscroll-behavior-x: contain;
1166
+ scroll-snap-type: x proximity;
1167
+ padding: var(--smrt-spacing-2, 0.5rem) var(--smrt-spacing-3, 0.75rem);
1168
+ border-bottom: 1px solid var(--cv-border);
1169
+ scrollbar-width: none;
1170
+ }
1171
+
1172
+ .cv-strip-day {
1173
+ flex: 0 0 var(--cv-target);
1174
+ display: flex;
1175
+ flex-direction: column;
1176
+ align-items: center;
1177
+ gap: 2px;
1178
+ min-height: 3.5rem;
1179
+ padding: var(--smrt-spacing-1, 0.25rem) 0;
1180
+ border: 1px solid transparent;
1181
+ border-radius: var(--smrt-radius-medium, 8px);
1182
+ background: transparent;
1183
+ color: inherit;
1184
+ font: inherit;
1185
+ cursor: pointer;
1186
+ scroll-snap-align: center;
1187
+ }
1188
+
1189
+ .cv-strip-day[aria-pressed='true'] {
1190
+ background: var(--smrt-color-primary, #005ac1);
1191
+ color: var(--smrt-color-on-primary, #ffffff);
1192
+ }
1193
+
1194
+ .cv-strip-day--today:not([aria-pressed='true']) {
1195
+ border-color: var(--smrt-color-primary, #005ac1);
1196
+ }
1197
+
1198
+ .cv-strip-weekday {
1199
+ font-size: var(--smrt-typography-label-small-size, 0.6875rem);
1200
+ opacity: 0.8;
1201
+ }
1202
+
1203
+ .cv-strip-num {
1204
+ font-weight: 600;
1205
+ }
1206
+
1207
+ .cv-strip-dot {
1208
+ width: 0.375rem;
1209
+ height: 0.375rem;
1210
+ border-radius: 50%;
1211
+ }
1212
+
1213
+ .cv-strip-dot--on {
1214
+ background: currentColor;
1215
+ }
1216
+
1217
+ .cv-agenda {
1218
+ list-style: none;
1219
+ margin: 0;
1220
+ padding: var(--smrt-spacing-2, 0.5rem) var(--smrt-spacing-3, 0.75rem) var(--smrt-spacing-3, 0.75rem);
1221
+ display: flex;
1222
+ flex-direction: column;
1223
+ gap: var(--smrt-spacing-4, 1rem);
1224
+ }
1225
+
1226
+ .cv-agenda-day {
1227
+ display: flex;
1228
+ flex-direction: column;
1229
+ gap: var(--smrt-spacing-2, 0.5rem);
1230
+ scroll-margin-top: var(--smrt-spacing-4, 1rem);
1231
+ }
1232
+
1233
+ .cv-agenda-day--today .cv-agenda-date {
1234
+ color: var(--smrt-color-primary, #005ac1);
1235
+ }
1236
+
1237
+ .cv--agenda .cv-empty {
1238
+ padding: 0 var(--smrt-spacing-3, 0.75rem);
1239
+ }
1240
+
1241
+ .cv-agenda .cv-empty {
1242
+ padding: 0;
1243
+ }
1244
+
1245
+ @media (prefers-reduced-motion: reduce) {
1246
+ .cv-strip {
1247
+ scroll-behavior: auto;
1248
+ }
1249
+ }
1250
+ </style>