@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,191 @@
1
+ import {
2
+ DestroyRef,
3
+ Directive,
4
+ ElementRef,
5
+ Injector,
6
+ afterNextRender,
7
+ computed,
8
+ effect,
9
+ inject,
10
+ input,
11
+ signal,
12
+ } from '@angular/core';
13
+ import {
14
+ KJ_CHAT,
15
+ KJ_CHAT_LOG,
16
+ type KjChatContext,
17
+ type KjChatRole,
18
+ type KjChatSide,
19
+ } from './chat.context';
20
+
21
+ /**
22
+ * Element → directive map used by `KjChat` to walk previous siblings without
23
+ * relying on Angular dev-mode internals. Each row registers itself on its host
24
+ * element on construction and unregisters on destroy.
25
+ */
26
+ const CHAT_ROW_REGISTRY = new WeakMap<Element, KjChat>();
27
+
28
+ /**
29
+ * A single chat row. Owns the logical side, the per-row role, and the
30
+ * group-by-sender computation; reflects header / footer ids registered by
31
+ * its children for `aria-labelledby` / `aria-describedby` wiring.
32
+ *
33
+ * **Grouping uses DOM traversal, not a registry**, deliberately: the registry
34
+ * pattern races on the bottom-up `ngOnInit` order (children before parent),
35
+ * which means the first row could ask for its previous sibling before the
36
+ * previous row has registered. DOM traversal via `previousElementSibling` is
37
+ * deterministic and works at first render.
38
+ *
39
+ * Standalone rows (no `KjChatLog` parent) skip auto-grouping entirely;
40
+ * consumers set `kjChatGrouped` explicitly when they want grouped chrome.
41
+ *
42
+ * @example
43
+ * ```html
44
+ * <div kjChat kjSide="start" kjChatAuthor="alice">
45
+ * <span kjChatAvatar>…</span>
46
+ * <header kjChatHeader>Alice 12:46</header>
47
+ * <p kjChatBubble>Hey!</p>
48
+ * <footer kjChatFooter kjState="read">Read 12:46</footer>
49
+ * </div>
50
+ * ```
51
+ * @doc-category Core/Data display
52
+ * @doc
53
+ * @doc-name chat
54
+ * @doc-description Unstyled chat row with side, sender grouping, and ARIA wiring for message log semantics.
55
+ * @doc-is-main
56
+ */
57
+ @Directive({
58
+ selector: '[kjChat]',
59
+ standalone: true,
60
+ providers: [{ provide: KJ_CHAT, useExisting: KjChat }],
61
+ host: {
62
+ '[attr.role]': 'kjRole()',
63
+ '[attr.data-side]': 'kjSide()',
64
+ '[attr.data-grouped]': 'grouped() ? "" : null',
65
+ '[attr.aria-labelledby]': 'headerId() ?? null',
66
+ '[attr.aria-describedby]': 'footerId() ?? null',
67
+ },
68
+ })
69
+ export class KjChat implements KjChatContext {
70
+ private readonly el = inject<ElementRef<HTMLElement>>(ElementRef);
71
+ private readonly injector = inject(Injector);
72
+ private readonly destroyRef = inject(DestroyRef);
73
+ private readonly log = inject(KJ_CHAT_LOG, { optional: true });
74
+
75
+ /** Logical side of this row. CSS reads `data-side` and handles RTL flip. */
76
+ readonly kjSide = input<KjChatSide>('start');
77
+
78
+ /** Sender id — drives auto-grouping with the previous sibling row. */
79
+ readonly kjChatAuthor = input<string | undefined>(undefined);
80
+
81
+ /**
82
+ * Explicit group override. When defined, wins over the auto-computed value;
83
+ * use this when no `KjChatLog` parent is present.
84
+ */
85
+ readonly kjChatGrouped = input<boolean | undefined>(undefined);
86
+
87
+ /**
88
+ * Per-row role. Default `'article'` gives AT users a per-message landmark
89
+ * navigable via JAWS / NVDA `M`; `'listitem'` opts in to list semantics
90
+ * (parent log must then expose `role="list"`); `null` drops the attribute.
91
+ */
92
+ readonly kjRole = input<KjChatRole>('article');
93
+
94
+ private readonly headerIds = signal<readonly string[]>([]);
95
+ private readonly footerIds = signal<readonly string[]>([]);
96
+
97
+ /** Composed `aria-labelledby` value. */
98
+ readonly headerId = computed(() => {
99
+ const ids = this.headerIds();
100
+ return ids.length === 0 ? null : ids.join(' ');
101
+ });
102
+
103
+ /** Composed `aria-describedby` value. */
104
+ readonly footerId = computed(() => {
105
+ const ids = this.footerIds();
106
+ return ids.length === 0 ? null : ids.join(' ');
107
+ });
108
+
109
+ private readonly autoGrouped = signal(false);
110
+
111
+ /** True when this row groups with the previous one (same author). */
112
+ readonly grouped = computed(
113
+ () => this.kjChatGrouped() ?? this.autoGrouped(),
114
+ );
115
+
116
+ constructor() {
117
+ // Registry write happens in the constructor so any sibling rendered
118
+ // *after* us finds us during its own afterNextRender pass. (Angular
119
+ // builds child instances bottom-up, but DOM siblings are ordered
120
+ // top-down — by the time the second row's afterNextRender fires,
121
+ // the first row's host element exists and is keyed in the WeakMap.)
122
+ CHAT_ROW_REGISTRY.set(this.el.nativeElement, this);
123
+ this.destroyRef.onDestroy(() =>
124
+ CHAT_ROW_REGISTRY.delete(this.el.nativeElement),
125
+ );
126
+
127
+ // DOM-traversal grouping: run after first render (so the previous
128
+ // sibling exists) and re-run whenever this row's author changes.
129
+ afterNextRender(() => {
130
+ this.recomputeAutoGrouped();
131
+ });
132
+ effect(
133
+ () => {
134
+ // Track the input — touching it inside the effect creates the dep.
135
+ this.kjChatAuthor();
136
+ this.recomputeAutoGrouped();
137
+ },
138
+ { injector: this.injector },
139
+ );
140
+ }
141
+
142
+ /** @internal */
143
+ registerHeaderId(id: string): void {
144
+ this.headerIds.update(ids => [...ids, id]);
145
+ this.destroyRef.onDestroy(() => this.unregisterHeaderId(id));
146
+ }
147
+
148
+ /** @internal */
149
+ unregisterHeaderId(id: string): void {
150
+ this.headerIds.update(ids => ids.filter(x => x !== id));
151
+ }
152
+
153
+ /** @internal */
154
+ registerFooterId(id: string): void {
155
+ this.footerIds.update(ids => [...ids, id]);
156
+ this.destroyRef.onDestroy(() => this.unregisterFooterId(id));
157
+ }
158
+
159
+ /** @internal */
160
+ unregisterFooterId(id: string): void {
161
+ this.footerIds.update(ids => ids.filter(x => x !== id));
162
+ }
163
+
164
+ private recomputeAutoGrouped(): void {
165
+ if (!this.log) {
166
+ this.autoGrouped.set(false);
167
+ return;
168
+ }
169
+ const me = this.kjChatAuthor();
170
+ if (me === undefined) {
171
+ this.autoGrouped.set(false);
172
+ return;
173
+ }
174
+ const prev = this.previousChatRow();
175
+ if (!prev) {
176
+ this.autoGrouped.set(false);
177
+ return;
178
+ }
179
+ this.autoGrouped.set(prev.kjChatAuthor() === me);
180
+ }
181
+
182
+ private previousChatRow(): KjChat | null {
183
+ let sib = this.el.nativeElement.previousElementSibling as Element | null;
184
+ while (sib) {
185
+ const row = CHAT_ROW_REGISTRY.get(sib);
186
+ if (row) return row;
187
+ sib = sib.previousElementSibling;
188
+ }
189
+ return null;
190
+ }
191
+ }
@@ -0,0 +1,53 @@
1
+ import { InjectionToken, Provider } from '@angular/core';
2
+
3
+ /** Shape of the bubble's preset configuration. */
4
+ export interface KjChatBubbleConfig {
5
+ variants: string[];
6
+ sizes: string[];
7
+ defaults: { variant: string; size: string };
8
+ }
9
+
10
+ /**
11
+ * Default Chat-Bubble presets shipped by kouji-ui. Exported so consumers can
12
+ * spread when extending: `[...KJ_CHAT_BUBBLE_DEFAULTS.variants, 'brand']`.
13
+ */
14
+ export const KJ_CHAT_BUBBLE_DEFAULTS: KjChatBubbleConfig = {
15
+ variants: [
16
+ 'default',
17
+ 'primary',
18
+ 'secondary',
19
+ 'accent',
20
+ 'info',
21
+ 'success',
22
+ 'warning',
23
+ 'error',
24
+ ],
25
+ sizes: ['sm', 'md', 'lg'],
26
+ defaults: { variant: 'default', size: 'md' },
27
+ };
28
+
29
+ /**
30
+ * DI token for the active Chat-Bubble presets. Default factory yields
31
+ * `KJ_CHAT_BUBBLE_DEFAULTS`. Override via `provideKjChatBubble(…)` at the
32
+ * application or component scope.
33
+ */
34
+ export const KJ_CHAT_BUBBLE_CONFIG = new InjectionToken<KjChatBubbleConfig>(
35
+ 'kj.chat-bubble.config',
36
+ { factory: () => KJ_CHAT_BUBBLE_DEFAULTS },
37
+ );
38
+
39
+ /**
40
+ * Configures the Chat-Bubble presets for the enclosing injector. Replaces
41
+ * (does not merge) `variants` and `sizes`; spread `KJ_CHAT_BUBBLE_DEFAULTS.variants`
42
+ * to extend.
43
+ */
44
+ export function provideKjChatBubble(
45
+ config: Partial<KjChatBubbleConfig>,
46
+ ): Provider[] {
47
+ return [
48
+ {
49
+ provide: KJ_CHAT_BUBBLE_CONFIG,
50
+ useValue: { ...KJ_CHAT_BUBBLE_DEFAULTS, ...config },
51
+ },
52
+ ];
53
+ }
@@ -0,0 +1,21 @@
1
+ export { KjChatLog } from './chat-log';
2
+ export { KjChat } from './chat';
3
+ export { KjChatAvatar } from './chat-avatar';
4
+ export { KjChatHeader } from './chat-header';
5
+ export { KjChatBubble } from './chat-bubble';
6
+ export { KjChatFooter } from './chat-footer';
7
+ export {
8
+ KJ_CHAT,
9
+ KJ_CHAT_LOG,
10
+ type KjChatContext,
11
+ type KjChatLogContext,
12
+ type KjChatRole,
13
+ type KjChatSide,
14
+ type KjChatState,
15
+ } from './chat.context';
16
+ export {
17
+ KJ_CHAT_BUBBLE_CONFIG,
18
+ KJ_CHAT_BUBBLE_DEFAULTS,
19
+ provideKjChatBubble,
20
+ type KjChatBubbleConfig,
21
+ } from './config';
@@ -1,10 +1,14 @@
1
- import { Directive, inject, model } from '@angular/core';
1
+ import { Directive, inject, model } from '@angular/core';
2
2
  import { KjDisabled, KjFocusRing, KjFormControl } from '../primitives';
3
3
 
4
4
  /**
5
5
  * Adds checkbox semantics with Angular forms integration.
6
6
  * Supports `formControl`, `formControlName`, and `ngModel` bindings.
7
7
  *
8
+ * Apply `[kjCheckbox]` to any focusable element to get `role="checkbox"`,
9
+ * `aria-checked` reflection, Space/click toggle, and the shared focus-ring
10
+ * primitive — all composable onto custom markup with no styling attached.
11
+ *
8
12
  * @example
9
13
  * ```html
10
14
  * <div kjCheckbox tabindex="0" [(kjChecked)]="accepted" aria-label="Accept terms">Accept</div>
@@ -44,7 +48,10 @@ import { KjDisabled, KjFocusRing, KjFormControl } from '../primitives';
44
48
  * newsletter = signal(true);
45
49
  * }
46
50
  * ```
47
- * @category Core/Inputs
51
+ * @doc-category Core/Inputs
52
+ * @doc-name checkbox
53
+ * @doc-description Adds accessible checkbox behaviour and Angular forms support to any element you bring.
54
+ * @doc-is-main
48
55
  */
49
56
  @Directive({
50
57
  selector: '[kjCheckbox]',
@@ -0,0 +1,74 @@
1
+ import { InjectionToken, Signal } from '@angular/core';
2
+ import type { KjHsl, KjHsv, KjRgb } from './color-picker.utils';
3
+
4
+ /** Output formats supported by `KjColorPicker`. */
5
+ export type KjColorFormat = 'hex' | 'rgb' | 'hsl';
6
+
7
+ /**
8
+ * Public emitted value for `KjColorPicker`.
9
+ * Concrete shape depends on the active `kjFormat`:
10
+ * - `'hex'` → `string` (`'#rrggbb'` or `'#rrggbbaa'` when alpha is on).
11
+ * - `'rgb'` → `[r, g, b]` or `[r, g, b, a]` when alpha is on.
12
+ * - `'hsl'` → `{ h, s, l }` or `{ h, s, l, a }` when alpha is on.
13
+ */
14
+ export type KjColorValue =
15
+ | string
16
+ | readonly [number, number, number]
17
+ | readonly [number, number, number, number]
18
+ | { h: number; s: number; l: number }
19
+ | { h: number; s: number; l: number; a: number };
20
+
21
+ /** A single preset swatch in the color picker's palette grid. */
22
+ export interface KjColorPreset {
23
+ /** Any parseable hex color string. */
24
+ readonly value: string;
25
+ /** Optional accessible name announced to AT in place of the raw hex. */
26
+ readonly label?: string;
27
+ }
28
+
29
+ /**
30
+ * Shared state container exposed to every `KjColorPicker` sub-directive
31
+ * via the `KJ_COLOR_PICKER` token. The root directive is the sole writer;
32
+ * sub-directives read derived signals and call mutator methods.
33
+ */
34
+ export interface KjColorPickerContext {
35
+ // Canonical state
36
+ readonly hue: Signal<number>;
37
+ readonly saturation: Signal<number>;
38
+ readonly value: Signal<number>;
39
+ readonly alpha: Signal<number>;
40
+
41
+ // Derived projections
42
+ readonly hex: Signal<string>;
43
+ readonly rgb: Signal<KjRgb>;
44
+ readonly hsv: Signal<KjHsv>;
45
+ readonly hsl: Signal<KjHsl>;
46
+
47
+ // Mutators
48
+ setHue(h: number): void;
49
+ setSaturationValue(s: number, v: number): void;
50
+ setAlpha(a: number): void;
51
+ setHex(hex: string): boolean;
52
+ setRgb(r: number, g: number, b: number, a?: number): void;
53
+
54
+ // UI state
55
+ readonly open: Signal<boolean>;
56
+ readonly disabled: Signal<boolean>;
57
+ readonly format: Signal<KjColorFormat>;
58
+ readonly showAlpha: Signal<boolean>;
59
+ readonly presets: Signal<readonly KjColorPreset[]>;
60
+ setOpen(open: boolean): void;
61
+
62
+ // Wiring ids
63
+ readonly panelId: Signal<string>;
64
+ readonly triggerId: Signal<string>;
65
+ }
66
+
67
+ /**
68
+ * DI token consumed by every `KjColorPicker*` sub-directive. Provided by
69
+ * the root `KjColorPicker` directive — sub-directives outside that
70
+ * context throw at injection time in dev.
71
+ */
72
+ export const KJ_COLOR_PICKER = new InjectionToken<KjColorPickerContext>(
73
+ 'KjColorPicker',
74
+ );
@@ -0,0 +1,241 @@
1
+ import { render, fireEvent } from '@testing-library/angular';
2
+ import { FormControl, ReactiveFormsModule } from '@angular/forms';
3
+ import { axe, toHaveNoViolations } from 'jest-axe';
4
+ import {
5
+ KjColorPicker,
6
+ KjColorPickerAlphaSlider,
7
+ KjColorPickerArea,
8
+ KjColorPickerHueSlider,
9
+ KjColorPickerInput,
10
+ KjColorPickerPanel,
11
+ KjColorPickerTrigger,
12
+ } from './color-picker';
13
+ import {
14
+ kjHsvToRgb,
15
+ kjParseHex,
16
+ kjRgbToHex,
17
+ kjRgbToHsv,
18
+ } from './color-picker.utils';
19
+
20
+ expect.extend(toHaveNoViolations);
21
+
22
+ const directiveImports = [
23
+ KjColorPicker,
24
+ KjColorPickerTrigger,
25
+ KjColorPickerPanel,
26
+ KjColorPickerArea,
27
+ KjColorPickerHueSlider,
28
+ KjColorPickerAlphaSlider,
29
+ KjColorPickerInput,
30
+ ];
31
+
32
+ const fullTemplate = `
33
+ <div kjColorPicker [formControl]="ctrl" [kjShowAlpha]="showAlpha">
34
+ <button kjColorPickerTrigger>open</button>
35
+ <div kjColorPickerPanel>
36
+ <div kjColorPickerArea></div>
37
+ <input kjColorPickerHueSlider />
38
+ <input kjColorPickerAlphaSlider />
39
+ <input kjColorPickerInput />
40
+ </div>
41
+ </div>
42
+ `;
43
+
44
+ /** When `bodyPortal` mounts the panel out to <body>, search there too. */
45
+ function findPanel(container: HTMLElement): HTMLElement {
46
+ return (
47
+ container.querySelector('[kjColorPickerPanel]') ??
48
+ document.body.querySelector('[kjColorPickerPanel]')!
49
+ ) as HTMLElement;
50
+ }
51
+
52
+ describe('color-picker utils', () => {
53
+ it('parses 6-char hex with leading hash', () => {
54
+ expect(kjParseHex('#ff0000')).toEqual({ r: 255, g: 0, b: 0, a: 1 });
55
+ });
56
+
57
+ it('parses 3-char hex without leading hash', () => {
58
+ expect(kjParseHex('f00')).toEqual({ r: 255, g: 0, b: 0, a: 1 });
59
+ });
60
+
61
+ it('parses 8-char hex with alpha', () => {
62
+ const r = kjParseHex('#ff000080');
63
+ expect(r?.r).toBe(255);
64
+ expect(r?.g).toBe(0);
65
+ expect(r?.b).toBe(0);
66
+ expect(r?.a).toBeCloseTo(0x80 / 255, 5);
67
+ });
68
+
69
+ it('returns null for invalid hex', () => {
70
+ expect(kjParseHex('not-a-color')).toBeNull();
71
+ expect(kjParseHex('#xyzxyz')).toBeNull();
72
+ expect(kjParseHex('#ff')).toBeNull();
73
+ });
74
+
75
+ it('round-trips RGB → HSV → RGB for primary red', () => {
76
+ const hsv = kjRgbToHsv({ r: 255, g: 0, b: 0, a: 1 });
77
+ const back = kjHsvToRgb(hsv);
78
+ expect(back.r).toBe(255);
79
+ expect(back.g).toBe(0);
80
+ expect(back.b).toBe(0);
81
+ });
82
+
83
+ it('round-trips RGB → HSV → RGB for arbitrary color', () => {
84
+ const original = { r: 184, g: 245, b: 0, a: 1 };
85
+ const hsv = kjRgbToHsv(original);
86
+ const back = kjHsvToRgb(hsv);
87
+ expect(back.r).toBe(original.r);
88
+ expect(back.g).toBe(original.g);
89
+ expect(back.b).toBe(original.b);
90
+ });
91
+
92
+ it('formats RGB to lowercase six-char hex by default', () => {
93
+ expect(kjRgbToHex({ r: 0xab, g: 0xcd, b: 0xef, a: 1 }, false))
94
+ .toBe('#abcdef');
95
+ });
96
+
97
+ it('formats RGB to eight-char hex when alpha is requested', () => {
98
+ expect(kjRgbToHex({ r: 0, g: 0, b: 0, a: 0.5 }, true))
99
+ .toBe('#00000080');
100
+ });
101
+ });
102
+
103
+ describe('KjColorPicker', () => {
104
+ function makeCtrl(initial = '#ff0000') {
105
+ return new FormControl(initial);
106
+ }
107
+
108
+ it('panel is hidden by default', async () => {
109
+ const { container } = await render(fullTemplate, {
110
+ imports: [...directiveImports, ReactiveFormsModule],
111
+ componentProperties: { ctrl: makeCtrl(), showAlpha: false },
112
+ });
113
+ expect(findPanel(container)).toHaveAttribute('hidden', '');
114
+ });
115
+
116
+ it('clicking the trigger opens the panel', async () => {
117
+ const { container } = await render(fullTemplate, {
118
+ imports: [...directiveImports, ReactiveFormsModule],
119
+ componentProperties: { ctrl: makeCtrl(), showAlpha: false },
120
+ });
121
+ fireEvent.click(container.querySelector('[kjColorPickerTrigger]')!);
122
+ expect(findPanel(container)).not.toHaveAttribute('hidden');
123
+ });
124
+
125
+ it('trigger reflects aria-expanded based on open state', async () => {
126
+ const { container } = await render(fullTemplate, {
127
+ imports: [...directiveImports, ReactiveFormsModule],
128
+ componentProperties: { ctrl: makeCtrl(), showAlpha: false },
129
+ });
130
+ const trigger = container.querySelector('[kjColorPickerTrigger]')!;
131
+ expect(trigger).toHaveAttribute('aria-expanded', 'false');
132
+ fireEvent.click(trigger);
133
+ expect(trigger).toHaveAttribute('aria-expanded', 'true');
134
+ });
135
+
136
+ it('trigger has aria-haspopup="dialog"', async () => {
137
+ const { container } = await render(fullTemplate, {
138
+ imports: [...directiveImports, ReactiveFormsModule],
139
+ componentProperties: { ctrl: makeCtrl(), showAlpha: false },
140
+ });
141
+ const trigger = container.querySelector('[kjColorPickerTrigger]')!;
142
+ expect(trigger).toHaveAttribute('aria-haspopup', 'dialog');
143
+ });
144
+
145
+ it('panel has role="dialog"', async () => {
146
+ const { container } = await render(fullTemplate, {
147
+ imports: [...directiveImports, ReactiveFormsModule],
148
+ componentProperties: { ctrl: makeCtrl(), showAlpha: false },
149
+ });
150
+ const panel = findPanel(container);
151
+ expect(panel).toHaveAttribute('role', 'dialog');
152
+ });
153
+
154
+ it('sat/value area has role="slider" with aria-valuetext', async () => {
155
+ const { container } = await render(fullTemplate, {
156
+ imports: [...directiveImports, ReactiveFormsModule],
157
+ componentProperties: { ctrl: makeCtrl(), showAlpha: false },
158
+ });
159
+ const area = container.querySelector('[kjColorPickerArea]')!;
160
+ expect(area).toHaveAttribute('role', 'slider');
161
+ expect(area.getAttribute('aria-valuetext')).toMatch(/saturation/i);
162
+ });
163
+
164
+ it('arrow keys on the sat/value area update aria-valuenow', async () => {
165
+ const { container } = await render(fullTemplate, {
166
+ imports: [...directiveImports, ReactiveFormsModule],
167
+ componentProperties: { ctrl: makeCtrl(), showAlpha: false },
168
+ });
169
+ fireEvent.click(container.querySelector('[kjColorPickerTrigger]')!);
170
+ const area = container.querySelector('[kjColorPickerArea]')
171
+ ?? document.body.querySelector('[kjColorPickerArea]')!;
172
+ // Initial red has saturation=1; ArrowLeft is the unambiguous direction to move.
173
+ const before = area.getAttribute('aria-valuenow');
174
+ fireEvent.keyDown(area, { key: 'ArrowLeft' });
175
+ const after = area.getAttribute('aria-valuenow');
176
+ expect(after).not.toBe(before);
177
+ });
178
+
179
+ it('hex input commits a parseable value on Enter', async () => {
180
+ const ctrl = makeCtrl();
181
+ const { container, fixture } = await render(fullTemplate, {
182
+ imports: [...directiveImports, ReactiveFormsModule],
183
+ componentProperties: { ctrl, showAlpha: false },
184
+ });
185
+ fireEvent.click(container.querySelector('[kjColorPickerTrigger]')!);
186
+ const input = (container.querySelector('[kjColorPickerInput]')
187
+ ?? document.body.querySelector('[kjColorPickerInput]')) as HTMLInputElement;
188
+ input.value = '#00ff00';
189
+ fireEvent.input(input, { target: input });
190
+ fireEvent.keyDown(input, { key: 'Enter' });
191
+ fixture.detectChanges();
192
+ expect(ctrl.value).toBe('#00ff00');
193
+ });
194
+
195
+ it('writeValue parses external hex and reflects it on the trigger', async () => {
196
+ const ctrl = makeCtrl('#000000');
197
+ const { container, fixture } = await render(fullTemplate, {
198
+ imports: [...directiveImports, ReactiveFormsModule],
199
+ componentProperties: { ctrl, showAlpha: false },
200
+ });
201
+ ctrl.setValue('#0000ff');
202
+ fixture.detectChanges();
203
+ await fixture.whenStable();
204
+ fixture.detectChanges();
205
+ const trigger = container.querySelector('[kjColorPickerTrigger]') as HTMLElement;
206
+ expect(trigger.style.getPropertyValue('--kj-color-picker-current'))
207
+ .toBe('#0000ff');
208
+ });
209
+
210
+ it('hue slider has aria-label="Hue" and is a native range input', async () => {
211
+ const { container } = await render(fullTemplate, {
212
+ imports: [...directiveImports, ReactiveFormsModule],
213
+ componentProperties: { ctrl: makeCtrl(), showAlpha: false },
214
+ });
215
+ const hue = container.querySelector('[kjColorPickerHueSlider]')!;
216
+ expect(hue).toHaveAttribute('aria-label', 'Hue');
217
+ expect(hue).toHaveAttribute('type', 'range');
218
+ });
219
+
220
+ it('alpha slider mounts but is disabled until kjShowAlpha is true', async () => {
221
+ const { container, fixture } = await render(fullTemplate, {
222
+ imports: [...directiveImports, ReactiveFormsModule],
223
+ componentProperties: { ctrl: makeCtrl(), showAlpha: false },
224
+ });
225
+ const alpha = container.querySelector('[kjColorPickerAlphaSlider]') as HTMLInputElement;
226
+ expect(alpha.disabled).toBe(true);
227
+ (fixture.componentInstance as { showAlpha: boolean }).showAlpha = true;
228
+ fixture.detectChanges();
229
+ expect(alpha.disabled).toBe(false);
230
+ });
231
+
232
+ it('panel passes axe audit when open', async () => {
233
+ const { container } = await render(fullTemplate, {
234
+ imports: [...directiveImports, ReactiveFormsModule],
235
+ componentProperties: { ctrl: makeCtrl(), showAlpha: false },
236
+ });
237
+ fireEvent.click(container.querySelector('[kjColorPickerTrigger]')!);
238
+ const results = await axe(document.body);
239
+ expect(results).toHaveNoViolations();
240
+ });
241
+ });