@kouji-ui/core 0.0.4 → 0.1.1

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 (534) hide show
  1. package/.turbo/turbo-build.log +7 -4
  2. package/CHANGELOG.md +169 -0
  3. package/ng-package.json +5 -1
  4. package/package.json +18 -6
  5. package/src/a11y/aria-describedby.ts +7 -3
  6. package/src/a11y/aria-labelledby.spec.ts +55 -0
  7. package/src/a11y/aria-labelledby.ts +38 -0
  8. package/src/a11y/focus-trap.ts +4 -2
  9. package/src/a11y/index.ts +1 -0
  10. package/src/a11y/live-region.ts +4 -2
  11. package/src/a11y/roving-tabindex.spec.ts +139 -0
  12. package/src/a11y/roving-tabindex.ts +56 -7
  13. package/src/a11y/visually-hidden.ts +4 -2
  14. package/src/accordion/accordion.context.ts +22 -0
  15. package/src/accordion/accordion.spec.ts +233 -23
  16. package/src/accordion/accordion.ts +311 -39
  17. package/src/accordion/index.ts +8 -2
  18. package/src/alert/alert.context.ts +28 -0
  19. package/src/alert/alert.spec.ts +217 -0
  20. package/src/alert/alert.ts +417 -0
  21. package/src/alert/config.ts +46 -0
  22. package/src/alert/index.ts +19 -0
  23. package/src/avatar/avatar-group.context.ts +45 -0
  24. package/src/avatar/avatar-group.spec.ts +358 -0
  25. package/src/avatar/avatar-group.ts +259 -0
  26. package/src/avatar/avatar.ts +17 -4
  27. package/src/avatar/index.ts +11 -1
  28. package/src/badge/badge.spec.ts +8 -0
  29. package/src/badge/badge.ts +24 -3
  30. package/src/breadcrumb/breadcrumb-current.ts +38 -0
  31. package/src/breadcrumb/breadcrumb-ellipsis.ts +71 -0
  32. package/src/breadcrumb/breadcrumb-item.ts +41 -0
  33. package/src/breadcrumb/breadcrumb-link.ts +44 -0
  34. package/src/breadcrumb/breadcrumb-list.ts +42 -0
  35. package/src/breadcrumb/breadcrumb-separator.ts +45 -0
  36. package/src/breadcrumb/breadcrumb.context.ts +45 -0
  37. package/src/breadcrumb/breadcrumb.spec.ts +354 -0
  38. package/src/breadcrumb/breadcrumb.ts +239 -0
  39. package/src/breadcrumb/config.ts +84 -0
  40. package/src/breadcrumb/index.ts +18 -0
  41. package/src/button/_examples/button.example.ts +71 -0
  42. package/src/button/_examples/button.finance.example.ts +83 -0
  43. package/src/button/_examples/button.retro.example.ts +91 -0
  44. package/src/button/_examples/button.sizes.example.ts +60 -0
  45. package/src/button/_examples/index.ts +6 -0
  46. package/src/button/button.spec.ts +139 -8
  47. package/src/button/button.ts +114 -16
  48. package/src/button/config.spec.ts +33 -0
  49. package/src/button/config.ts +45 -0
  50. package/src/button/index.ts +2 -1
  51. package/src/button-group/button-group.context.ts +28 -0
  52. package/src/button-group/button-group.spec.ts +99 -0
  53. package/src/button-group/button-group.ts +99 -0
  54. package/src/button-group/index.ts +2 -0
  55. package/src/calendar/calendar-day.ts +102 -0
  56. package/src/calendar/calendar-grid.ts +90 -0
  57. package/src/calendar/calendar-header.ts +65 -0
  58. package/src/calendar/calendar.context.ts +53 -0
  59. package/src/calendar/calendar.spec.ts +225 -0
  60. package/src/calendar/calendar.ts +233 -0
  61. package/src/calendar/date-utils.ts +225 -0
  62. package/src/calendar/index.ts +28 -0
  63. package/src/carousel/carousel.context.ts +81 -0
  64. package/src/carousel/carousel.spec.ts +337 -0
  65. package/src/carousel/carousel.ts +919 -0
  66. package/src/carousel/index.ts +22 -0
  67. package/src/cascade-select/cascade-select-option.ts +205 -0
  68. package/src/cascade-select/cascade-select-panel.ts +106 -0
  69. package/src/cascade-select/cascade-select-root.ts +265 -0
  70. package/src/cascade-select/cascade-select-sub-panel.ts +168 -0
  71. package/src/cascade-select/cascade-select-trigger.ts +44 -0
  72. package/src/cascade-select/cascade-select.context.ts +71 -0
  73. package/src/cascade-select/cascade-select.spec.ts +241 -0
  74. package/src/cascade-select/cascade-select.ts +5 -0
  75. package/src/cascade-select/index.ts +11 -0
  76. package/src/chart/chart.ts +6 -2
  77. package/src/chat/chat-avatar.ts +31 -0
  78. package/src/chat/chat-bubble.ts +49 -0
  79. package/src/chat/chat-footer.ts +67 -0
  80. package/src/chat/chat-header.ts +41 -0
  81. package/src/chat/chat-log.ts +43 -0
  82. package/src/chat/chat.context.ts +64 -0
  83. package/src/chat/chat.spec.ts +341 -0
  84. package/src/chat/chat.ts +191 -0
  85. package/src/chat/config.ts +53 -0
  86. package/src/chat/index.ts +21 -0
  87. package/src/checkbox/checkbox.ts +9 -2
  88. package/src/color-picker/color-picker.context.ts +74 -0
  89. package/src/color-picker/color-picker.spec.ts +241 -0
  90. package/src/color-picker/color-picker.ts +698 -0
  91. package/src/color-picker/color-picker.utils.ts +140 -0
  92. package/src/color-picker/index.ts +28 -0
  93. package/src/combobox/combobox-input.ts +143 -0
  94. package/src/combobox/combobox-listbox.ts +49 -0
  95. package/src/combobox/combobox-option.ts +43 -0
  96. package/src/combobox/combobox-root.ts +281 -0
  97. package/src/combobox/combobox.spec.ts +75 -0
  98. package/src/combobox/combobox.ts +4 -0
  99. package/src/combobox/index.ts +4 -0
  100. package/src/command-palette/command-empty.ts +36 -0
  101. package/src/command-palette/command-group.ts +21 -0
  102. package/src/command-palette/command-input.ts +70 -0
  103. package/src/command-palette/command-item.ts +60 -0
  104. package/src/command-palette/command-list.ts +25 -0
  105. package/src/command-palette/command-palette-dialog.ts +74 -0
  106. package/src/command-palette/command-palette-trigger.ts +64 -0
  107. package/src/command-palette/command-palette.filters.ts +53 -0
  108. package/src/command-palette/command-palette.spec.ts +349 -0
  109. package/src/command-palette/command-palette.ts +204 -0
  110. package/src/command-palette/command-separator.ts +18 -0
  111. package/src/command-palette/index.ts +15 -0
  112. package/src/confirm-popup/confirm-popup-action.ts +39 -0
  113. package/src/confirm-popup/confirm-popup-cancel.ts +38 -0
  114. package/src/confirm-popup/confirm-popup-content.ts +113 -0
  115. package/src/confirm-popup/confirm-popup-message.ts +36 -0
  116. package/src/confirm-popup/confirm-popup-trigger.ts +31 -0
  117. package/src/confirm-popup/confirm-popup.context.ts +41 -0
  118. package/src/confirm-popup/confirm-popup.spec.ts +337 -0
  119. package/src/confirm-popup/confirm-popup.ts +109 -0
  120. package/src/confirm-popup/index.ts +12 -0
  121. package/src/date-picker/date-picker-calendar.ts +65 -0
  122. package/src/date-picker/date-picker-trigger.ts +215 -0
  123. package/src/date-picker/date-picker.context.ts +50 -0
  124. package/src/date-picker/date-picker.spec.ts +117 -0
  125. package/src/date-picker/date-picker.ts +127 -0
  126. package/src/date-picker/index.ts +7 -0
  127. package/src/dialog/_examples/dialog.confirm.example.ts +145 -0
  128. package/src/dialog/_examples/dialog.example.ts +106 -0
  129. package/src/dialog/_examples/dialog.finance.example.ts +121 -0
  130. package/src/dialog/_examples/dialog.retro.example.ts +126 -0
  131. package/src/dialog/_examples/index.ts +6 -0
  132. package/src/dialog/dialog.ref.ts +42 -0
  133. package/src/dialog/dialog.service.ts +57 -167
  134. package/src/dialog/dialog.spec.ts +35 -120
  135. package/src/dialog/dialog.ts +17 -192
  136. package/src/dialog/index.ts +3 -9
  137. package/src/divider/divider.spec.ts +223 -0
  138. package/src/divider/divider.ts +137 -0
  139. package/src/divider/index.ts +5 -0
  140. package/src/drawer/drawer.ref.ts +63 -0
  141. package/src/drawer/drawer.service.ts +106 -0
  142. package/src/drawer/drawer.spec.ts +117 -0
  143. package/src/drawer/drawer.ts +122 -0
  144. package/src/drawer/index.ts +10 -0
  145. package/src/dropdown-menu/dropdown-menu-content.ts +258 -0
  146. package/src/dropdown-menu/dropdown-menu-group.ts +31 -0
  147. package/src/dropdown-menu/dropdown-menu-item.ts +51 -0
  148. package/src/dropdown-menu/dropdown-menu-label.ts +41 -0
  149. package/src/dropdown-menu/dropdown-menu-separator.ts +27 -0
  150. package/src/dropdown-menu/dropdown-menu-trigger.ts +140 -0
  151. package/src/dropdown-menu/dropdown-menu.spec.ts +265 -0
  152. package/src/dropdown-menu/dropdown-menu.ts +60 -0
  153. package/src/dropdown-menu/index.ts +15 -0
  154. package/src/example-components.ts +39 -30
  155. package/src/field/field-error.ts +49 -0
  156. package/src/field/field-group.ts +39 -0
  157. package/src/field/field-help.ts +47 -0
  158. package/src/field/field-label.ts +30 -0
  159. package/src/field/field.context.ts +28 -0
  160. package/src/field/field.spec.ts +179 -0
  161. package/src/field/field.ts +121 -0
  162. package/src/field/index.ts +6 -0
  163. package/src/file-upload/file-upload.context.ts +89 -0
  164. package/src/file-upload/file-upload.spec.ts +290 -0
  165. package/src/file-upload/file-upload.ts +639 -0
  166. package/src/file-upload/file-upload.types.ts +59 -0
  167. package/src/file-upload/index.ts +23 -0
  168. package/src/form/form-error-summary.ts +87 -0
  169. package/src/form/form-field.context.ts +3 -0
  170. package/src/form/form-field.spec.ts +25 -0
  171. package/src/form/form-field.ts +52 -0
  172. package/src/form/form.context.ts +52 -2
  173. package/src/form/form.spec.ts +155 -18
  174. package/src/form/form.ts +315 -35
  175. package/src/form/index.ts +11 -2
  176. package/src/icon/E2E.md +14 -0
  177. package/src/icon/icon.css +40 -0
  178. package/src/icon/icon.directive.spec.ts +198 -0
  179. package/src/icon/icon.directive.ts +113 -0
  180. package/src/icon/icon.mode.spec.ts +29 -0
  181. package/src/icon/icon.mode.ts +23 -0
  182. package/src/icon/icon.providers.spec.ts +63 -0
  183. package/src/icon/icon.providers.ts +47 -0
  184. package/src/icon/icon.resolver.spec.ts +96 -0
  185. package/src/icon/icon.resolver.ts +49 -0
  186. package/src/icon/icon.tokens.ts +88 -0
  187. package/src/icon/icon.types.ts +48 -0
  188. package/src/icon/index.ts +21 -0
  189. package/src/input/input.ts +12 -3
  190. package/src/input-group/index.ts +3 -0
  191. package/src/input-group/input-group-addon.ts +92 -0
  192. package/src/input-group/input-group.context.ts +27 -0
  193. package/src/input-group/input-group.spec.ts +186 -0
  194. package/src/input-group/input-group.ts +136 -0
  195. package/src/input-mask/index.ts +11 -0
  196. package/src/input-mask/input-mask.compile.ts +69 -0
  197. package/src/input-mask/input-mask.engine.ts +376 -0
  198. package/src/input-mask/input-mask.presets.ts +24 -0
  199. package/src/input-mask/input-mask.spec.ts +304 -0
  200. package/src/input-mask/input-mask.tokens.ts +49 -0
  201. package/src/input-mask/input-mask.ts +280 -0
  202. package/src/input-otp/index.ts +2 -0
  203. package/src/input-otp/input-otp.context.ts +41 -0
  204. package/src/input-otp/input-otp.spec.ts +308 -0
  205. package/src/input-otp/input-otp.ts +470 -0
  206. package/src/kbd/index.ts +1 -0
  207. package/src/kbd/kbd.spec.ts +127 -0
  208. package/src/kbd/kbd.ts +132 -0
  209. package/src/link/config.ts +44 -0
  210. package/src/link/index.ts +2 -0
  211. package/src/link/link.spec.ts +246 -0
  212. package/src/link/link.ts +226 -0
  213. package/src/list/index.ts +11 -0
  214. package/src/list/list.spec.ts +237 -0
  215. package/src/list/list.ts +277 -0
  216. package/src/menubar/index.ts +7 -0
  217. package/src/menubar/menubar-item.ts +204 -0
  218. package/src/menubar/menubar.context.ts +43 -0
  219. package/src/menubar/menubar.spec.ts +433 -0
  220. package/src/menubar/menubar.ts +309 -0
  221. package/src/number-input/index.ts +4 -0
  222. package/src/number-input/number-input-group.ts +64 -0
  223. package/src/number-input/number-input.context.ts +31 -0
  224. package/src/number-input/number-input.format.ts +148 -0
  225. package/src/number-input/number-input.spec.ts +170 -0
  226. package/src/number-input/number-input.ts +457 -0
  227. package/src/number-input/number-stepper.ts +193 -0
  228. package/src/overlay-badge/index.ts +7 -0
  229. package/src/overlay-badge/overlay-badge-content.ts +73 -0
  230. package/src/overlay-badge/overlay-badge.context.ts +37 -0
  231. package/src/overlay-badge/overlay-badge.spec.ts +220 -0
  232. package/src/overlay-badge/overlay-badge.ts +128 -0
  233. package/src/pagination/config.ts +82 -0
  234. package/src/pagination/index.ts +19 -0
  235. package/src/pagination/page-tokens.ts +78 -0
  236. package/src/pagination/pagination-ellipsis.ts +69 -0
  237. package/src/pagination/pagination-first.ts +84 -0
  238. package/src/pagination/pagination-info.ts +70 -0
  239. package/src/pagination/pagination-item.ts +81 -0
  240. package/src/pagination/pagination-last.ts +85 -0
  241. package/src/pagination/pagination-next.ts +86 -0
  242. package/src/pagination/pagination-previous.ts +90 -0
  243. package/src/pagination/pagination.context.ts +52 -0
  244. package/src/pagination/pagination.spec.ts +388 -0
  245. package/src/pagination/pagination.ts +288 -0
  246. package/src/password-input/index.ts +15 -0
  247. package/src/password-input/password-input.context.ts +65 -0
  248. package/src/password-input/password-input.scorer.ts +34 -0
  249. package/src/password-input/password-input.spec.ts +274 -0
  250. package/src/password-input/password-input.ts +488 -0
  251. package/src/popover/_examples/index.ts +5 -0
  252. package/src/popover/_examples/popover.example.ts +92 -0
  253. package/src/popover/_examples/popover.finance.example.ts +106 -0
  254. package/src/popover/_examples/popover.retro.example.ts +103 -0
  255. package/src/popover/index.ts +5 -12
  256. package/src/popover/popover-arrow.ts +30 -0
  257. package/src/popover/popover-close.ts +36 -0
  258. package/src/popover/popover-content.ts +41 -0
  259. package/src/popover/popover-title.ts +39 -0
  260. package/src/popover/popover-trigger.ts +40 -0
  261. package/src/popover/popover.spec.ts +51 -44
  262. package/src/popover/popover.ts +5 -124
  263. package/src/presets/bind-presets.spec.ts +50 -0
  264. package/src/presets/bind-presets.ts +43 -0
  265. package/src/presets/index.ts +3 -0
  266. package/src/presets/size.spec.ts +67 -0
  267. package/src/presets/size.ts +59 -0
  268. package/src/presets/variant.spec.ts +72 -0
  269. package/src/presets/variant.ts +62 -0
  270. package/src/primitives/directionality/directionality.spec.ts +112 -0
  271. package/src/primitives/directionality/directionality.ts +89 -0
  272. package/src/primitives/directionality/index.ts +1 -0
  273. package/src/primitives/forms/form-control.spec.ts +11 -3
  274. package/src/primitives/forms/form-control.ts +6 -2
  275. package/src/primitives/index.ts +3 -0
  276. package/src/primitives/interaction/disabled.ts +12 -5
  277. package/src/primitives/interaction/focus-ring.ts +4 -2
  278. package/src/primitives/list/filterable-list.spec.ts +114 -0
  279. package/src/primitives/list/filterable-list.ts +126 -0
  280. package/src/primitives/list/group.spec.ts +99 -0
  281. package/src/primitives/list/group.ts +139 -0
  282. package/src/primitives/list/index.ts +8 -0
  283. package/src/primitives/list/inject-helpers.ts +8 -0
  284. package/src/primitives/list/item.spec.ts +173 -0
  285. package/src/primitives/list/item.ts +222 -0
  286. package/src/primitives/list/navigator.spec.ts +286 -0
  287. package/src/primitives/list/navigator.ts +238 -0
  288. package/src/primitives/list/selection.spec.ts +214 -0
  289. package/src/primitives/list/selection.ts +339 -0
  290. package/src/primitives/list/tokens.ts +106 -0
  291. package/src/primitives/list/type-ahead.spec.ts +58 -0
  292. package/src/primitives/list/type-ahead.ts +46 -0
  293. package/src/primitives/overlay/backdrop.spec.ts +54 -0
  294. package/src/primitives/overlay/backdrop.ts +31 -0
  295. package/src/primitives/overlay/builder.spec.ts +38 -0
  296. package/src/primitives/overlay/builder.ts +151 -0
  297. package/src/primitives/overlay/container.ts +42 -0
  298. package/src/primitives/overlay/context.ts +14 -0
  299. package/src/primitives/overlay/controller.spec.ts +86 -0
  300. package/src/primitives/overlay/controller.ts +190 -0
  301. package/src/primitives/overlay/id.spec.ts +24 -0
  302. package/src/primitives/overlay/id.ts +17 -0
  303. package/src/primitives/overlay/index.ts +18 -1
  304. package/src/primitives/overlay/overlay.css +42 -0
  305. package/src/primitives/overlay/panel.spec.ts +52 -0
  306. package/src/primitives/overlay/panel.ts +102 -0
  307. package/src/primitives/overlay/stack.spec.ts +55 -0
  308. package/src/primitives/overlay/stack.ts +113 -0
  309. package/src/primitives/overlay/strategies/backdrop/blurred.spec.ts +11 -0
  310. package/src/primitives/overlay/strategies/backdrop/blurred.ts +5 -0
  311. package/src/primitives/overlay/strategies/backdrop/none.spec.ts +10 -0
  312. package/src/primitives/overlay/strategies/backdrop/none.ts +9 -0
  313. package/src/primitives/overlay/strategies/backdrop/solid.spec.ts +18 -0
  314. package/src/primitives/overlay/strategies/backdrop/solid.ts +23 -0
  315. package/src/primitives/overlay/strategies/focus-trap/inert-based.spec.ts +28 -0
  316. package/src/primitives/overlay/strategies/focus-trap/inert-based.ts +38 -0
  317. package/src/primitives/overlay/strategies/focus-trap/no-trap.spec.ts +9 -0
  318. package/src/primitives/overlay/strategies/focus-trap/no-trap.ts +8 -0
  319. package/src/primitives/overlay/strategies/focus-trap/tab-cycle.spec.ts +49 -0
  320. package/src/primitives/overlay/strategies/focus-trap/tab-cycle.ts +85 -0
  321. package/src/primitives/overlay/strategies/index.ts +34 -0
  322. package/src/primitives/overlay/strategies/live-announcer/_announce.ts +35 -0
  323. package/src/primitives/overlay/strategies/live-announcer/assertive.spec.ts +21 -0
  324. package/src/primitives/overlay/strategies/live-announcer/assertive.ts +11 -0
  325. package/src/primitives/overlay/strategies/live-announcer/polite.spec.ts +21 -0
  326. package/src/primitives/overlay/strategies/live-announcer/polite.ts +11 -0
  327. package/src/primitives/overlay/strategies/live-announcer/silent.spec.ts +12 -0
  328. package/src/primitives/overlay/strategies/live-announcer/silent.ts +5 -0
  329. package/src/primitives/overlay/strategies/mount/body-portal.spec.ts +40 -0
  330. package/src/primitives/overlay/strategies/mount/body-portal.ts +91 -0
  331. package/src/primitives/overlay/strategies/mount/in-container.spec.ts +43 -0
  332. package/src/primitives/overlay/strategies/mount/in-container.ts +39 -0
  333. package/src/primitives/overlay/strategies/mount/in-place.spec.ts +40 -0
  334. package/src/primitives/overlay/strategies/mount/in-place.ts +20 -0
  335. package/src/primitives/overlay/strategies/position/anchored-to.spec.ts +56 -0
  336. package/src/primitives/overlay/strategies/position/anchored-to.ts +248 -0
  337. package/src/primitives/overlay/strategies/position/corner.spec.ts +36 -0
  338. package/src/primitives/overlay/strategies/position/corner.ts +33 -0
  339. package/src/primitives/overlay/strategies/position/edge-sheet.spec.ts +32 -0
  340. package/src/primitives/overlay/strategies/position/edge-sheet.ts +36 -0
  341. package/src/primitives/overlay/strategies/position/in-place-sibling.spec.ts +25 -0
  342. package/src/primitives/overlay/strategies/position/in-place-sibling.ts +13 -0
  343. package/src/primitives/overlay/strategies/position/point-at.spec.ts +40 -0
  344. package/src/primitives/overlay/strategies/position/point-at.ts +34 -0
  345. package/src/primitives/overlay/strategies/position/viewport-centered.spec.ts +31 -0
  346. package/src/primitives/overlay/strategies/position/viewport-centered.ts +31 -0
  347. package/src/primitives/overlay/strategies/scroll-lock/css-clip.spec.ts +15 -0
  348. package/src/primitives/overlay/strategies/scroll-lock/css-clip.ts +39 -0
  349. package/src/primitives/overlay/strategies/scroll-lock/html-overflow.spec.ts +21 -0
  350. package/src/primitives/overlay/strategies/scroll-lock/html-overflow.ts +44 -0
  351. package/src/primitives/overlay/strategies/scroll-lock/none.spec.ts +9 -0
  352. package/src/primitives/overlay/strategies/scroll-lock/none.ts +5 -0
  353. package/src/primitives/overlay/strategies/trigger-event/on-click.spec.ts +32 -0
  354. package/src/primitives/overlay/strategies/trigger-event/on-click.ts +28 -0
  355. package/src/primitives/overlay/strategies/trigger-event/on-context-menu.spec.ts +45 -0
  356. package/src/primitives/overlay/strategies/trigger-event/on-context-menu.ts +70 -0
  357. package/src/primitives/overlay/strategies/trigger-event/on-focus-or-input.spec.ts +35 -0
  358. package/src/primitives/overlay/strategies/trigger-event/on-focus-or-input.ts +34 -0
  359. package/src/primitives/overlay/strategies/trigger-event/on-focus.spec.ts +64 -0
  360. package/src/primitives/overlay/strategies/trigger-event/on-focus.ts +37 -0
  361. package/src/primitives/overlay/strategies/trigger-event/on-hotkey.spec.ts +37 -0
  362. package/src/primitives/overlay/strategies/trigger-event/on-hotkey.ts +59 -0
  363. package/src/primitives/overlay/strategies/trigger-event/on-hover.spec.ts +34 -0
  364. package/src/primitives/overlay/strategies/trigger-event/on-hover.ts +86 -0
  365. package/src/primitives/overlay/strategies/trigger-event/programmatic.spec.ts +16 -0
  366. package/src/primitives/overlay/strategies/trigger-event/programmatic.ts +12 -0
  367. package/src/primitives/overlay/tokens.ts +54 -0
  368. package/src/primitives/overlay/trigger.spec.ts +103 -0
  369. package/src/primitives/overlay/trigger.ts +75 -0
  370. package/src/primitives/overlay/types.ts +24 -0
  371. package/src/primitives/overlay/wrapper.ts +50 -0
  372. package/src/primitives/signals/deep-signal.ts +109 -0
  373. package/src/primitives/signals/index.ts +1 -0
  374. package/src/progress-bar/config.ts +48 -0
  375. package/src/progress-bar/index.ts +12 -0
  376. package/src/progress-bar/progress-bar-fill.ts +37 -0
  377. package/src/progress-bar/progress-bar.context.ts +34 -0
  378. package/src/progress-bar/progress-bar.spec.ts +423 -0
  379. package/src/progress-bar/progress-bar.ts +217 -0
  380. package/src/public-api.ts +58 -9
  381. package/src/radio/radio.ts +14 -3
  382. package/src/select/index.ts +4 -2
  383. package/src/select/select-content.ts +69 -0
  384. package/src/select/select-option.ts +35 -0
  385. package/src/select/select-root.ts +129 -0
  386. package/src/select/select-trigger.ts +93 -0
  387. package/src/select/select.spec.ts +133 -140
  388. package/src/select/select.ts +4 -250
  389. package/src/skeleton/index.ts +5 -0
  390. package/src/skeleton/skeleton.spec.ts +115 -0
  391. package/src/skeleton/skeleton.ts +56 -0
  392. package/src/slider/index.ts +10 -0
  393. package/src/slider/slider-range.ts +49 -0
  394. package/src/slider/slider-thumb.ts +335 -0
  395. package/src/slider/slider-track.ts +70 -0
  396. package/src/slider/slider.context.ts +79 -0
  397. package/src/slider/slider.geometry.ts +73 -0
  398. package/src/slider/slider.spec.ts +216 -0
  399. package/src/slider/slider.ts +443 -0
  400. package/src/speed-dial/index.ts +10 -0
  401. package/src/speed-dial/speed-dial-action.ts +40 -0
  402. package/src/speed-dial/speed-dial-actions.ts +31 -0
  403. package/src/speed-dial/speed-dial-trigger.ts +55 -0
  404. package/src/speed-dial/speed-dial.context.ts +36 -0
  405. package/src/speed-dial/speed-dial.spec.ts +143 -0
  406. package/src/speed-dial/speed-dial.ts +127 -0
  407. package/src/spinner/config.ts +48 -0
  408. package/src/spinner/index.ts +8 -0
  409. package/src/spinner/spinner.spec.ts +262 -0
  410. package/src/spinner/spinner.ts +129 -0
  411. package/src/stepper/index.ts +15 -0
  412. package/src/stepper/stepper.context.ts +63 -0
  413. package/src/stepper/stepper.spec.ts +403 -0
  414. package/src/stepper/stepper.ts +544 -0
  415. package/src/styles.css +17 -0
  416. package/src/table/column-helpers.spec.ts +74 -0
  417. package/src/table/column-helpers.ts +68 -0
  418. package/src/table/filter-fns.ts +230 -0
  419. package/src/table/filter-models.spec.ts +240 -0
  420. package/src/table/filter-models.ts +124 -0
  421. package/src/table/filter-params.ts +90 -0
  422. package/src/table/grid-api-impl.ts +321 -0
  423. package/src/table/grid-api.spec.ts +178 -0
  424. package/src/table/grid-api.ts +285 -0
  425. package/src/table/index.ts +73 -1
  426. package/src/table/table-cell.spec.ts +43 -0
  427. package/src/table/table-cell.ts +38 -0
  428. package/src/table/table-filter-outlet.ts +120 -0
  429. package/src/table/table-header.spec.ts +54 -0
  430. package/src/table/table-header.ts +63 -0
  431. package/src/table/table-keyboard.spec.ts +68 -0
  432. package/src/table/table-keyboard.ts +107 -0
  433. package/src/table/table-resource.spec.ts +37 -0
  434. package/src/table/table-resource.ts +36 -0
  435. package/src/table/table-row.spec.ts +54 -0
  436. package/src/table/table-row.ts +31 -0
  437. package/src/table/table-storage.spec.ts +67 -0
  438. package/src/table/table-storage.ts +76 -0
  439. package/src/table/table.spec.ts +101 -42
  440. package/src/table/table.ts +228 -73
  441. package/src/table/table.types.ts +83 -0
  442. package/src/tabs/index.ts +7 -2
  443. package/src/tabs/tabs.context.ts +27 -4
  444. package/src/tabs/tabs.spec.ts +336 -24
  445. package/src/tabs/tabs.ts +313 -79
  446. package/src/tag/config.ts +53 -0
  447. package/src/tag/index.ts +7 -0
  448. package/src/tag/tag-list.ts +76 -0
  449. package/src/tag/tag-remove.ts +61 -0
  450. package/src/tag/tag.context.ts +36 -0
  451. package/src/tag/tag.spec.ts +240 -0
  452. package/src/tag/tag.ts +203 -0
  453. package/src/textarea/config.ts +42 -0
  454. package/src/textarea/index.ts +8 -0
  455. package/src/textarea/textarea.spec.ts +176 -0
  456. package/src/textarea/textarea.ts +235 -0
  457. package/src/time-picker/index.ts +15 -0
  458. package/src/time-picker/time-picker-meridiem.ts +123 -0
  459. package/src/time-picker/time-picker-segment.ts +318 -0
  460. package/src/time-picker/time-picker.context.ts +38 -0
  461. package/src/time-picker/time-picker.format.ts +181 -0
  462. package/src/time-picker/time-picker.spec.ts +189 -0
  463. package/src/time-picker/time-picker.ts +274 -0
  464. package/src/toast/_examples/index.ts +5 -0
  465. package/src/toast/_examples/toast.example.ts +282 -0
  466. package/src/toast/_examples/toast.finance.example.ts +180 -0
  467. package/src/toast/_examples/toast.retro.example.ts +161 -0
  468. package/src/toast/index.ts +8 -1
  469. package/src/toast/toast.ref.ts +26 -0
  470. package/src/toast/toast.service.ts +227 -41
  471. package/src/toast/toast.spec.ts +166 -14
  472. package/src/toast/toast.strategy.ts +10 -0
  473. package/src/toast/toast.ts +177 -21
  474. package/src/toggle/toggle.ts +10 -2
  475. package/src/tooltip/_examples/index.ts +6 -0
  476. package/src/tooltip/_examples/tooltip.example.ts +82 -0
  477. package/src/tooltip/_examples/tooltip.finance.example.ts +97 -0
  478. package/src/tooltip/_examples/tooltip.placements.example.ts +83 -0
  479. package/src/tooltip/_examples/tooltip.retro.example.ts +107 -0
  480. package/src/tooltip/index.ts +4 -5
  481. package/src/tooltip/tooltip-arrow.ts +31 -0
  482. package/src/tooltip/tooltip-content.ts +38 -0
  483. package/src/tooltip/tooltip-group.ts +32 -0
  484. package/src/tooltip/tooltip-trigger.ts +47 -0
  485. package/src/tooltip/tooltip.spec.ts +38 -102
  486. package/src/tooltip/tooltip.ts +5 -129
  487. package/src/tree-select/index.ts +12 -0
  488. package/src/tree-select/tree-select-content.ts +199 -0
  489. package/src/tree-select/tree-select-node.ts +132 -0
  490. package/src/tree-select/tree-select-root.ts +272 -0
  491. package/src/tree-select/tree-select-trigger.ts +90 -0
  492. package/src/tree-select/tree-select.context.ts +63 -0
  493. package/src/tree-select/tree-select.spec.ts +433 -0
  494. package/src/tree-select/tree-select.ts +2 -0
  495. package/src/typography/blockquote.ts +57 -0
  496. package/src/typography/code.ts +53 -0
  497. package/src/typography/index.ts +22 -0
  498. package/src/typography/lead.ts +49 -0
  499. package/src/typography/muted.ts +29 -0
  500. package/src/typography/prose.css +312 -0
  501. package/src/typography/truncate.ts +99 -0
  502. package/src/typography/typography.spec.ts +203 -0
  503. package/tsconfig.lib.json +9 -1
  504. package/tsconfig.lib.prod.json +7 -1
  505. package/tsconfig.spec.json +9 -1
  506. package/vite.config.ts +8 -0
  507. package/src/button/button.example.ts +0 -30
  508. package/src/button/button.finance.example.ts +0 -37
  509. package/src/button/button.retro.example.ts +0 -47
  510. package/src/button/button.sizes.example.ts +0 -25
  511. package/src/dialog/dialog.confirm.example.ts +0 -57
  512. package/src/dialog/dialog.context.ts +0 -11
  513. package/src/dialog/dialog.example.ts +0 -40
  514. package/src/dialog/dialog.finance.example.ts +0 -47
  515. package/src/dialog/dialog.retro.example.ts +0 -46
  516. package/src/menu/index.ts +0 -2
  517. package/src/menu/menu.context.ts +0 -10
  518. package/src/menu/menu.example.ts +0 -39
  519. package/src/menu/menu.spec.ts +0 -168
  520. package/src/menu/menu.ts +0 -176
  521. package/src/popover/popover.context.ts +0 -18
  522. package/src/popover/popover.example.ts +0 -50
  523. package/src/popover/popover.finance.example.ts +0 -52
  524. package/src/popover/popover.retro.example.ts +0 -53
  525. package/src/primitives/overlay/overlay.spec.ts +0 -90
  526. package/src/primitives/overlay/overlay.ts +0 -106
  527. package/src/select/select.context.ts +0 -3
  528. package/src/toast/toast.example.ts +0 -134
  529. package/src/toast/toast.finance.example.ts +0 -68
  530. package/src/toast/toast.retro.example.ts +0 -69
  531. package/src/tooltip/tooltip.example.ts +0 -46
  532. package/src/tooltip/tooltip.finance.example.ts +0 -54
  533. package/src/tooltip/tooltip.placements.example.ts +0 -56
  534. package/src/tooltip/tooltip.retro.example.ts +0 -65
@@ -0,0 +1,67 @@
1
+ import { DestroyRef, Directive, computed, inject, input } from '@angular/core';
2
+ import { KJ_CHAT, nextChatFooterId, type KjChatState } from './chat.context';
3
+
4
+ const STATE_LABEL: Readonly<Record<KjChatState, string>> = {
5
+ sending: 'Sending',
6
+ sent: 'Sent',
7
+ delivered: 'Delivered',
8
+ read: 'Read',
9
+ error: 'Failed to send',
10
+ };
11
+
12
+ /**
13
+ * Mints a stable id for the chat row's footer and registers it with the
14
+ * parent `KjChat` so the row's host `aria-describedby` references it.
15
+ *
16
+ * Reflects `kjState` to a `data-state` host attribute that components-package
17
+ * CSS reads to paint a status glyph (clock / single-check / double-check /
18
+ * filled-double-check / exclamation). Also injects an `aria-label` so the
19
+ * description is intelligible to AT even when the glyph is the only visible
20
+ * content.
21
+ *
22
+ * Free-form text ("Seen 12:46") is fine too — leave `kjState` undefined and
23
+ * the directive renders only the consumer's projected content.
24
+ *
25
+ * @example
26
+ * ```html
27
+ * <footer kjChatFooter kjState="read">Read 12:46</footer>
28
+ * ```
29
+ * @doc-category Core/Data display
30
+ * @doc
31
+ * @doc-name chat
32
+ */
33
+ @Directive({
34
+ selector: '[kjChatFooter]',
35
+ standalone: true,
36
+ host: {
37
+ '[id]': 'id',
38
+ '[attr.data-state]': 'kjState() ?? null',
39
+ '[attr.aria-label]': 'ariaLabel()',
40
+ },
41
+ })
42
+ export class KjChatFooter {
43
+ private readonly chat = inject(KJ_CHAT, { optional: true });
44
+ private readonly destroyRef = inject(DestroyRef);
45
+
46
+ /** Auto-generated id used for the parent row's `aria-describedby`. */
47
+ readonly id = nextChatFooterId();
48
+
49
+ /**
50
+ * Read / delivery state. Drives `data-state` for the glyph and an injected
51
+ * `aria-label` for AT. When undefined, only projected content renders.
52
+ */
53
+ readonly kjState = input<KjChatState | undefined>(undefined);
54
+
55
+ /** Aria label derived from `kjState`. `null` when no state is set. */
56
+ readonly ariaLabel = computed(() => {
57
+ const s = this.kjState();
58
+ return s ? STATE_LABEL[s] : null;
59
+ });
60
+
61
+ constructor() {
62
+ if (this.chat) {
63
+ this.chat.registerFooterId(this.id);
64
+ this.destroyRef.onDestroy(() => this.chat?.unregisterFooterId(this.id));
65
+ }
66
+ }
67
+ }
@@ -0,0 +1,41 @@
1
+ import { DestroyRef, Directive, inject } from '@angular/core';
2
+ import { KJ_CHAT, nextChatHeaderId } from './chat.context';
3
+
4
+ /**
5
+ * Mints a stable id for the chat row's header and registers it with the
6
+ * parent `KjChat` so the row's host `aria-labelledby` attribute references
7
+ * the sender's name + time. Visual chrome (small font, muted colour, spacing
8
+ * above the bubble) belongs to CSS; the directive does not paint.
9
+ *
10
+ * Standalone (no `KjChat` parent) the directive still mints an id but no
11
+ * wiring fires — the consumer is responsible for the link.
12
+ *
13
+ * @example
14
+ * ```html
15
+ * <header kjChatHeader>Alice <time datetime="…">12:46</time></header>
16
+ * ```
17
+ * @doc-category Core/Data display
18
+ * @doc
19
+ * @doc-name chat
20
+ */
21
+ @Directive({
22
+ selector: '[kjChatHeader]',
23
+ standalone: true,
24
+ host: {
25
+ '[id]': 'id',
26
+ },
27
+ })
28
+ export class KjChatHeader {
29
+ private readonly chat = inject(KJ_CHAT, { optional: true });
30
+ private readonly destroyRef = inject(DestroyRef);
31
+
32
+ /** Auto-generated id used for the parent row's `aria-labelledby`. */
33
+ readonly id = nextChatHeaderId();
34
+
35
+ constructor() {
36
+ if (this.chat) {
37
+ this.chat.registerHeaderId(this.id);
38
+ this.destroyRef.onDestroy(() => this.chat?.unregisterHeaderId(this.id));
39
+ }
40
+ }
41
+ }
@@ -0,0 +1,43 @@
1
+ import { Directive, input } from '@angular/core';
2
+ import { KJ_CHAT_LOG, type KjChatLogContext } from './chat.context';
3
+
4
+ /**
5
+ * Container for a stream of `[kjChat]` rows. Owns the live region: per APG's
6
+ * Log pattern, the **container** announces new arrivals — never the bubble
7
+ * itself. Default `aria-live="polite"` matches the chat-app convention; we
8
+ * deliberately do not expose `'assertive'` (it would interrupt the user
9
+ * mid-typing on every inbound message and is the canonical chat-app a11y bug).
10
+ *
11
+ * `aria-relevant="additions"` confines announcements to *new* messages so that
12
+ * edits to existing bubbles don't re-announce the full edited content.
13
+ *
14
+ * @example
15
+ * ```html
16
+ * <div kjChatLog kjChatLogLabel="Conversation with Alice">
17
+ * <div kjChat kjSide="start" kjChatAuthor="alice">…</div>
18
+ * <div kjChat kjSide="end" kjChatAuthor="me">…</div>
19
+ * </div>
20
+ * ```
21
+ * @doc-category Core/Data display
22
+ * @doc
23
+ * @doc-name chat
24
+ */
25
+ @Directive({
26
+ selector: '[kjChatLog]',
27
+ standalone: true,
28
+ providers: [{ provide: KJ_CHAT_LOG, useExisting: KjChatLog }],
29
+ host: {
30
+ 'role': 'log',
31
+ '[attr.aria-live]': 'kjChatLogLive()',
32
+ '[attr.aria-relevant]': '"additions"',
33
+ '[attr.aria-atomic]': '"false"',
34
+ '[attr.aria-label]': 'kjChatLogLabel() ?? null',
35
+ },
36
+ })
37
+ export class KjChatLog implements KjChatLogContext {
38
+ /** Live-region politeness. `'polite'` (default) or `'off'`. */
39
+ readonly kjChatLogLive = input<'polite' | 'off'>('polite');
40
+
41
+ /** Optional `aria-label` for the log landmark. */
42
+ readonly kjChatLogLabel = input<string | undefined>(undefined);
43
+ }
@@ -0,0 +1,64 @@
1
+ import { InjectionToken, type Signal } from '@angular/core';
2
+
3
+ /** Logical side of a chat row. CSS handles the physical RTL flip via cascade. */
4
+ export type KjChatSide = 'start' | 'end';
5
+
6
+ /**
7
+ * Per-row role override. Default `'article'` gives screen readers a per-message
8
+ * landmark; `'listitem'` opts in to list semantics (parent log must then carry
9
+ * `role="list"`); `null` drops the role attribute entirely.
10
+ */
11
+ export type KjChatRole = 'article' | 'listitem' | null;
12
+
13
+ /** Read / delivery state machine for the footer. */
14
+ export type KjChatState =
15
+ | 'sending'
16
+ | 'sent'
17
+ | 'delivered'
18
+ | 'read'
19
+ | 'error';
20
+
21
+ /** Context shape exposed by `KjChatLog` to descendant chat directives. */
22
+ export interface KjChatLogContext {
23
+ /** Reflected to `aria-live`. `'polite'` (default) or `'off'`. */
24
+ readonly kjChatLogLive: Signal<'polite' | 'off'>;
25
+ /** Optional landmark label for the log. */
26
+ readonly kjChatLogLabel: Signal<string | undefined>;
27
+ }
28
+
29
+ /**
30
+ * Context shape exposed by `KjChat` to its child directives. Children read this
31
+ * to register their minted ids (header / footer) for `aria-labelledby` /
32
+ * `aria-describedby` wiring, and to ask whether the row is part of a grouped
33
+ * run for tail / avatar suppression.
34
+ */
35
+ export interface KjChatContext {
36
+ readonly kjSide: Signal<KjChatSide>;
37
+ readonly kjChatAuthor: Signal<string | undefined>;
38
+ /** True when this row is grouped with the previous row (same author). */
39
+ readonly grouped: Signal<boolean>;
40
+ /** Register a header id; the row reflects it as `aria-labelledby`. */
41
+ registerHeaderId(id: string): void;
42
+ unregisterHeaderId(id: string): void;
43
+ /** Register a footer id; the row reflects it as `aria-describedby`. */
44
+ registerFooterId(id: string): void;
45
+ unregisterFooterId(id: string): void;
46
+ }
47
+
48
+ /** Injection token for the `KjChatLog` container. */
49
+ export const KJ_CHAT_LOG = new InjectionToken<KjChatLogContext>('KjChatLog');
50
+
51
+ /** Injection token for the `KjChat` row. */
52
+ export const KJ_CHAT = new InjectionToken<KjChatContext>('KjChat');
53
+
54
+ let _chatHeaderIdCounter = 0;
55
+ /** Allocate a stable id used for `aria-labelledby` wiring. */
56
+ export function nextChatHeaderId(): string {
57
+ return `kj-chat-header-${++_chatHeaderIdCounter}`;
58
+ }
59
+
60
+ let _chatFooterIdCounter = 0;
61
+ /** Allocate a stable id used for `aria-describedby` wiring. */
62
+ export function nextChatFooterId(): string {
63
+ return `kj-chat-footer-${++_chatFooterIdCounter}`;
64
+ }
@@ -0,0 +1,341 @@
1
+ import { render } from '@testing-library/angular';
2
+ import { axe, toHaveNoViolations } from 'jest-axe';
3
+ import { describe, expect, it } from 'vitest';
4
+ import { KjChat } from './chat';
5
+ import { KjChatAvatar } from './chat-avatar';
6
+ import { KjChatBubble } from './chat-bubble';
7
+ import { KjChatFooter } from './chat-footer';
8
+ import { KjChatHeader } from './chat-header';
9
+ import { KjChatLog } from './chat-log';
10
+ import { provideKjChatBubble } from './config';
11
+
12
+ expect.extend(toHaveNoViolations);
13
+
14
+ const imports = [
15
+ KjChatLog,
16
+ KjChat,
17
+ KjChatAvatar,
18
+ KjChatHeader,
19
+ KjChatBubble,
20
+ KjChatFooter,
21
+ ];
22
+
23
+ describe('KjChatLog', () => {
24
+ it('hosts role=log + aria-live=polite by default', async () => {
25
+ const { container } = await render(
26
+ `<div kjChatLog data-testid="log"></div>`,
27
+ { imports },
28
+ );
29
+ const log = container.querySelector('[data-testid="log"]')!;
30
+ expect(log).toHaveAttribute('role', 'log');
31
+ expect(log).toHaveAttribute('aria-live', 'polite');
32
+ expect(log).toHaveAttribute('aria-relevant', 'additions');
33
+ expect(log).toHaveAttribute('aria-atomic', 'false');
34
+ });
35
+
36
+ it('reflects kjChatLogLive=off', async () => {
37
+ const { container } = await render(
38
+ `<div kjChatLog kjChatLogLive="off" data-testid="log"></div>`,
39
+ { imports },
40
+ );
41
+ expect(container.querySelector('[data-testid="log"]')).toHaveAttribute(
42
+ 'aria-live',
43
+ 'off',
44
+ );
45
+ });
46
+
47
+ it('reflects kjChatLogLabel as aria-label', async () => {
48
+ const { container } = await render(
49
+ `<div kjChatLog kjChatLogLabel="Conversation with Alice" data-testid="log"></div>`,
50
+ { imports },
51
+ );
52
+ expect(container.querySelector('[data-testid="log"]')).toHaveAttribute(
53
+ 'aria-label',
54
+ 'Conversation with Alice',
55
+ );
56
+ });
57
+ });
58
+
59
+ describe('KjChat', () => {
60
+ it('default role is article', async () => {
61
+ const { container } = await render(
62
+ `<div kjChat data-testid="row"></div>`,
63
+ { imports },
64
+ );
65
+ expect(container.querySelector('[data-testid="row"]')).toHaveAttribute(
66
+ 'role',
67
+ 'article',
68
+ );
69
+ });
70
+
71
+ it('kjRole=listitem reflects on host', async () => {
72
+ const { container } = await render(
73
+ `<div kjChat kjRole="listitem" data-testid="row"></div>`,
74
+ { imports },
75
+ );
76
+ expect(container.querySelector('[data-testid="row"]')).toHaveAttribute(
77
+ 'role',
78
+ 'listitem',
79
+ );
80
+ });
81
+
82
+ it('kjRole=null drops the role attribute', async () => {
83
+ const { container } = await render(
84
+ `<div kjChat [kjRole]="null" data-testid="row"></div>`,
85
+ { imports },
86
+ );
87
+ expect(
88
+ container.querySelector('[data-testid="row"]'),
89
+ ).not.toHaveAttribute('role');
90
+ });
91
+
92
+ it('kjSide reflects to data-side', async () => {
93
+ const { container } = await render(
94
+ `<div kjChat kjSide="end" data-testid="row"></div>`,
95
+ { imports },
96
+ );
97
+ expect(container.querySelector('[data-testid="row"]')).toHaveAttribute(
98
+ 'data-side',
99
+ 'end',
100
+ );
101
+ });
102
+
103
+ it('auto-grouping marks the second same-author row with data-grouped', async () => {
104
+ const { container, fixture } = await render(
105
+ `<div kjChatLog>
106
+ <div kjChat kjChatAuthor="alice" data-testid="r1"></div>
107
+ <div kjChat kjChatAuthor="alice" data-testid="r2"></div>
108
+ <div kjChat kjChatAuthor="bob" data-testid="r3"></div>
109
+ </div>`,
110
+ { imports },
111
+ );
112
+ fixture.detectChanges();
113
+ await fixture.whenStable();
114
+ fixture.detectChanges();
115
+ expect(
116
+ container.querySelector('[data-testid="r1"]'),
117
+ ).not.toHaveAttribute('data-grouped');
118
+ expect(container.querySelector('[data-testid="r2"]')).toHaveAttribute(
119
+ 'data-grouped',
120
+ '',
121
+ );
122
+ expect(
123
+ container.querySelector('[data-testid="r3"]'),
124
+ ).not.toHaveAttribute('data-grouped');
125
+ });
126
+
127
+ it('explicit kjChatGrouped overrides auto-grouping', async () => {
128
+ const { container, fixture } = await render(
129
+ `<div kjChatLog>
130
+ <div kjChat kjChatAuthor="alice" data-testid="r1"></div>
131
+ <div kjChat kjChatAuthor="bob" [kjChatGrouped]="true" data-testid="r2"></div>
132
+ </div>`,
133
+ { imports },
134
+ );
135
+ fixture.detectChanges();
136
+ await fixture.whenStable();
137
+ expect(container.querySelector('[data-testid="r2"]')).toHaveAttribute(
138
+ 'data-grouped',
139
+ '',
140
+ );
141
+ });
142
+
143
+ it('header/footer ids wire aria-labelledby and aria-describedby on the row', async () => {
144
+ const { container } = await render(
145
+ `<div kjChat data-testid="row">
146
+ <header kjChatHeader data-testid="h">Alice 12:46</header>
147
+ <p kjChatBubble>Hi</p>
148
+ <footer kjChatFooter data-testid="f">Sent</footer>
149
+ </div>`,
150
+ { imports },
151
+ );
152
+ const row = container.querySelector('[data-testid="row"]')!;
153
+ const header = container.querySelector('[data-testid="h"]')!;
154
+ const footer = container.querySelector('[data-testid="f"]')!;
155
+ expect(header.id).toMatch(/^kj-chat-header-\d+$/);
156
+ expect(footer.id).toMatch(/^kj-chat-footer-\d+$/);
157
+ expect(row.getAttribute('aria-labelledby')).toBe(header.id);
158
+ expect(row.getAttribute('aria-describedby')).toBe(footer.id);
159
+ });
160
+ });
161
+
162
+ describe('KjChatAvatar', () => {
163
+ it('forces aria-hidden=true', async () => {
164
+ const { container } = await render(
165
+ `<span kjChatAvatar data-testid="av"></span>`,
166
+ { imports },
167
+ );
168
+ const av = container.querySelector('[data-testid="av"]')!;
169
+ expect(av).toHaveAttribute('aria-hidden', 'true');
170
+ expect(av).toHaveAttribute('role', 'presentation');
171
+ });
172
+ });
173
+
174
+ describe('KjChatHeader / KjChatFooter', () => {
175
+ it('mint unique ids for two adjacent headers / footers', async () => {
176
+ const { container } = await render(
177
+ `<div kjChatLog>
178
+ <div kjChat>
179
+ <header kjChatHeader data-testid="h1">A</header>
180
+ <footer kjChatFooter data-testid="f1">x</footer>
181
+ </div>
182
+ <div kjChat>
183
+ <header kjChatHeader data-testid="h2">B</header>
184
+ <footer kjChatFooter data-testid="f2">y</footer>
185
+ </div>
186
+ </div>`,
187
+ { imports },
188
+ );
189
+ const h1 = container.querySelector('[data-testid="h1"]')!.id;
190
+ const h2 = container.querySelector('[data-testid="h2"]')!.id;
191
+ const f1 = container.querySelector('[data-testid="f1"]')!.id;
192
+ const f2 = container.querySelector('[data-testid="f2"]')!.id;
193
+ expect(h1).not.toEqual(h2);
194
+ expect(f1).not.toEqual(f2);
195
+ expect(h1).toMatch(/^kj-chat-header-/);
196
+ expect(f1).toMatch(/^kj-chat-footer-/);
197
+ });
198
+
199
+ it('footer reflects kjState as data-state and aria-label', async () => {
200
+ const { container } = await render(
201
+ `<footer kjChatFooter kjState="read" data-testid="f">Read 12:46</footer>`,
202
+ { imports },
203
+ );
204
+ const f = container.querySelector('[data-testid="f"]')!;
205
+ expect(f).toHaveAttribute('data-state', 'read');
206
+ expect(f).toHaveAttribute('aria-label', 'Read');
207
+ });
208
+
209
+ it('footer omits data-state and aria-label when kjState is unset', async () => {
210
+ const { container } = await render(
211
+ `<footer kjChatFooter data-testid="f">Sent 12:46</footer>`,
212
+ { imports },
213
+ );
214
+ const f = container.querySelector('[data-testid="f"]')!;
215
+ expect(f).not.toHaveAttribute('data-state');
216
+ expect(f).not.toHaveAttribute('aria-label');
217
+ });
218
+ });
219
+
220
+ describe('KjChatBubble', () => {
221
+ it('composes KjVariant -> data-variant default', async () => {
222
+ const { container } = await render(
223
+ `<p kjChatBubble data-testid="b">Hi</p>`,
224
+ { imports },
225
+ );
226
+ expect(container.querySelector('[data-testid="b"]')).toHaveAttribute(
227
+ 'data-variant',
228
+ 'default',
229
+ );
230
+ });
231
+
232
+ it('composes KjSize -> data-size default', async () => {
233
+ const { container } = await render(
234
+ `<p kjChatBubble data-testid="b">Hi</p>`,
235
+ { imports },
236
+ );
237
+ expect(container.querySelector('[data-testid="b"]')).toHaveAttribute(
238
+ 'data-size',
239
+ 'md',
240
+ );
241
+ });
242
+
243
+ it('forwards kjVariant + kjSize through hostDirectives', async () => {
244
+ const { container } = await render(
245
+ `<p kjChatBubble kjVariant="primary" kjSize="lg" data-testid="b">Hi</p>`,
246
+ { imports },
247
+ );
248
+ const b = container.querySelector('[data-testid="b"]')!;
249
+ expect(b).toHaveAttribute('data-variant', 'primary');
250
+ expect(b).toHaveAttribute('data-size', 'lg');
251
+ });
252
+
253
+ it('renders data-tail when not grouped and no-tail not set', async () => {
254
+ const { container } = await render(
255
+ `<div kjChat>
256
+ <p kjChatBubble data-testid="b">Hi</p>
257
+ </div>`,
258
+ { imports },
259
+ );
260
+ expect(container.querySelector('[data-testid="b"]')).toHaveAttribute(
261
+ 'data-tail',
262
+ '',
263
+ );
264
+ });
265
+
266
+ it('omits data-tail when kjChatBubbleNoTail=true', async () => {
267
+ const { container } = await render(
268
+ `<div kjChat>
269
+ <p kjChatBubble [kjChatBubbleNoTail]="true" data-testid="b">Hi</p>
270
+ </div>`,
271
+ { imports },
272
+ );
273
+ expect(
274
+ container.querySelector('[data-testid="b"]'),
275
+ ).not.toHaveAttribute('data-tail');
276
+ });
277
+
278
+ it('omits data-tail when the parent row is auto-grouped', async () => {
279
+ const { container, fixture } = await render(
280
+ `<div kjChatLog>
281
+ <div kjChat kjChatAuthor="alice">
282
+ <p kjChatBubble data-testid="b1">A</p>
283
+ </div>
284
+ <div kjChat kjChatAuthor="alice">
285
+ <p kjChatBubble data-testid="b2">B</p>
286
+ </div>
287
+ </div>`,
288
+ { imports },
289
+ );
290
+ fixture.detectChanges();
291
+ await fixture.whenStable();
292
+ fixture.detectChanges();
293
+ expect(container.querySelector('[data-testid="b1"]')).toHaveAttribute(
294
+ 'data-tail',
295
+ '',
296
+ );
297
+ expect(
298
+ container.querySelector('[data-testid="b2"]'),
299
+ ).not.toHaveAttribute('data-tail');
300
+ });
301
+
302
+ it('respects provideKjChatBubble preset overrides', async () => {
303
+ const { container } = await render(
304
+ `<p kjChatBubble data-testid="b">x</p>`,
305
+ {
306
+ imports,
307
+ providers: [
308
+ provideKjChatBubble({
309
+ variants: ['default', 'primary', 'brand'],
310
+ defaults: { variant: 'brand', size: 'md' },
311
+ }),
312
+ ],
313
+ },
314
+ );
315
+ expect(container.querySelector('[data-testid="b"]')).toHaveAttribute(
316
+ 'data-variant',
317
+ 'brand',
318
+ );
319
+ });
320
+ });
321
+
322
+ describe('a11y', () => {
323
+ it('passes axe audit on a populated chat log', async () => {
324
+ const { container } = await render(
325
+ `<div kjChatLog kjChatLogLabel="Test conversation">
326
+ <div kjChat kjSide="start" kjChatAuthor="alice">
327
+ <span kjChatAvatar></span>
328
+ <header kjChatHeader>Alice 12:46</header>
329
+ <p kjChatBubble>Hi there</p>
330
+ <footer kjChatFooter kjState="read">Read 12:46</footer>
331
+ </div>
332
+ <div kjChat kjSide="end" kjChatAuthor="me">
333
+ <p kjChatBubble kjVariant="primary">Hello back</p>
334
+ <footer kjChatFooter kjState="delivered">Delivered</footer>
335
+ </div>
336
+ </div>`,
337
+ { imports },
338
+ );
339
+ expect(await axe(container)).toHaveNoViolations();
340
+ });
341
+ });