@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,217 @@
1
+ import { Component, signal, ChangeDetectionStrategy } from '@angular/core';
2
+ import { TestBed } from '@angular/core/testing';
3
+ import { render } from '@testing-library/angular';
4
+ import { axe, toHaveNoViolations } from 'jest-axe';
5
+ import { describe, expect, it } from 'vitest';
6
+ import {
7
+ KjAlert,
8
+ KjAlertActions,
9
+ KjAlertDescription,
10
+ KjAlertDismiss,
11
+ KjAlertIcon,
12
+ KjAlertTitle,
13
+ } from './alert';
14
+ import { provideKjAlert } from './config';
15
+
16
+ expect.extend(toHaveNoViolations);
17
+
18
+ const ALERT_IMPORTS = [
19
+ KjAlert,
20
+ KjAlertTitle,
21
+ KjAlertDescription,
22
+ KjAlertIcon,
23
+ KjAlertActions,
24
+ KjAlertDismiss,
25
+ ];
26
+
27
+ @Component({
28
+ standalone: true,
29
+ imports: [KjAlert, KjAlertTitle, KjAlertDismiss],
30
+ changeDetection: ChangeDetectionStrategy.Eager,
31
+ template: `
32
+ @if (visible()) {
33
+ <div kjAlert (kjAlertDismissed)="onDismissed()">
34
+ <h3 kjAlertTitle>Saved</h3>
35
+ <button kjAlertDismiss>×</button>
36
+ </div>
37
+ }
38
+ `,
39
+ })
40
+ class DismissHost {
41
+ readonly visible = signal(true);
42
+ fired = 0;
43
+ onDismissed(): void {
44
+ this.fired++;
45
+ this.visible.set(false);
46
+ }
47
+ }
48
+
49
+ describe('KjAlert', () => {
50
+ it('renders with role="status" and aria-live="polite" by default', async () => {
51
+ const { container } = await render(`<div kjAlert><h3 kjAlertTitle>Heads up</h3></div>`, {
52
+ imports: ALERT_IMPORTS,
53
+ });
54
+ const root = container.querySelector('[kjAlert]')!;
55
+ expect(root.getAttribute('role')).toBe('status');
56
+ expect(root.getAttribute('aria-live')).toBe('polite');
57
+ expect(root.getAttribute('aria-atomic')).toBe('true');
58
+ });
59
+
60
+ it('error variant resolves to assertive (role="alert" + aria-live="assertive")', async () => {
61
+ const { container } = await render(
62
+ `<div kjAlert kjVariant="error"><h3 kjAlertTitle>Failed</h3></div>`,
63
+ { imports: ALERT_IMPORTS },
64
+ );
65
+ const root = container.querySelector('[kjAlert]')!;
66
+ expect(root.getAttribute('role')).toBe('alert');
67
+ expect(root.getAttribute('aria-live')).toBe('assertive');
68
+ });
69
+
70
+ it('static flag resolves to role="region" with no aria-live', async () => {
71
+ const { container } = await render(
72
+ `<div kjAlert [kjAlertStatic]="true" aria-label="Banner"><span kjAlertDescription>x</span></div>`,
73
+ { imports: ALERT_IMPORTS },
74
+ );
75
+ const root = container.querySelector('[kjAlert]')!;
76
+ expect(root.getAttribute('role')).toBe('region');
77
+ expect(root.getAttribute('aria-live')).toBeNull();
78
+ });
79
+
80
+ it('explicit kjAlertMode wins over the matrix', async () => {
81
+ const { container } = await render(
82
+ `<div kjAlert kjVariant="error" kjAlertMode="polite"><span kjAlertDescription>x</span></div>`,
83
+ { imports: ALERT_IMPORTS },
84
+ );
85
+ const root = container.querySelector('[kjAlert]')!;
86
+ expect(root.getAttribute('role')).toBe('status');
87
+ expect(root.getAttribute('aria-live')).toBe('polite');
88
+ });
89
+
90
+ it('mode="off" omits role and aria-live', async () => {
91
+ const { container } = await render(
92
+ `<div kjAlert kjAlertMode="off"><span kjAlertDescription>x</span></div>`,
93
+ { imports: ALERT_IMPORTS },
94
+ );
95
+ const root = container.querySelector('[kjAlert]')!;
96
+ expect(root.getAttribute('role')).toBeNull();
97
+ expect(root.getAttribute('aria-live')).toBeNull();
98
+ });
99
+
100
+ it('reflects data-variant on the host', async () => {
101
+ const { container } = await render(
102
+ `<div kjAlert kjVariant="warning"><span kjAlertDescription>x</span></div>`,
103
+ { imports: ALERT_IMPORTS },
104
+ );
105
+ expect(container.querySelector('[kjAlert]')!.getAttribute('data-variant')).toBe('warning');
106
+ });
107
+
108
+ it('wires aria-labelledby to the title id', async () => {
109
+ const { container } = await render(`<div kjAlert><h3 kjAlertTitle>Title</h3></div>`, {
110
+ imports: ALERT_IMPORTS,
111
+ });
112
+ const root = container.querySelector('[kjAlert]')!;
113
+ const title = container.querySelector('[kjAlertTitle]')!;
114
+ expect(title.id).toMatch(/-title$/);
115
+ expect(root.getAttribute('aria-labelledby')).toBe(title.id);
116
+ });
117
+
118
+ it('wires aria-describedby to the description id', async () => {
119
+ const { container } = await render(`<div kjAlert><p kjAlertDescription>Body</p></div>`, {
120
+ imports: ALERT_IMPORTS,
121
+ });
122
+ const root = container.querySelector('[kjAlert]')!;
123
+ const desc = container.querySelector('[kjAlertDescription]')!;
124
+ expect(desc.id).toMatch(/-description$/);
125
+ expect(root.getAttribute('aria-describedby')).toBe(desc.id);
126
+ });
127
+
128
+ it('icon sets aria-hidden="true" and mirrors data-variant', async () => {
129
+ const { container } = await render(
130
+ `<div kjAlert kjVariant="success"><span kjAlertIcon></span><span kjAlertDescription>x</span></div>`,
131
+ { imports: ALERT_IMPORTS },
132
+ );
133
+ const icon = container.querySelector('[kjAlertIcon]')!;
134
+ expect(icon.getAttribute('aria-hidden')).toBe('true');
135
+ expect(icon.getAttribute('data-variant')).toBe('success');
136
+ });
137
+
138
+ it('actions container sets role="group" with default aria-label', async () => {
139
+ const { container } = await render(
140
+ `<div kjAlert>
141
+ <span kjAlertDescription>x</span>
142
+ <div kjAlertActions></div>
143
+ </div>`,
144
+ { imports: ALERT_IMPORTS },
145
+ );
146
+ const actions = container.querySelector('[kjAlertActions]')!;
147
+ expect(actions.getAttribute('role')).toBe('group');
148
+ expect(actions.getAttribute('aria-label')).toBe('Alert actions');
149
+ });
150
+
151
+ it('dismiss button has aria-label="Dismiss" and inherits KjButton chrome', async () => {
152
+ const { container } = await render(
153
+ `<div kjAlert><span kjAlertDescription>x</span><button kjAlertDismiss>×</button></div>`,
154
+ { imports: ALERT_IMPORTS },
155
+ );
156
+ const dismiss = container.querySelector('[kjAlertDismiss]')!;
157
+ expect(dismiss.getAttribute('aria-label')).toBe('Dismiss');
158
+ // Inherits the KjButton variant default forwarded by KjAlertDismiss
159
+ expect(dismiss.getAttribute('data-variant')).toBe('ghost');
160
+ expect(dismiss.getAttribute('data-size')).toBe('icon');
161
+ });
162
+
163
+ it('clicking dismiss fires kjAlertDismissed (consumer unmounts)', () => {
164
+ TestBed.configureTestingModule({ imports: [DismissHost] });
165
+ const fixture = TestBed.createComponent(DismissHost);
166
+ fixture.detectChanges();
167
+ const btn = fixture.nativeElement.querySelector('[kjAlertDismiss]');
168
+ btn.click();
169
+ fixture.detectChanges();
170
+ expect(fixture.componentInstance.fired).toBe(1);
171
+ expect(fixture.nativeElement.querySelector('[kjAlert]')).toBeNull();
172
+ });
173
+
174
+ it('default variant is "info" from KJ_ALERT_DEFAULTS', async () => {
175
+ const { container } = await render(`<div kjAlert><span kjAlertDescription>x</span></div>`, {
176
+ imports: ALERT_IMPORTS,
177
+ });
178
+ expect(container.querySelector('[kjAlert]')!.getAttribute('data-variant')).toBe('info');
179
+ });
180
+
181
+ it('provideKjAlert at TestBed scope flows into directive defaults', async () => {
182
+ TestBed.configureTestingModule({
183
+ providers: [
184
+ provideKjAlert({
185
+ variants: ['info', 'success', 'warning', 'error', 'neutral', 'brand'],
186
+ defaults: { variant: 'brand', size: 'md' },
187
+ }),
188
+ ],
189
+ });
190
+ const { container } = await render(`<div kjAlert><span kjAlertDescription>x</span></div>`, {
191
+ imports: ALERT_IMPORTS,
192
+ });
193
+ expect(container.querySelector('[kjAlert]')!.getAttribute('data-variant')).toBe('brand');
194
+ });
195
+
196
+ it('passes axe audit (polite alert)', async () => {
197
+ const { container } = await render(
198
+ `<div kjAlert>
199
+ <h3 kjAlertTitle>Saved</h3>
200
+ <p kjAlertDescription>Your changes were saved.</p>
201
+ </div>`,
202
+ { imports: ALERT_IMPORTS },
203
+ );
204
+ expect(await axe(container)).toHaveNoViolations();
205
+ });
206
+
207
+ it('passes axe audit (static banner with aria-labelledby)', async () => {
208
+ const { container } = await render(
209
+ `<div kjAlert [kjAlertStatic]="true" aria-labelledby="banner-title">
210
+ <h3 kjAlertTitle id="banner-title">Maintenance</h3>
211
+ <p kjAlertDescription>Down Sat 02:00–04:00 UTC.</p>
212
+ </div>`,
213
+ { imports: ALERT_IMPORTS },
214
+ );
215
+ expect(await axe(container)).toHaveNoViolations();
216
+ });
217
+ });
@@ -0,0 +1,417 @@
1
+ import {
2
+ Directive,
3
+ ElementRef,
4
+ InputSignalWithTransform,
5
+ type OnDestroy,
6
+ booleanAttribute,
7
+ computed,
8
+ effect,
9
+ inject,
10
+ input,
11
+ isDevMode,
12
+ output,
13
+ signal,
14
+ } from '@angular/core';
15
+ import { KjButton } from '../button/button';
16
+ import { KJ_ALERT, KjAlertContext, KjAlertMode } from './alert.context';
17
+ import { KJ_ALERT_CONFIG } from './config';
18
+
19
+ let _alertIdCounter = 0;
20
+
21
+ /**
22
+ * Persistent, in-flow notification (also known as a banner). Owns the
23
+ * `role` / `aria-live` matrix described in `docs/component-analyses/feedback/alert.md`:
24
+ * `kjAlertMode='assertive'` → `role="alert"` + `aria-live="assertive"`;
25
+ * `'polite'` → `role="status"` + `aria-live="polite"`;
26
+ * `'static'` → `role="region"` (no live, requires `aria-label` /
27
+ * `aria-labelledby`); `'off'` → no role, no live region.
28
+ *
29
+ * When `kjAlertMode` is unset, the directive resolves the matrix from
30
+ * `kjAlertStatic` and `kjVariant`:
31
+ *
32
+ * 1. `kjAlertStatic === true` → `'static'`
33
+ * 2. `kjVariant === 'error'` → `'assertive'`
34
+ * 3. otherwise → `'polite'`
35
+ *
36
+ * Visibility is consumer-managed: `KjAlertDismiss` (or programmatic
37
+ * `dismiss()`) fires `kjAlertDismissed` and the parent `*ngIf` / `@if`
38
+ * unmounts. The directive itself is stateless beyond exposing
39
+ * `data-dismissed="true"` for the brief render window before unmount,
40
+ * so themes can run an exit animation.
41
+ *
42
+ * @example
43
+ * ```html
44
+ * <div kjAlert kjVariant="error" #a="kjAlert">
45
+ * <h3 kjAlertTitle>Could not save draft</h3>
46
+ * <p kjAlertDescription>Network request timed out — retry?</p>
47
+ * <div kjAlertActions>
48
+ * <button kjButton (click)="retry()">Retry</button>
49
+ * </div>
50
+ * <button kjAlertDismiss>×</button>
51
+ * </div>
52
+ * ```
53
+ * @doc-category Core/Feedback
54
+ * @doc
55
+ * @doc-name alert
56
+ * @doc-description Unstyled in-flow alert that picks the right ARIA live-region role for its severity.
57
+ * @doc-is-main
58
+ */
59
+ @Directive({
60
+ selector: '[kjAlert]',
61
+ standalone: true,
62
+ exportAs: 'kjAlert',
63
+ providers: [
64
+ { provide: KJ_ALERT, useExisting: KjAlert },
65
+ ],
66
+ host: {
67
+ '[attr.role]': 'role()',
68
+ '[attr.aria-live]': 'live()',
69
+ '[attr.aria-atomic]': 'live() ? "true" : null',
70
+ '[attr.aria-labelledby]': 'titleId()',
71
+ '[attr.aria-describedby]': 'descriptionId()',
72
+ '[attr.data-variant]': 'variant()',
73
+ '[attr.data-size]': 'size()',
74
+ '[attr.data-dismissed]': 'dismissed() ? "true" : null',
75
+ '[attr.data-mode]': 'mode()',
76
+ '[id]': 'alertId()',
77
+ },
78
+ })
79
+ export class KjAlert implements KjAlertContext {
80
+ private readonly el = inject<ElementRef<HTMLElement>>(ElementRef);
81
+ private readonly preset = inject(KJ_ALERT_CONFIG);
82
+
83
+ /**
84
+ * Stable, deterministic id for the alert root. Used to derive
85
+ * `${alertId}-title` / `${alertId}-description` ids from descendant
86
+ * directives. The host's `[id]` is set so descendants can reference it
87
+ * via `aria-labelledby` / `aria-describedby` cross-references.
88
+ */
89
+ readonly alertId = signal(`kj-alert-${++_alertIdCounter}`);
90
+
91
+ /**
92
+ * Severity variant (validated against `KJ_ALERT_CONFIG.variants` in dev).
93
+ * Reflects to `data-variant`. Drives the default `kjAlertMode` resolution
94
+ * (error → assertive, others → polite).
95
+ *
96
+ * Field-annotated explicitly so ng-packagr does not narrow the emitted
97
+ * `.d.ts` write-side to `string` (dropping `string | undefined` flow-through
98
+ * — same trick as `KjButton.kjPressed` and `KjVariant.kjVariant`).
99
+ */
100
+ readonly kjVariant: InputSignalWithTransform<string, string | undefined> = input(
101
+ this.preset.defaults.variant,
102
+ { transform: (v?: string) => v || this.preset.defaults.variant },
103
+ );
104
+
105
+ /** Size token validated against `KJ_ALERT_CONFIG.sizes`. Reflects to `data-size`. */
106
+ readonly kjSize: InputSignalWithTransform<string, string | undefined> = input(
107
+ this.preset.defaults.size,
108
+ { transform: (v?: string) => v || this.preset.defaults.size },
109
+ );
110
+
111
+ /**
112
+ * Explicit override for the role/live matrix. Leave unset to let the
113
+ * directive resolve from `kjAlertStatic` / `kjVariant`.
114
+ */
115
+ readonly kjAlertMode = input<KjAlertMode | undefined>(undefined);
116
+
117
+ /**
118
+ * Marks the alert as a static page-level banner. When `true` and
119
+ * `kjAlertMode` is unset, resolves to `'static'` (`role="region"`,
120
+ * no `aria-live`). The host MUST also carry an accessible name —
121
+ * either `aria-label`, `aria-labelledby`, or a projected
122
+ * `[kjAlertTitle]` (validated in dev mode).
123
+ */
124
+ readonly kjAlertStatic = input(false, { transform: booleanAttribute });
125
+
126
+ /**
127
+ * Last-resort override for the host `role` attribute. Discouraged —
128
+ * prefer `kjAlertMode`. Only honoured when the resolved mode is
129
+ * `'off'`. Kept for consumers who need a non-standard role
130
+ * (e.g., `role="alertdialog"` is wrong here, but `role="status"`
131
+ * forced on a non-error is valid).
132
+ */
133
+ readonly kjAlertRole = input<string | undefined>(undefined);
134
+
135
+ /** Fired when the dismiss button (or programmatic `dismiss()`) runs. */
136
+ readonly kjAlertDismissed = output<void>();
137
+
138
+ private readonly _titleIds = signal<readonly string[]>([]);
139
+ private readonly _descriptionIds = signal<readonly string[]>([]);
140
+ private readonly _dismissed = signal(false);
141
+
142
+ /** Resolved variant for context consumers (`KjAlertIcon`, etc.). */
143
+ readonly variant = this.kjVariant;
144
+
145
+ /** Resolved size signal exposed for symmetry — currently used only as `data-size`. */
146
+ readonly size = this.kjSize;
147
+
148
+ /**
149
+ * Resolved mode following the matrix:
150
+ * 1. explicit `kjAlertMode`,
151
+ * 2. `kjAlertStatic` → `'static'`,
152
+ * 3. `error` variant → `'assertive'`,
153
+ * 4. else `'polite'`.
154
+ */
155
+ readonly mode = computed<KjAlertMode>(() => {
156
+ const explicit = this.kjAlertMode();
157
+ if (explicit) return explicit;
158
+ if (this.kjAlertStatic()) return 'static';
159
+ if (this.variant() === 'error') return 'assertive';
160
+ return 'polite';
161
+ });
162
+
163
+ /** Resolved `role` attribute value (or `null` when in `'off'` mode without a `kjAlertRole`). */
164
+ readonly role = computed<string | null>(() => {
165
+ switch (this.mode()) {
166
+ case 'assertive': return 'alert';
167
+ case 'polite': return 'status';
168
+ case 'static': return 'region';
169
+ case 'off': return this.kjAlertRole() ?? null;
170
+ }
171
+ });
172
+
173
+ /** Resolved `aria-live` value, or `null` for static / off. */
174
+ readonly live = computed<'assertive' | 'polite' | null>(() => {
175
+ const m = this.mode();
176
+ return m === 'assertive' ? 'assertive'
177
+ : m === 'polite' ? 'polite'
178
+ : null;
179
+ });
180
+
181
+ /** First registered title id (most recent registration wins, but only one is expected). */
182
+ readonly titleId = computed<string | null>(() => this._titleIds()[0] ?? null);
183
+
184
+ /** First registered description id. */
185
+ readonly descriptionId = computed<string | null>(() => this._descriptionIds()[0] ?? null);
186
+
187
+ /** Brief flag set on `dismiss()` so themes can animate exit. */
188
+ readonly dismissed = this._dismissed.asReadonly();
189
+
190
+ registerTitle(id: string): void {
191
+ this._titleIds.update(ids => ids.includes(id) ? ids : [...ids, id]);
192
+ }
193
+ unregisterTitle(id: string): void {
194
+ this._titleIds.update(ids => ids.filter(x => x !== id));
195
+ }
196
+ registerDescription(id: string): void {
197
+ this._descriptionIds.update(ids => ids.includes(id) ? ids : [...ids, id]);
198
+ }
199
+ unregisterDescription(id: string): void {
200
+ this._descriptionIds.update(ids => ids.filter(x => x !== id));
201
+ }
202
+
203
+ /**
204
+ * Marks the alert as dismissed and emits `kjAlertDismissed`. The consumer
205
+ * is responsible for unmounting (this directive does not remove itself).
206
+ */
207
+ dismiss(): void {
208
+ if (this._dismissed()) return;
209
+ this._dismissed.set(true);
210
+ this.kjAlertDismissed.emit();
211
+ }
212
+
213
+ constructor() {
214
+ if (isDevMode()) {
215
+ effect(() => {
216
+ const v = this.kjVariant();
217
+ if (!this.preset.variants.includes(v)) {
218
+ console.warn(
219
+ `[kj-alert] unknown variant "${v}". Allowed values: ${this.preset.variants.join(', ')}.`,
220
+ );
221
+ }
222
+ });
223
+ effect(() => {
224
+ const s = this.kjSize();
225
+ if (!this.preset.sizes.includes(s)) {
226
+ console.warn(
227
+ `[kj-alert] unknown size "${s}". Allowed values: ${this.preset.sizes.join(', ')}.`,
228
+ );
229
+ }
230
+ });
231
+
232
+ effect(() => {
233
+ const m = this.mode();
234
+ const node = this.el.nativeElement;
235
+
236
+ // 1. Static mode without an accessible name → unlabelled region.
237
+ if (m === 'static') {
238
+ const hasAriaLabel = node.hasAttribute('aria-label');
239
+ const hasAriaLabelledby = node.hasAttribute('aria-labelledby') || this.titleId() != null;
240
+ if (!hasAriaLabel && !hasAriaLabelledby) {
241
+ console.warn(
242
+ '[kj-alert] static mode requires an accessible name — add aria-label, ' +
243
+ 'aria-labelledby, or a [kjAlertTitle] child.',
244
+ );
245
+ }
246
+ }
247
+
248
+ // 2. No accessible content at all.
249
+ if (this.titleId() == null && this.descriptionId() == null && !node.textContent?.trim()) {
250
+ console.warn('[kj-alert] alert has no accessible content (no title, description, or text).');
251
+ }
252
+
253
+ // 3. Assertive + success — almost certainly a misuse.
254
+ if (m === 'assertive' && this.variant() === 'success') {
255
+ console.warn(
256
+ '[kj-alert] assertive mode with success variant is unusual — success messages ' +
257
+ 'should not interrupt screen reader speech.',
258
+ );
259
+ }
260
+ });
261
+ }
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Marks an element as the alert title. Generates `${alertId}-title`,
267
+ * registers with the `KJ_ALERT` context so the root reflects
268
+ * `aria-labelledby`. Apply to a heading element (`<h3 kjAlertTitle>`)
269
+ * when the alert is in a content region.
270
+ *
271
+ * @doc-category Core/Feedback
272
+ * @doc
273
+ * @doc-name alert
274
+ */
275
+ @Directive({
276
+ selector: '[kjAlertTitle]',
277
+ standalone: true,
278
+ host: {
279
+ '[attr.id]': 'titleId',
280
+ },
281
+ })
282
+ export class KjAlertTitle implements OnDestroy {
283
+ private readonly ctx = inject(KJ_ALERT);
284
+ readonly titleId = `${this.ctx.alertId()}-title`;
285
+
286
+ constructor() {
287
+ this.ctx.registerTitle(this.titleId);
288
+ inject(ElementRef); // ensure host element is bound before destroy
289
+ }
290
+
291
+ ngOnDestroy(): void {
292
+ this.ctx.unregisterTitle(this.titleId);
293
+ }
294
+ }
295
+
296
+ /**
297
+ * Marks an element as the alert description. Generates
298
+ * `${alertId}-description`, registers with `KJ_ALERT` for
299
+ * `aria-describedby`. Same id-registration pattern as `KjAlertTitle`.
300
+ *
301
+ * @doc-category Core/Feedback
302
+ * @doc
303
+ * @doc-name alert
304
+ */
305
+ @Directive({
306
+ selector: '[kjAlertDescription]',
307
+ standalone: true,
308
+ host: {
309
+ '[attr.id]': 'descriptionId',
310
+ },
311
+ })
312
+ export class KjAlertDescription implements OnDestroy {
313
+ private readonly ctx = inject(KJ_ALERT);
314
+ readonly descriptionId = `${this.ctx.alertId()}-description`;
315
+
316
+ constructor() {
317
+ this.ctx.registerDescription(this.descriptionId);
318
+ }
319
+
320
+ ngOnDestroy(): void {
321
+ this.ctx.unregisterDescription(this.descriptionId);
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Decorative icon slot for an alert. Sets `aria-hidden="true"` (the
327
+ * meaning is carried by the title/description) and mirrors the
328
+ * resolved variant as `data-variant` so themes can swap glyphs without
329
+ * the consumer re-typing it.
330
+ *
331
+ * @doc-category Core/Feedback
332
+ * @doc
333
+ * @doc-name alert
334
+ */
335
+ @Directive({
336
+ selector: '[kjAlertIcon]',
337
+ standalone: true,
338
+ host: {
339
+ '[attr.aria-hidden]': '"true"',
340
+ '[attr.data-variant]': 'variant()',
341
+ },
342
+ })
343
+ export class KjAlertIcon {
344
+ private readonly ctx = inject(KJ_ALERT);
345
+ readonly variant = this.ctx.variant;
346
+ }
347
+
348
+ /**
349
+ * Semantic group for action buttons inside an alert. Sets
350
+ * `role="group"` on the host and an overridable
351
+ * `aria-label="Alert actions"` so AT users can skip past the
352
+ * actions container as a unit.
353
+ *
354
+ * @doc-category Core/Feedback
355
+ * @doc
356
+ * @doc-name alert
357
+ */
358
+ @Directive({
359
+ selector: '[kjAlertActions]',
360
+ standalone: true,
361
+ host: {
362
+ 'role': 'group',
363
+ '[attr.aria-label]': 'kjAlertActionsLabel()',
364
+ },
365
+ })
366
+ export class KjAlertActions {
367
+ /** AT label for the group. Localisable. */
368
+ readonly kjAlertActionsLabel = input<string>('Alert actions');
369
+ }
370
+
371
+ /**
372
+ * Dismiss button for an alert. Composes `KjButton` via host directives
373
+ * so the button gets the full focus-ring / aria-disabled / capture-phase
374
+ * click contract. Calls `KJ_ALERT.dismiss()` on click — the consumer's
375
+ * own `(click)` listener still fires (e.g. for analytics).
376
+ *
377
+ * Aliased inputs: `kjAlertDismissLabel` → `KjButton.kjAriaLabel`,
378
+ * `kjAlertDismissVariant` → `KjButton.kjVariant`,
379
+ * `kjAlertDismissSize` → `KjButton.kjSize`.
380
+ *
381
+ * @doc-category Core/Feedback
382
+ * @doc
383
+ * @doc-name alert
384
+ */
385
+ @Directive({
386
+ selector: '[kjAlertDismiss]',
387
+ standalone: true,
388
+ hostDirectives: [KjButton],
389
+ host: {
390
+ '[attr.aria-label]': 'kjAlertDismissLabel()',
391
+ '[attr.data-variant]': 'kjAlertDismissVariant()',
392
+ '[attr.data-size]': 'kjAlertDismissSize()',
393
+ '(click)': 'onClick()',
394
+ },
395
+ })
396
+ export class KjAlertDismiss {
397
+ private readonly ctx = inject(KJ_ALERT);
398
+
399
+ /** AT label for the dismiss button. */
400
+ readonly kjAlertDismissLabel = input<string>('Dismiss');
401
+
402
+ /**
403
+ * Variant token mirrored as `data-variant`. Defaults to `'ghost'` so themes
404
+ * pick up the unobtrusive close-button surface. Mirrored directly rather
405
+ * than aliased through `KjButton`'s host-directive `kjVariant` input
406
+ * because Angular does not let `hostDirectives.inputs` alias an input that
407
+ * is itself only exposed via a nested host directive.
408
+ */
409
+ readonly kjAlertDismissVariant = input<string>('ghost');
410
+
411
+ /** Size token mirrored as `data-size`. Defaults to `'icon'` for the 44×44 touch target. */
412
+ readonly kjAlertDismissSize = input<string>('icon');
413
+
414
+ onClick(): void {
415
+ this.ctx.dismiss();
416
+ }
417
+ }
@@ -0,0 +1,46 @@
1
+ import { InjectionToken, Provider } from '@angular/core';
2
+
3
+ /**
4
+ * Shape of the Alert preset configuration. Mirrors `KjButtonConfig` —
5
+ * `bindPresets(KJ_ALERT_CONFIG)` translates this into the shared
6
+ * `KJ_VARIANT_PRESET` / `KJ_SIZE_PRESET` tokens consumed by the
7
+ * `KjVariant` / `KjSize` host directives composed on `KjAlert`.
8
+ */
9
+ export interface KjAlertConfig {
10
+ variants: string[];
11
+ sizes: string[];
12
+ defaults: { variant: string; size: string };
13
+ }
14
+
15
+ /**
16
+ * Default Alert presets shipped by kouji-ui. Severities follow the APG /
17
+ * shadcn / daisyUI taxonomy. Spread to extend:
18
+ * `[...KJ_ALERT_DEFAULTS.variants, 'brand']`.
19
+ */
20
+ export const KJ_ALERT_DEFAULTS: KjAlertConfig = {
21
+ variants: ['info', 'success', 'warning', 'error', 'neutral'],
22
+ sizes: ['sm', 'md', 'lg'],
23
+ defaults: { variant: 'info', size: 'md' },
24
+ };
25
+
26
+ /**
27
+ * DI token for the active Alert presets. Default factory yields
28
+ * `KJ_ALERT_DEFAULTS`. Override via `provideKjAlert(…)`.
29
+ */
30
+ export const KJ_ALERT_CONFIG = new InjectionToken<KjAlertConfig>('kj.alert.config', {
31
+ factory: () => KJ_ALERT_DEFAULTS,
32
+ });
33
+
34
+ /**
35
+ * Configures the Alert presets for the enclosing injector. Replaces
36
+ * (does not merge) `variants` / `sizes`; spread `KJ_ALERT_DEFAULTS.variants`
37
+ * to extend.
38
+ */
39
+ export function provideKjAlert(config: Partial<KjAlertConfig>): Provider[] {
40
+ return [
41
+ {
42
+ provide: KJ_ALERT_CONFIG,
43
+ useValue: { ...KJ_ALERT_DEFAULTS, ...config },
44
+ },
45
+ ];
46
+ }
@@ -0,0 +1,19 @@
1
+ export {
2
+ KjAlert,
3
+ KjAlertTitle,
4
+ KjAlertDescription,
5
+ KjAlertIcon,
6
+ KjAlertActions,
7
+ KjAlertDismiss,
8
+ } from './alert';
9
+ export {
10
+ KJ_ALERT,
11
+ type KjAlertContext,
12
+ type KjAlertMode,
13
+ } from './alert.context';
14
+ export {
15
+ KJ_ALERT_CONFIG,
16
+ KJ_ALERT_DEFAULTS,
17
+ type KjAlertConfig,
18
+ provideKjAlert,
19
+ } from './config';