@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,63 @@
1
+ import { InjectionToken, Signal } from '@angular/core';
2
+
3
+ /**
4
+ * A single node in a tree structure. Nodes may carry an arbitrary value
5
+ * of type `T`, a display label, an optional disabled flag, and optional
6
+ * child nodes (making the node a branch rather than a leaf).
7
+ */
8
+ export interface KjTreeNode<T = unknown> {
9
+ /** The value represented by this node. Used for selection and identity. */
10
+ readonly value: T;
11
+ /** Human-readable display label shown in the tree. */
12
+ readonly label: string;
13
+ /** When `true`, the node cannot be selected or expanded by the user. */
14
+ readonly disabled?: boolean;
15
+ /** Child nodes. When present and non-empty, this node is a branch. */
16
+ readonly children?: readonly KjTreeNode<T>[];
17
+ }
18
+
19
+ /**
20
+ * Context interface exposed by `KjTreeSelect` to descendant directives
21
+ * (panel, nodes, toggle buttons) via the `KJ_TREE_SELECT` injection token.
22
+ *
23
+ * Selection state (current value, membership checks, toggles) is **not**
24
+ * part of this context — it lives on the shared `KjSelectionModel`
25
+ * provided by the root and consumed by `KjListItem` via the
26
+ * `KJ_LIST_NAVIGATOR_CONFIG` plumbing. This interface only carries the
27
+ * **tree-specific** surface: node data, expansion state, and the public
28
+ * `selectionMode` input value (used by panel ARIA to drive
29
+ * `aria-multiselectable`).
30
+ *
31
+ * Open/close state lives on the overlay primitives and is not part of this
32
+ * context — descendants that need it inject `KjOverlayController` directly.
33
+ */
34
+ export interface KjTreeSelectContext<T = unknown> {
35
+ /** Stable id for the panel element (used for `aria-controls`). */
36
+ readonly panelId: string;
37
+ /** Current selection mode: `'single'` or `'multiple'`. */
38
+ readonly selectionMode: Signal<'single' | 'multiple'>;
39
+ /** Set of node IDs that are currently expanded. */
40
+ readonly expandedIds: Signal<ReadonlySet<string>>;
41
+ /** Set of node **values** that are currently expanded. */
42
+ readonly expandedValues: Signal<ReadonlySet<unknown>>;
43
+ /** The full tree data. */
44
+ readonly nodes: Signal<readonly KjTreeNode<T>[]>;
45
+
46
+ /**
47
+ * Toggle expand/collapse for the given node DOM id. Does nothing for leaf
48
+ * nodes (those without children).
49
+ */
50
+ toggleNode(nodeId: string): void;
51
+ /**
52
+ * Toggle expand/collapse for a node identified by its **value**. Used by
53
+ * wrapper components that track expansion by value rather than by DOM id.
54
+ */
55
+ toggleNodeByValue(value: T): void;
56
+ /** Returns `true` when the node with the given DOM id is expanded. */
57
+ isExpanded(nodeId: string): boolean;
58
+ /** Returns `true` when the given value is in the expanded values set. */
59
+ isValueExpanded(value: T): boolean;
60
+ }
61
+
62
+ /** Injection token providing the nearest `KjTreeSelect` context to descendants. */
63
+ export const KJ_TREE_SELECT = new InjectionToken<KjTreeSelectContext>('KjTreeSelect');
@@ -0,0 +1,433 @@
1
+ import { render, fireEvent } from '@testing-library/angular';
2
+ import { By } from '@angular/platform-browser';
3
+ import { toHaveNoViolations } from 'jest-axe';
4
+ import { afterEach } from 'vitest';
5
+ import {
6
+ KjTreeSelect,
7
+ KjTreeSelectNode,
8
+ KjTreeSelectToggle,
9
+ } from './tree-select';
10
+ import { KjTreeSelectTrigger } from './tree-select-trigger';
11
+ import { KjTreeSelectContent } from './tree-select-content';
12
+ import { KJ_LIST_NAVIGATOR_CONFIG } from '../primitives/list';
13
+
14
+ expect.extend(toHaveNoViolations);
15
+
16
+ // Sweep portalled overlay wrappers between tests. The body-portal
17
+ // mount strategy detaches them on overlay close, but if a test ends
18
+ // while the panel is still open the wrapper survives in `document.body`
19
+ // and pollutes subsequent `document.querySelector` calls.
20
+ afterEach(() => {
21
+ document
22
+ .querySelectorAll('.kj-overlay-wrapper, .kj-overlay-container')
23
+ .forEach(w => w.remove());
24
+ });
25
+
26
+ const imports = [
27
+ KjTreeSelect,
28
+ KjTreeSelectTrigger,
29
+ KjTreeSelectContent,
30
+ KjTreeSelectNode,
31
+ KjTreeSelectToggle,
32
+ ];
33
+
34
+ // The overlay uses the body-portal mount strategy, so on open the panel
35
+ // (and all of its tree-node children) move from the test `container`
36
+ // into a per-overlay wrapper inside `document.body`. Pre-open queries
37
+ // happily use the test `container`; post-open queries must scope to
38
+ // `document` (or fall back to `container` when the portal is closed).
39
+ const q = (sel: string): Element | null =>
40
+ document.querySelector(sel) ?? null;
41
+ const qa = (sel: string): NodeListOf<Element> => document.querySelectorAll(sel);
42
+
43
+ // ── Base single-select template ──────────────────────────────────────────────
44
+
45
+ const singleTemplate = `
46
+ <div kjTreeSelect [(kjValue)]="selected">
47
+ <button kjTreeSelectTrigger #t="kjTreeSelectTrigger">Choose</button>
48
+ <kj-tree-select-content [kjFor]="t">
49
+ <div
50
+ kjTreeSelectNode
51
+ [kjValue]="'fruits'"
52
+ kjLabel="Fruits"
53
+ [kjHasChildren]="true"
54
+ [kjNodeLevel]="1" [kjNodeSize]="2" [kjNodePos]="1"
55
+ >
56
+ <button kjTreeSelectToggle type="button">▶</button>
57
+ <span>Fruits</span>
58
+ <div
59
+ kjTreeSelectNode
60
+ [kjValue]="'apple'"
61
+ kjLabel="Apple"
62
+ [kjNodeLevel]="2" [kjNodeSize]="2" [kjNodePos]="1"
63
+ >Apple</div>
64
+ <div
65
+ kjTreeSelectNode
66
+ [kjValue]="'banana'"
67
+ kjLabel="Banana"
68
+ [kjNodeLevel]="2" [kjNodeSize]="2" [kjNodePos]="2"
69
+ >Banana</div>
70
+ </div>
71
+ <div
72
+ kjTreeSelectNode
73
+ [kjValue]="'veggies'"
74
+ kjLabel="Vegetables"
75
+ [kjNodeLevel]="1" [kjNodeSize]="2" [kjNodePos]="2"
76
+ >Vegetables</div>
77
+ </kj-tree-select-content>
78
+ </div>
79
+ `;
80
+
81
+ // ── Tests ────────────────────────────────────────────────────────────────────
82
+
83
+ describe('KjTreeSelect – panel visibility', () => {
84
+ it('panel is hidden by default', async () => {
85
+ const { container } = await render(singleTemplate, {
86
+ imports,
87
+ componentProperties: { selected: undefined },
88
+ });
89
+ expect(container.querySelector('kj-tree-select-content')).toHaveAttribute('hidden', '');
90
+ });
91
+
92
+ it('clicking the trigger opens the panel', async () => {
93
+ const { container } = await render(singleTemplate, {
94
+ imports,
95
+ componentProperties: { selected: undefined },
96
+ });
97
+ fireEvent.click(container.querySelector('[kjTreeSelectTrigger]')!);
98
+ // Panel is portalled to body on open — query via `document`.
99
+ expect(q('kj-tree-select-content')).not.toHaveAttribute('hidden');
100
+ });
101
+
102
+ it('aria-expanded on trigger reflects open state', async () => {
103
+ const { container } = await render(singleTemplate, {
104
+ imports,
105
+ componentProperties: { selected: undefined },
106
+ });
107
+ const trigger = container.querySelector('[kjTreeSelectTrigger]')!;
108
+ expect(trigger).toHaveAttribute('aria-expanded', 'false');
109
+ fireEvent.click(trigger);
110
+ expect(trigger).toHaveAttribute('aria-expanded', 'true');
111
+ });
112
+
113
+ it('Escape closes the panel', async () => {
114
+ const { container } = await render(singleTemplate, {
115
+ imports,
116
+ componentProperties: { selected: undefined },
117
+ });
118
+ fireEvent.click(container.querySelector('[kjTreeSelectTrigger]')!);
119
+ fireEvent.keyDown(document, { key: 'Escape' });
120
+ // `KjOverlayController.close` schedules its DOM teardown inside a
121
+ // requestAnimationFrame (so consumers can animate close transitions
122
+ // without panicking the state machine). Wait one RAF before we
123
+ // assert on the final `hidden` attribute. Once closed, the panel
124
+ // is detached/hidden regardless of which parent currently holds
125
+ // it — query against `document` so the assertion stays
126
+ // intent-preserving.
127
+ await new Promise<void>(r => requestAnimationFrame(() => r()));
128
+ expect(q('kj-tree-select-content')).toHaveAttribute('hidden', '');
129
+ });
130
+ });
131
+
132
+ describe('KjTreeSelect – trigger ARIA', () => {
133
+ it('trigger has role="combobox" and aria-haspopup="tree"', async () => {
134
+ const { container } = await render(singleTemplate, {
135
+ imports,
136
+ componentProperties: { selected: undefined },
137
+ });
138
+ const trigger = container.querySelector('[kjTreeSelectTrigger]')!;
139
+ expect(trigger).toHaveAttribute('role', 'combobox');
140
+ expect(trigger).toHaveAttribute('aria-haspopup', 'tree');
141
+ });
142
+ });
143
+
144
+ describe('KjTreeSelect – panel ARIA', () => {
145
+ it('panel has role="tree"', async () => {
146
+ const { container } = await render(singleTemplate, {
147
+ imports,
148
+ componentProperties: { selected: undefined },
149
+ });
150
+ expect(container.querySelector('kj-tree-select-content')).toHaveAttribute('role', 'tree');
151
+ });
152
+
153
+ it('nodes carry role="treeitem"', async () => {
154
+ const { container } = await render(singleTemplate, {
155
+ imports,
156
+ componentProperties: { selected: undefined },
157
+ });
158
+ const nodes = container.querySelectorAll('[kjTreeSelectNode]');
159
+ nodes.forEach(n => expect(n).toHaveAttribute('role', 'treeitem'));
160
+ });
161
+
162
+ it('branch node has aria-expanded="false" when collapsed', async () => {
163
+ const { container } = await render(singleTemplate, {
164
+ imports,
165
+ componentProperties: { selected: undefined },
166
+ });
167
+ const branchNode = container.querySelector('[kjTreeSelectNode][data-has-children="true"]')!;
168
+ expect(branchNode).toHaveAttribute('aria-expanded', 'false');
169
+ });
170
+
171
+ it('node host element has an id minted by the composed KjListItem', async () => {
172
+ const { container } = await render(singleTemplate, {
173
+ imports,
174
+ componentProperties: { selected: undefined },
175
+ });
176
+ const node = container.querySelector('[kjTreeSelectNode]')!;
177
+ // KjListItem auto-mints `kj-list-item-N` ids unless an `id` is
178
+ // already set on the host element.
179
+ expect(node.id).toMatch(/^kj-list-item-\d+$/);
180
+ });
181
+ });
182
+
183
+ describe('KjTreeSelect – expand / collapse', () => {
184
+ it('clicking the toggle button expands the branch node', async () => {
185
+ const { container } = await render(singleTemplate, {
186
+ imports,
187
+ componentProperties: { selected: undefined },
188
+ });
189
+ fireEvent.click(container.querySelector('[kjTreeSelectTrigger]')!);
190
+ const toggle = q('[kjTreeSelectToggle]')!;
191
+ fireEvent.click(toggle);
192
+ const branchNode = q('[kjTreeSelectNode][data-has-children="true"]')!;
193
+ expect(branchNode).toHaveAttribute('aria-expanded', 'true');
194
+ expect(branchNode).toHaveAttribute('data-expanded', 'true');
195
+ });
196
+
197
+ it('clicking toggle again collapses the branch', async () => {
198
+ const { container } = await render(singleTemplate, {
199
+ imports,
200
+ componentProperties: { selected: undefined },
201
+ });
202
+ fireEvent.click(container.querySelector('[kjTreeSelectTrigger]')!);
203
+ const toggle = q('[kjTreeSelectToggle]')!;
204
+ fireEvent.click(toggle);
205
+ fireEvent.click(toggle);
206
+ const branchNode = q('[kjTreeSelectNode][data-has-children="true"]')!;
207
+ expect(branchNode).toHaveAttribute('aria-expanded', 'false');
208
+ });
209
+
210
+ it('toggle click does not select the branch node', async () => {
211
+ const { container, fixture } = await render(singleTemplate, {
212
+ imports,
213
+ componentProperties: { selected: undefined },
214
+ });
215
+ fireEvent.click(container.querySelector('[kjTreeSelectTrigger]')!);
216
+ const toggle = q('[kjTreeSelectToggle]')!;
217
+ fireEvent.click(toggle);
218
+ fixture.detectChanges();
219
+ expect(q('kj-tree-select-content')).not.toHaveAttribute('hidden');
220
+ });
221
+ });
222
+
223
+ describe('KjTreeSelect – single selection', () => {
224
+ it('clicking a leaf node selects it and closes the panel', async () => {
225
+ const { container, fixture } = await render(singleTemplate, {
226
+ imports,
227
+ componentProperties: { selected: undefined },
228
+ });
229
+ fireEvent.click(container.querySelector('[kjTreeSelectTrigger]')!);
230
+ const leafNode = q('[kjTreeSelectNode][aria-level="2"]')!;
231
+ // Capture the node's id before activation so we can find it again
232
+ // post-close (panel may be portalled back to its original slot, or
233
+ // remain in the body wrapper — `document.querySelector` covers
234
+ // both placements).
235
+ const leafId = leafNode.id;
236
+ fireEvent.click(leafNode);
237
+ fixture.detectChanges();
238
+ // `KjOverlayController.close` schedules its DOM teardown inside a
239
+ // requestAnimationFrame — wait one RAF before asserting on
240
+ // `hidden`.
241
+ await new Promise<void>(r => requestAnimationFrame(() => r()));
242
+ expect(q('kj-tree-select-content')).toHaveAttribute('hidden', '');
243
+ const refoundLeaf = q(`#${leafId}`)!;
244
+ expect(refoundLeaf).toHaveAttribute('aria-selected', 'true');
245
+ expect(refoundLeaf).toHaveAttribute('data-selected', '');
246
+ });
247
+
248
+ it('aria-selected="false" on unselected nodes', async () => {
249
+ const { container } = await render(singleTemplate, {
250
+ imports,
251
+ componentProperties: { selected: undefined },
252
+ });
253
+ const nodes = container.querySelectorAll('[kjTreeSelectNode]');
254
+ nodes.forEach(n => expect(n).toHaveAttribute('aria-selected', 'false'));
255
+ });
256
+
257
+ it('clicking a different node updates the selection', async () => {
258
+ const { container, fixture } = await render(singleTemplate, {
259
+ imports,
260
+ componentProperties: { selected: undefined },
261
+ });
262
+ fireEvent.click(container.querySelector('[kjTreeSelectTrigger]')!);
263
+ const initial = qa('[kjTreeSelectNode][aria-level="2"]');
264
+ const firstId = initial[0].id;
265
+ const secondId = initial[1].id;
266
+ fireEvent.click(initial[0]);
267
+ fixture.detectChanges();
268
+
269
+ fireEvent.click(container.querySelector('[kjTreeSelectTrigger]')!);
270
+ fixture.detectChanges();
271
+ expect(q(`#${firstId}`)).toHaveAttribute('aria-selected', 'true');
272
+ expect(q(`#${secondId}`)).toHaveAttribute('aria-selected', 'false');
273
+ });
274
+ });
275
+
276
+ describe('KjTreeSelect – multi selection', () => {
277
+ const multiTemplate = `
278
+ <div kjTreeSelect [(kjValue)]="selected" kjSelectionMode="multiple">
279
+ <button kjTreeSelectTrigger #t="kjTreeSelectTrigger">Choose</button>
280
+ <kj-tree-select-content [kjFor]="t">
281
+ <div kjTreeSelectNode [kjValue]="'a'" kjLabel="A" [kjNodeLevel]="1" [kjNodeSize]="3" [kjNodePos]="1">A</div>
282
+ <div kjTreeSelectNode [kjValue]="'b'" kjLabel="B" [kjNodeLevel]="1" [kjNodeSize]="3" [kjNodePos]="2">B</div>
283
+ <div kjTreeSelectNode [kjValue]="'c'" kjLabel="C" [kjNodeLevel]="1" [kjNodeSize]="3" [kjNodePos]="3">C</div>
284
+ </kj-tree-select-content>
285
+ </div>
286
+ `;
287
+
288
+ it('panel stays open after selecting a node', async () => {
289
+ const { container } = await render(multiTemplate, {
290
+ imports,
291
+ componentProperties: { selected: [] },
292
+ });
293
+ fireEvent.click(container.querySelector('[kjTreeSelectTrigger]')!);
294
+ const nodes = qa('[kjTreeSelectNode]');
295
+ fireEvent.click(nodes[0]);
296
+ expect(q('kj-tree-select-content')).not.toHaveAttribute('hidden');
297
+ });
298
+
299
+ it('panel has aria-multiselectable="true"', async () => {
300
+ const { container } = await render(multiTemplate, {
301
+ imports,
302
+ componentProperties: { selected: [] },
303
+ });
304
+ expect(container.querySelector('kj-tree-select-content')).toHaveAttribute(
305
+ 'aria-multiselectable', 'true',
306
+ );
307
+ });
308
+
309
+ it('selecting multiple nodes accumulates the selection', async () => {
310
+ const { container, fixture } = await render(multiTemplate, {
311
+ imports,
312
+ componentProperties: { selected: [] },
313
+ });
314
+ fireEvent.click(container.querySelector('[kjTreeSelectTrigger]')!);
315
+ const nodes = qa('[kjTreeSelectNode]');
316
+ fireEvent.click(nodes[0]);
317
+ fireEvent.click(nodes[2]);
318
+ fixture.detectChanges();
319
+ expect(nodes[0]).toHaveAttribute('aria-selected', 'true');
320
+ expect(nodes[1]).toHaveAttribute('aria-selected', 'false');
321
+ expect(nodes[2]).toHaveAttribute('aria-selected', 'true');
322
+ });
323
+
324
+ it('clicking a selected node deselects it', async () => {
325
+ const { container, fixture } = await render(multiTemplate, {
326
+ imports,
327
+ componentProperties: { selected: [] },
328
+ });
329
+ fireEvent.click(container.querySelector('[kjTreeSelectTrigger]')!);
330
+ const nodes = qa('[kjTreeSelectNode]');
331
+ fireEvent.click(nodes[0]);
332
+ fireEvent.click(nodes[0]);
333
+ fixture.detectChanges();
334
+ expect(nodes[0]).toHaveAttribute('aria-selected', 'false');
335
+ });
336
+
337
+ it('single mode panel does not have aria-multiselectable', async () => {
338
+ const { container } = await render(singleTemplate, {
339
+ imports,
340
+ componentProperties: { selected: undefined },
341
+ });
342
+ const panel = container.querySelector('kj-tree-select-content')!;
343
+ expect(panel).not.toHaveAttribute('aria-multiselectable');
344
+ });
345
+ });
346
+
347
+ describe('KjTreeSelect – disabled node', () => {
348
+ const disabledTemplate = `
349
+ <div kjTreeSelect [(kjValue)]="selected">
350
+ <button kjTreeSelectTrigger #t="kjTreeSelectTrigger">Choose</button>
351
+ <kj-tree-select-content [kjFor]="t">
352
+ <div kjTreeSelectNode [kjValue]="'x'" kjLabel="X" kjDisabled [kjNodeLevel]="1" [kjNodeSize]="1" [kjNodePos]="1">X</div>
353
+ </kj-tree-select-content>
354
+ </div>
355
+ `;
356
+
357
+ it('disabled node has aria-disabled="true"', async () => {
358
+ const { container } = await render(disabledTemplate, {
359
+ imports,
360
+ componentProperties: { selected: undefined },
361
+ });
362
+ const node = container.querySelector('[kjTreeSelectNode]')!;
363
+ expect(node).toHaveAttribute('aria-disabled', 'true');
364
+ });
365
+
366
+ it('disabled node does not change the selection when clicked', async () => {
367
+ const { container, fixture } = await render(disabledTemplate, {
368
+ imports,
369
+ componentProperties: { selected: undefined },
370
+ });
371
+ fireEvent.click(container.querySelector('[kjTreeSelectTrigger]')!);
372
+ const node = q('[kjTreeSelectNode]')!;
373
+ fireEvent.click(node);
374
+ fixture.detectChanges();
375
+ expect(node).toHaveAttribute('aria-selected', 'false');
376
+ });
377
+ });
378
+
379
+ describe('KjTreeSelect – KjListNavigatorConfig integration', () => {
380
+ it('provides KJ_LIST_NAVIGATOR_CONFIG that reads/writes the kjValue model', async () => {
381
+ const { fixture } = await render(singleTemplate, {
382
+ imports,
383
+ componentProperties: { selected: undefined },
384
+ });
385
+ const rootDe = fixture.debugElement.query(By.directive(KjTreeSelect));
386
+ const cfg = rootDe.injector.get(KJ_LIST_NAVIGATOR_CONFIG);
387
+ expect(cfg.value).toBeDefined();
388
+ cfg.value!.set('apple');
389
+ fixture.detectChanges();
390
+ expect((fixture.componentInstance as { selected: unknown }).selected).toBe('apple');
391
+ });
392
+ });
393
+
394
+ describe('KjTreeSelect – keyboard navigation', () => {
395
+ // Post-Task-4: Up/Down/Home/End/Enter/Space/type-ahead are owned by
396
+ // the composed `KjListNavigator`; tree-specific ArrowLeft/Right stay
397
+ // on `KjTreeSelectContent`. Roving DOM focus is wired by the content
398
+ // component via a local effect on `KjListNavigator.activeId` plus a
399
+ // `KJ_LIST_FOCUS_MODE` provider override (Angular's
400
+ // `hostDirectives.inputs` cannot statically push `'roving'` into the
401
+ // navigator's input signal). Net effect for these tests: keyboard
402
+ // moves `activeId` → DOM focus follows → `activeElement` matches.
403
+ it('ArrowDown on the panel moves focus to the first node', async () => {
404
+ const { container } = await render(singleTemplate, {
405
+ imports,
406
+ componentProperties: { selected: undefined },
407
+ });
408
+ fireEvent.click(container.querySelector('[kjTreeSelectTrigger]')!);
409
+ const panel = q('kj-tree-select-content')!;
410
+ fireEvent.keyDown(panel, { key: 'ArrowDown' });
411
+ const nodes = qa('[kjTreeSelectNode]');
412
+ expect(document.activeElement).toBe(nodes[0]);
413
+ });
414
+
415
+ it('Enter on focused node selects it', async () => {
416
+ const { container, fixture } = await render(singleTemplate, {
417
+ imports,
418
+ componentProperties: { selected: undefined },
419
+ });
420
+ fireEvent.click(container.querySelector('[kjTreeSelectTrigger]')!);
421
+ const leafNode = q('[kjTreeSelectNode][aria-level="2"]')!;
422
+ const leafId = leafNode.id;
423
+ fireEvent.keyDown(leafNode, { key: 'Enter' });
424
+ fixture.detectChanges();
425
+ // Enter routes through the content keydown handler, which calls
426
+ // `node.click()` — `KjListItem`'s `(click)` runs `_activate` →
427
+ // `KjSelectionModel.toggle` → root `afterSelect` closes the panel
428
+ // (single mode). Look up by id via `document` so we don't depend
429
+ // on whether the panel has been re-parented yet.
430
+ const refoundLeaf = q(`#${leafId}`)!;
431
+ expect(refoundLeaf).toHaveAttribute('aria-selected', 'true');
432
+ });
433
+ });
@@ -0,0 +1,2 @@
1
+ export { KjTreeSelect } from './tree-select-root';
2
+ export { KjTreeSelectNode, KjTreeSelectToggle } from './tree-select-node';
@@ -0,0 +1,57 @@
1
+ import {
2
+ Directive,
3
+ ElementRef,
4
+ afterNextRender,
5
+ inject,
6
+ isDevMode,
7
+ } from '@angular/core';
8
+
9
+ /**
10
+ * Applies kouji's blockquote tone — left rule, italic, indent — to a
11
+ * `<blockquote>` element. Reflects `data-tone="blockquote"`. Useful when
12
+ * the blockquote sits *outside* a `kj-prose` container (the prose container
13
+ * styles its own `<blockquote>` descendants automatically).
14
+ *
15
+ * Recommended host is `<blockquote>` to preserve native semantics; applied
16
+ * to a non-`<blockquote>` host the directive emits a dev-mode warning but
17
+ * does not enforce the tag.
18
+ *
19
+ * @example
20
+ * ```html
21
+ * <blockquote kjBlockquote>
22
+ * "kouji-ui shipped on the day they said it would."
23
+ * </blockquote>
24
+ * ```
25
+ * @doc-category Core/Data display
26
+ * @doc
27
+ * @doc-name typography
28
+ * @doc-description Styles a standalone blockquote outside a prose container with the kouji quotation tone.
29
+ * @doc-is-main
30
+ */
31
+ @Directive({
32
+ selector: '[kjBlockquote]',
33
+ standalone: true,
34
+ exportAs: 'kjBlockquote',
35
+ host: {
36
+ '[attr.data-tone]': '"blockquote"',
37
+ },
38
+ })
39
+ export class KjBlockquote {
40
+ private readonly el = inject<ElementRef<HTMLElement>>(ElementRef);
41
+
42
+ constructor() {
43
+ if (isDevMode()) {
44
+ afterNextRender(() => {
45
+ const host = this.el.nativeElement;
46
+ if (host.tagName?.toLowerCase() !== 'blockquote') {
47
+ console.warn(
48
+ `[kj] kjBlockquote applied to <${host.tagName?.toLowerCase()}>. ` +
49
+ `Recommended host element is <blockquote> for native semantics ` +
50
+ `(WCAG 1.3.1 / 4.1.2). Apply only to non-blockquote elements ` +
51
+ `when you have a specific styling reason.`,
52
+ );
53
+ }
54
+ });
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,53 @@
1
+ import {
2
+ Directive,
3
+ ElementRef,
4
+ afterNextRender,
5
+ inject,
6
+ isDevMode,
7
+ } from '@angular/core';
8
+
9
+ /**
10
+ * Marks an inline `<code>` element with kouji's inline-code tone — monospace
11
+ * font, subtle background fill, and inline padding. Reflects
12
+ * `data-tone="code"`. The directive does not handle multi-line `<pre><code>`
13
+ * blocks; those are styled by the prose container's `pre code` selector or
14
+ * by a consumer-wired syntax highlighter.
15
+ *
16
+ * Recommended host is `<code>` to preserve native semantics; applying
17
+ * `[kjCode]` to a different element is dev-mode warned but not enforced.
18
+ *
19
+ * @example
20
+ * ```html
21
+ * <p>Run <code kjCode>npm install @kouji-ui/core</code> to install.</p>
22
+ * ```
23
+ * @doc-category Core/Data display
24
+ * @doc
25
+ * @doc-name typography
26
+ */
27
+ @Directive({
28
+ selector: '[kjCode]',
29
+ standalone: true,
30
+ exportAs: 'kjCode',
31
+ host: {
32
+ '[attr.data-tone]': '"code"',
33
+ },
34
+ })
35
+ export class KjCode {
36
+ private readonly el = inject<ElementRef<HTMLElement>>(ElementRef);
37
+
38
+ constructor() {
39
+ if (isDevMode()) {
40
+ afterNextRender(() => {
41
+ const host = this.el.nativeElement;
42
+ if (host.tagName?.toLowerCase() !== 'code') {
43
+ console.warn(
44
+ `[kj] kjCode applied to <${host.tagName?.toLowerCase()}>. ` +
45
+ `Recommended host element is <code> for native semantics ` +
46
+ `(WCAG 1.3.1 / 4.1.2). Apply only to non-code elements when ` +
47
+ `you have a specific styling reason.`,
48
+ );
49
+ }
50
+ });
51
+ }
52
+ }
53
+ }
@@ -0,0 +1,22 @@
1
+ export { KjLead } from './lead';
2
+ export { KjMuted } from './muted';
3
+ export { KjCode } from './code';
4
+ export { KjBlockquote } from './blockquote';
5
+ export { KjTruncate } from './truncate';
6
+
7
+ /**
8
+ * Resolvable path to the kouji prose stylesheet. Consumers import the CSS
9
+ * once at the application root:
10
+ *
11
+ * ```ts
12
+ * import { KJ_PROSE_CSS_PATH } from '@kouji-ui/core';
13
+ * // or import the file directly:
14
+ * import '@kouji-ui/core/typography/prose.css';
15
+ * ```
16
+ *
17
+ * The path is exposed as a string so build tools can locate the file via
18
+ * `require.resolve(KJ_PROSE_CSS_PATH)` when programmatic resolution is
19
+ * needed (for example, when wiring the stylesheet into a docs-site
20
+ * pipeline).
21
+ */
22
+ export const KJ_PROSE_CSS_PATH = '@kouji-ui/core/typography/prose.css';
@@ -0,0 +1,49 @@
1
+ import {
2
+ Directive,
3
+ ElementRef,
4
+ afterNextRender,
5
+ inject,
6
+ isDevMode,
7
+ } from '@angular/core';
8
+
9
+ /**
10
+ * Marks a paragraph as the lead-in paragraph for a section — slightly larger
11
+ * size with a softer tone. Reflects `data-tone="lead"` so theme CSS keys off
12
+ * it; the directive owns no styling itself.
13
+ *
14
+ * Lead semantics are paragraph-bound; applied to a non-`<p>` host the
15
+ * directive emits a dev-mode warning but does not enforce the tag.
16
+ *
17
+ * @example
18
+ * ```html
19
+ * <p kjLead>Atlas helps engineering teams plan quarterly roadmaps.</p>
20
+ * ```
21
+ * @doc-category Core/Data display
22
+ * @doc
23
+ * @doc-name typography
24
+ */
25
+ @Directive({
26
+ selector: '[kjLead]',
27
+ standalone: true,
28
+ exportAs: 'kjLead',
29
+ host: {
30
+ '[attr.data-tone]': '"lead"',
31
+ },
32
+ })
33
+ export class KjLead {
34
+ private readonly el = inject<ElementRef<HTMLElement>>(ElementRef);
35
+
36
+ constructor() {
37
+ if (isDevMode()) {
38
+ afterNextRender(() => {
39
+ const host = this.el.nativeElement;
40
+ if (host.tagName?.toLowerCase() !== 'p') {
41
+ console.warn(
42
+ `[kj] kjLead applied to <${host.tagName?.toLowerCase()}>. ` +
43
+ `Lead semantics are paragraph-bound; recommended host is <p>.`,
44
+ );
45
+ }
46
+ });
47
+ }
48
+ }
49
+ }