@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
@@ -1,10 +1,12 @@
1
- import {
1
+ import {
2
2
  Directive,
3
3
  ElementRef,
4
4
  InjectionToken,
5
5
  contentChildren,
6
6
  effect,
7
+ forwardRef,
7
8
  inject,
9
+ input,
8
10
  signal,
9
11
  } from '@angular/core';
10
12
 
@@ -21,7 +23,9 @@ export const KJ_ROVING_TABINDEX = new InjectionToken<KjRovingTabindex>(
21
23
  * ```html
22
24
  * <button kjRovingTabindexItem>Item</button>
23
25
  * ```
24
- * @category Core/Accessibility
26
+ * @doc-category Core/Accessibility
27
+ * @doc
28
+ * @doc-name a11y
25
29
  */
26
30
  @Directive({
27
31
  selector: '[kjRovingTabindexItem]',
@@ -48,21 +52,37 @@ export class KjRovingTabindexItemDirective {
48
52
  * <button kjRovingTabindexItem>Italic</button>
49
53
  * </div>
50
54
  * ```
51
- * @category Core/Accessibility
55
+ * @doc-category Core/Accessibility
56
+ * @doc
57
+ * @doc-name a11y
52
58
  */
53
59
  @Directive({
54
60
  selector: '[kjRovingTabindex]',
55
61
  standalone: true,
56
- providers: [{ provide: KJ_ROVING_TABINDEX, useExisting: KjRovingTabindex }],
62
+ providers: [{ provide: KJ_ROVING_TABINDEX, useExisting: forwardRef(() => KjRovingTabindex) }],
57
63
  host: {
58
64
  '(keydown)': 'onKeydown($event)',
59
65
  '(focusin)': 'onFocusIn($event)',
60
66
  },
61
67
  })
62
68
  export class KjRovingTabindex {
63
- private readonly items = contentChildren(KjRovingTabindexItemDirective);
69
+ private readonly host = inject(ElementRef<HTMLElement>);
70
+ private readonly items = contentChildren(KjRovingTabindexItemDirective, {
71
+ descendants: true,
72
+ });
64
73
  private readonly activeIndex = signal(0);
65
74
 
75
+ /**
76
+ * Restricts arrow-key navigation to a single axis.
77
+ * - `'horizontal'`: only ArrowLeft / ArrowRight move focus.
78
+ * - `'vertical'`: only ArrowUp / ArrowDown move focus.
79
+ * - `'both'`: all four arrow keys move focus (default — preserves prior behaviour).
80
+ *
81
+ * Under `'horizontal'`, ArrowLeft / ArrowRight are swapped when the host
82
+ * element resolves to `dir="rtl"`.
83
+ */
84
+ readonly kjRovingOrientation = input<'horizontal' | 'vertical' | 'both'>('both');
85
+
66
86
  constructor() {
67
87
  effect(() => {
68
88
  const all = this.items();
@@ -70,6 +90,20 @@ export class KjRovingTabindex {
70
90
  });
71
91
  }
72
92
 
93
+ private resolveRtl(): boolean {
94
+ const el = this.host.nativeElement;
95
+ if (typeof getComputedStyle === 'function') {
96
+ const direction = getComputedStyle(el).direction;
97
+ if (direction === 'rtl') return true;
98
+ if (direction === 'ltr') {
99
+ // Some test environments (jsdom) return the default 'ltr' even when an
100
+ // ancestor sets `dir="rtl"`. Fall through to attribute lookup.
101
+ }
102
+ }
103
+ const dirHost = el.closest('[dir]') as HTMLElement | null;
104
+ return dirHost?.dir.toLowerCase() === 'rtl';
105
+ }
106
+
73
107
  /** @internal — syncs activeIndex when focus moves into an item programmatically */
74
108
  onFocusIn(event: FocusEvent): void {
75
109
  const all = this.items();
@@ -85,9 +119,24 @@ export class KjRovingTabindex {
85
119
  if (!all.length) return;
86
120
  let next = this.activeIndex();
87
121
 
88
- if (event.key === 'ArrowRight' || event.key === 'ArrowDown') {
122
+ const orientation = this.kjRovingOrientation();
123
+ const horizontalActive = orientation === 'horizontal' || orientation === 'both';
124
+ const verticalActive = orientation === 'vertical' || orientation === 'both';
125
+
126
+ // Resolve directionality lazily; only matters when horizontal arrows are active.
127
+ // Prefer computed style; fall back to the nearest `dir` attribute (jsdom does
128
+ // not propagate ancestor `dir` to computed style in all cases).
129
+ const isRtl = horizontalActive && this.resolveRtl();
130
+ const forwardKey = isRtl ? 'ArrowLeft' : 'ArrowRight';
131
+ const backwardKey = isRtl ? 'ArrowRight' : 'ArrowLeft';
132
+
133
+ if (horizontalActive && event.key === forwardKey) {
134
+ next = (next + 1) % all.length;
135
+ } else if (horizontalActive && event.key === backwardKey) {
136
+ next = (next - 1 + all.length) % all.length;
137
+ } else if (verticalActive && event.key === 'ArrowDown') {
89
138
  next = (next + 1) % all.length;
90
- } else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') {
139
+ } else if (verticalActive && event.key === 'ArrowUp') {
91
140
  next = (next - 1 + all.length) % all.length;
92
141
  } else if (event.key === 'Home') {
93
142
  next = 0;
@@ -1,4 +1,4 @@
1
- import { Directive } from '@angular/core';
1
+ import { Directive } from '@angular/core';
2
2
 
3
3
  /**
4
4
  * Visually hides an element while keeping it accessible to screen readers.
@@ -11,7 +11,9 @@
11
11
  * <span kjVisuallyHidden>Close dialog</span>
12
12
  * </button>
13
13
  * ```
14
- * @category Core/Accessibility
14
+ * @doc-category Core/Accessibility
15
+ * @doc
16
+ * @doc-name a11y
15
17
  */
16
18
  @Directive({
17
19
  selector: '[kjVisuallyHidden]',
@@ -1,17 +1,39 @@
1
1
  import { InjectionToken, Signal } from '@angular/core';
2
2
 
3
+ /** Accordion mode: `'single'` (one item open at a time) or `'multiple'` (any number open). */
4
+ export type KjAccordionType = 'single' | 'multiple';
5
+
3
6
  /** Context interface for the root accordion directive. */
4
7
  export interface KjAccordionContext {
8
+ /** Set of currently open item values. */
5
9
  readonly openIds: Signal<ReadonlySet<string>>;
10
+ /** Current mode: single or multiple. */
11
+ readonly type: Signal<KjAccordionType>;
12
+ /** Whether opt-in arrow-key roving navigation is enabled. */
13
+ readonly arrowNavigation: Signal<boolean>;
14
+ /** Toggle an item by its string value. In single mode, closes others first. */
6
15
  toggle(id: string): void;
16
+ /** Whether the given item value is currently open. */
7
17
  isOpen(id: string): boolean;
8
18
  }
9
19
 
10
20
  /** Context interface for an individual accordion item directive. */
11
21
  export interface KjAccordionItemContext {
22
+ /** Stable string value identifying the item within the parent accordion. */
23
+ readonly value: Signal<string>;
24
+ /** Whether this item is currently expanded. */
12
25
  readonly expanded: Signal<boolean>;
26
+ /** Whether this item is disabled (toggle is a no-op, trigger announces aria-disabled). */
27
+ readonly disabled: Signal<boolean>;
28
+ /** Stable id for the trigger element — `aria-labelledby` target on the content region. */
29
+ readonly headerId: Signal<string>;
30
+ /** Stable id for the content region — `aria-controls` target on the trigger. */
31
+ readonly contentId: Signal<string>;
32
+ /** Toggle this item's expanded state. No-op when disabled. */
13
33
  toggle(): void;
34
+ /** Open this item. No-op when disabled. */
14
35
  open(): void;
36
+ /** Close this item. No-op when disabled. */
15
37
  close(): void;
16
38
  }
17
39
 
@@ -1,32 +1,242 @@
1
- import { render, fireEvent } from '@testing-library/angular';
1
+ import { Component, signal } from '@angular/core';
2
+ import { render, fireEvent } from '@testing-library/angular';
2
3
  import { axe, toHaveNoViolations } from 'jest-axe';
3
- import { KjAccordion, KjAccordionItem, KjAccordionTrigger, KjAccordionContent } from './accordion';
4
+ import {
5
+ KjAccordion,
6
+ KjAccordionItem,
7
+ KjAccordionTrigger,
8
+ KjAccordionContent,
9
+ } from './accordion';
4
10
 
5
11
  expect.extend(toHaveNoViolations);
12
+
6
13
  const imports = [KjAccordion, KjAccordionItem, KjAccordionTrigger, KjAccordionContent];
7
- const template = `<div kjAccordion><div kjAccordionItem [kjItemValue]="'i1'"><button kjAccordionTrigger>Section</button><div kjAccordionContent>Content</div></div></div>`;
14
+
15
+ const baseTemplate = `
16
+ <div kjAccordion>
17
+ <div kjAccordionItem kjItemValue="i1">
18
+ <button kjAccordionTrigger>Section 1</button>
19
+ <div kjAccordionContent>Body 1</div>
20
+ </div>
21
+ <div kjAccordionItem kjItemValue="i2">
22
+ <button kjAccordionTrigger>Section 2</button>
23
+ <div kjAccordionContent>Body 2</div>
24
+ </div>
25
+ </div>
26
+ `;
8
27
 
9
28
  describe('KjAccordion', () => {
10
29
  it('content hidden by default', async () => {
11
- const { container } = await render(template, { imports });
12
- expect(container.querySelector('[kjAccordionContent]')).toHaveAttribute('hidden', '');
13
- });
14
- it('click trigger expands', async () => {
15
- const { container } = await render(template, { imports });
16
- fireEvent.click(container.querySelector('[kjAccordionTrigger]')!);
17
- expect(container.querySelector('[kjAccordionContent]')).not.toHaveAttribute('hidden');
18
- });
19
- it('trigger has aria-expanded=false when closed', async () => {
20
- const { container } = await render(template, { imports });
21
- expect(container.querySelector('[kjAccordionTrigger]')).toHaveAttribute('aria-expanded', 'false');
22
- });
23
- it('trigger has aria-expanded=true when open', async () => {
24
- const { container } = await render(template, { imports });
25
- fireEvent.click(container.querySelector('[kjAccordionTrigger]')!);
26
- expect(container.querySelector('[kjAccordionTrigger]')).toHaveAttribute('aria-expanded', 'true');
27
- });
28
- it('passes axe audit', async () => {
29
- const { container } = await render(template, { imports });
30
- expect(await axe(container)).toHaveNoViolations();
30
+ const { container } = await render(baseTemplate, { imports });
31
+ const content = container.querySelector('[kjAccordionContent]');
32
+ // Visibility is driven by `[data-state]` + CSS animation (the element
33
+ // stays in the DOM so open/close can transition). `aria-hidden` + `inert`
34
+ // keep the closed panel out of the AT tree and tab sequence.
35
+ expect(content).toHaveAttribute('data-state', 'closed');
36
+ expect(content).toHaveAttribute('aria-hidden', 'true');
37
+ expect(content).toHaveAttribute('inert', '');
38
+ });
39
+
40
+ it('click trigger expands and updates aria-expanded', async () => {
41
+ const { container } = await render(baseTemplate, { imports });
42
+ const trigger = container.querySelector('[kjAccordionTrigger]')!;
43
+ expect(trigger).toHaveAttribute('aria-expanded', 'false');
44
+ fireEvent.click(trigger);
45
+ expect(trigger).toHaveAttribute('aria-expanded', 'true');
46
+ const content = container.querySelector('[kjAccordionContent]');
47
+ expect(content).toHaveAttribute('data-state', 'open');
48
+ expect(content).not.toHaveAttribute('aria-hidden');
49
+ expect(content).not.toHaveAttribute('inert');
50
+ });
51
+
52
+ it('single mode closes other items when one opens', async () => {
53
+ const { container } = await render(baseTemplate, { imports });
54
+ const triggers = container.querySelectorAll('[kjAccordionTrigger]');
55
+ fireEvent.click(triggers[0]);
56
+ fireEvent.click(triggers[1]);
57
+ expect(triggers[0]).toHaveAttribute('aria-expanded', 'false');
58
+ expect(triggers[1]).toHaveAttribute('aria-expanded', 'true');
59
+ });
60
+
61
+ it('multiple mode keeps multiple items open', async () => {
62
+ const { container } = await render(
63
+ `<div kjAccordion kjType="multiple">
64
+ <div kjAccordionItem kjItemValue="a"><button kjAccordionTrigger>A</button><div kjAccordionContent>A</div></div>
65
+ <div kjAccordionItem kjItemValue="b"><button kjAccordionTrigger>B</button><div kjAccordionContent>B</div></div>
66
+ </div>`,
67
+ { imports },
68
+ );
69
+ const triggers = container.querySelectorAll('[kjAccordionTrigger]');
70
+ fireEvent.click(triggers[0]);
71
+ fireEvent.click(triggers[1]);
72
+ expect(triggers[0]).toHaveAttribute('aria-expanded', 'true');
73
+ expect(triggers[1]).toHaveAttribute('aria-expanded', 'true');
74
+ });
75
+
76
+ describe('ARIA wiring', () => {
77
+ it('trigger gets aria-controls pointing at the content id', async () => {
78
+ const { container } = await render(baseTemplate, { imports });
79
+ const trigger = container.querySelector('[kjAccordionTrigger]') as HTMLElement;
80
+ const content = container.querySelector('[kjAccordionContent]') as HTMLElement;
81
+ const controls = trigger.getAttribute('aria-controls');
82
+ expect(controls).toBeTruthy();
83
+ expect(controls).toBe(content.id);
84
+ });
85
+
86
+ it('content has role="region" and aria-labelledby pointing at the trigger id', async () => {
87
+ const { container } = await render(baseTemplate, { imports });
88
+ const trigger = container.querySelector('[kjAccordionTrigger]') as HTMLElement;
89
+ const content = container.querySelector('[kjAccordionContent]') as HTMLElement;
90
+ expect(content).toHaveAttribute('role', 'region');
91
+ const labelledBy = content.getAttribute('aria-labelledby');
92
+ expect(labelledBy).toBeTruthy();
93
+ expect(labelledBy).toBe(trigger.id);
94
+ });
95
+
96
+ it('header / content ids are stable across change detection', async () => {
97
+ const { container, fixture } = await render(baseTemplate, { imports });
98
+ const trigger = container.querySelector('[kjAccordionTrigger]') as HTMLElement;
99
+ const before = trigger.id;
100
+ fireEvent.click(trigger);
101
+ fixture.detectChanges();
102
+ expect(trigger.id).toBe(before);
103
+ });
104
+
105
+ it('passes axe audit (closed)', async () => {
106
+ const { container } = await render(baseTemplate, { imports });
107
+ expect(await axe(container)).toHaveNoViolations();
108
+ });
109
+
110
+ it('passes axe audit (open)', async () => {
111
+ const { container } = await render(baseTemplate, { imports });
112
+ fireEvent.click(container.querySelector('[kjAccordionTrigger]')!);
113
+ expect(await axe(container)).toHaveNoViolations();
114
+ });
115
+ });
116
+
117
+ describe('disabled item', () => {
118
+ it('toggle is a no-op and aria-disabled is set on the trigger', async () => {
119
+ const { container } = await render(
120
+ `<div kjAccordion>
121
+ <div kjAccordionItem kjItemValue="locked" kjItemDisabled>
122
+ <button kjAccordionTrigger>Locked</button>
123
+ <div kjAccordionContent>Body</div>
124
+ </div>
125
+ </div>`,
126
+ { imports },
127
+ );
128
+ const trigger = container.querySelector('[kjAccordionTrigger]') as HTMLElement;
129
+ expect(trigger).toHaveAttribute('aria-disabled', 'true');
130
+ fireEvent.click(trigger);
131
+ expect(trigger).toHaveAttribute('aria-expanded', 'false');
132
+ });
133
+ });
134
+
135
+ describe('two-way value binding', () => {
136
+ @Component({
137
+ standalone: true,
138
+ imports,
139
+ template: `
140
+ <div kjAccordion [(kjValue)]="open">
141
+ <div kjAccordionItem kjItemValue="a"><button kjAccordionTrigger>A</button><div kjAccordionContent>A</div></div>
142
+ <div kjAccordionItem kjItemValue="b"><button kjAccordionTrigger>B</button><div kjAccordionContent>B</div></div>
143
+ </div>
144
+ `,
145
+ })
146
+ class SingleHost {
147
+ readonly open = signal<string>('a');
148
+ }
149
+
150
+ it('opens the seeded item from the bound value (single mode)', async () => {
151
+ const { container, fixture } = await render(SingleHost);
152
+ await fixture.whenStable();
153
+ fixture.detectChanges();
154
+ const triggers = container.querySelectorAll('[kjAccordionTrigger]');
155
+ expect(triggers[0]).toHaveAttribute('aria-expanded', 'true');
156
+ expect(triggers[1]).toHaveAttribute('aria-expanded', 'false');
157
+ });
158
+
159
+ it('writes the open value back to the parent signal on click', async () => {
160
+ const { container, fixture } = await render(SingleHost);
161
+ await fixture.whenStable();
162
+ fireEvent.click(container.querySelectorAll('[kjAccordionTrigger]')[1]);
163
+ fixture.detectChanges();
164
+ expect(fixture.componentInstance.open()).toBe('b');
165
+ });
166
+
167
+ @Component({
168
+ standalone: true,
169
+ imports,
170
+ template: `
171
+ <div kjAccordion kjType="multiple" [(kjValue)]="open">
172
+ <div kjAccordionItem kjItemValue="a"><button kjAccordionTrigger>A</button><div kjAccordionContent>A</div></div>
173
+ <div kjAccordionItem kjItemValue="b"><button kjAccordionTrigger>B</button><div kjAccordionContent>B</div></div>
174
+ </div>
175
+ `,
176
+ })
177
+ class MultipleHost {
178
+ readonly open = signal<string[]>(['a']);
179
+ }
180
+
181
+ it('multiple mode emits array updates', async () => {
182
+ const { container, fixture } = await render(MultipleHost);
183
+ await fixture.whenStable();
184
+ fireEvent.click(container.querySelectorAll('[kjAccordionTrigger]')[1]);
185
+ fixture.detectChanges();
186
+ expect(fixture.componentInstance.open()).toEqual(['a', 'b']);
187
+ });
188
+ });
189
+
190
+ describe('arrow-key navigation (opt-in)', () => {
191
+ it('does not move focus when kjArrowNavigation is off (default)', async () => {
192
+ const { container } = await render(baseTemplate, { imports });
193
+ const triggers = container.querySelectorAll<HTMLElement>('[kjAccordionTrigger]');
194
+ triggers[0].focus();
195
+ fireEvent.keyDown(triggers[0], { key: 'ArrowDown' });
196
+ // Without arrow-nav opted in, focus stays on the first trigger.
197
+ expect(document.activeElement).toBe(triggers[0]);
198
+ });
199
+
200
+ it('ArrowDown / ArrowUp / Home / End move focus when kjArrowNavigation is on', async () => {
201
+ const { container } = await render(
202
+ `<div kjAccordion kjArrowNavigation kjType="multiple">
203
+ <div kjAccordionItem kjItemValue="a"><button kjAccordionTrigger>A</button><div kjAccordionContent>A</div></div>
204
+ <div kjAccordionItem kjItemValue="b"><button kjAccordionTrigger>B</button><div kjAccordionContent>B</div></div>
205
+ <div kjAccordionItem kjItemValue="c"><button kjAccordionTrigger>C</button><div kjAccordionContent>C</div></div>
206
+ </div>`,
207
+ { imports },
208
+ );
209
+ const triggers = container.querySelectorAll<HTMLElement>('[kjAccordionTrigger]');
210
+ triggers[0].focus();
211
+ fireEvent.keyDown(triggers[0], { key: 'ArrowDown' });
212
+ expect(document.activeElement).toBe(triggers[1]);
213
+ fireEvent.keyDown(triggers[1], { key: 'ArrowDown' });
214
+ expect(document.activeElement).toBe(triggers[2]);
215
+ fireEvent.keyDown(triggers[2], { key: 'ArrowDown' });
216
+ // Wraps to first.
217
+ expect(document.activeElement).toBe(triggers[0]);
218
+ fireEvent.keyDown(triggers[0], { key: 'ArrowUp' });
219
+ // Wraps backwards to last.
220
+ expect(document.activeElement).toBe(triggers[2]);
221
+ fireEvent.keyDown(triggers[2], { key: 'Home' });
222
+ expect(document.activeElement).toBe(triggers[0]);
223
+ fireEvent.keyDown(triggers[0], { key: 'End' });
224
+ expect(document.activeElement).toBe(triggers[2]);
225
+ });
226
+
227
+ it('skips disabled items during arrow navigation', async () => {
228
+ const { container } = await render(
229
+ `<div kjAccordion kjArrowNavigation>
230
+ <div kjAccordionItem kjItemValue="a"><button kjAccordionTrigger>A</button></div>
231
+ <div kjAccordionItem kjItemValue="b" kjItemDisabled><button kjAccordionTrigger>B</button></div>
232
+ <div kjAccordionItem kjItemValue="c"><button kjAccordionTrigger>C</button></div>
233
+ </div>`,
234
+ { imports },
235
+ );
236
+ const triggers = container.querySelectorAll<HTMLElement>('[kjAccordionTrigger]');
237
+ triggers[0].focus();
238
+ fireEvent.keyDown(triggers[0], { key: 'ArrowDown' });
239
+ expect(document.activeElement).toBe(triggers[2]);
240
+ });
31
241
  });
32
242
  });