@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,199 @@
1
+ import {
2
+ ChangeDetectionStrategy,
3
+ Component,
4
+ ElementRef,
5
+ Injector,
6
+ ViewEncapsulation,
7
+ effect,
8
+ inject,
9
+ input,
10
+ signal,
11
+ } from '@angular/core';
12
+ import { KjOverlayPanel } from '../primitives/overlay/panel';
13
+ import type { KjOverlayController } from '../primitives/overlay/controller';
14
+ import {
15
+ KJ_OVERLAY_MOUNT_STRATEGY,
16
+ KJ_OVERLAY_POSITION_STRATEGY,
17
+ KJ_OVERLAY_PANEL_ROLE,
18
+ } from '../primitives/overlay/tokens';
19
+ import type { KjSide, KjAlign } from '../primitives/overlay/types';
20
+ import { bodyPortal } from '../primitives/overlay/strategies/mount/body-portal';
21
+ import { anchoredTo } from '../primitives/overlay/strategies/position/anchored-to';
22
+ import {
23
+ KJ_LIST_FOCUS_MODE,
24
+ KjListNavigator,
25
+ type KjListFocusMode,
26
+ } from '../primitives/list';
27
+ import { KJ_TREE_SELECT } from './tree-select.context';
28
+
29
+ // Token override below pins the focus mode to `'roving'` for descendant
30
+ // `KjListItem`s. Defined at module scope so it can be referenced from
31
+ // the `providers` array literal without a class capture.
32
+ const ROVING_FOCUS_MODE = signal<KjListFocusMode>('roving');
33
+
34
+ /**
35
+ * Tree panel container. Composes `KjOverlayPanel` for mount/position/role
36
+ * wiring (carries `role="tree"` from the panel role provider) and
37
+ * `KjListNavigator` for the generic Up/Down / Home/End / Enter / Space /
38
+ * type-ahead contract. The tree-specific ArrowLeft / ArrowRight keys
39
+ * remain handled here because they carry expand/collapse +
40
+ * parent/first-child semantics the generic navigator doesn't cover.
41
+ *
42
+ * Roving DOM focus is wired here (rather than via the navigator's own
43
+ * `kjFocusMode="roving"` mode) because Angular's `hostDirectives.inputs`
44
+ * surface can only rename inputs — it can't push a static default into a
45
+ * composed input signal. Instead this component (1) overrides the
46
+ * `KJ_LIST_FOCUS_MODE` provider so child `KjListItem`s flip their
47
+ * `tabindex` correctly, and (2) drives the active-item DOM focus via a
48
+ * local effect on `KjListNavigator.activeId()`. The navigator itself
49
+ * continues to run its keyboard / type-ahead state machine unchanged.
50
+ *
51
+ * @doc-category Core/Inputs
52
+ */
53
+ @Component({
54
+ selector: 'kj-tree-select-content',
55
+ standalone: true,
56
+ hostDirectives: [
57
+ { directive: KjOverlayPanel, inputs: ['kjFor'] },
58
+ KjListNavigator,
59
+ ],
60
+ providers: [
61
+ { provide: KJ_OVERLAY_PANEL_ROLE, useValue: 'tree' as const },
62
+ { provide: KJ_OVERLAY_MOUNT_STRATEGY, useFactory: () => bodyPortal() },
63
+ { provide: KJ_OVERLAY_POSITION_STRATEGY, useFactory: () => anchoredTo() },
64
+ // Pin items' focus model to `'roving'` (per WAI-ARIA APG tree). This
65
+ // overrides the provider the navigator registers (which mirrors the
66
+ // navigator's own `kjFocusMode` signal). The navigator's internal
67
+ // seed / focus effects are skipped — both are driven here instead.
68
+ { provide: KJ_LIST_FOCUS_MODE, useValue: ROVING_FOCUS_MODE },
69
+ ],
70
+ host: {
71
+ '[attr.aria-multiselectable]':
72
+ 'ctx?.selectionMode() === "multiple" ? "true" : null',
73
+ '(keydown)': 'onKeydown($event)',
74
+ '(document:keydown.escape)': 'controller?.close("esc")',
75
+ '(document:click)': 'onDocClick($event)',
76
+ '(click)': '$event.stopPropagation()',
77
+ },
78
+ changeDetection: ChangeDetectionStrategy.OnPush,
79
+ encapsulation: ViewEncapsulation.None,
80
+ template: `<ng-content />`,
81
+ })
82
+ export class KjTreeSelectContent {
83
+ private readonly el = inject(ElementRef<HTMLElement>);
84
+ private readonly _injector = inject(Injector);
85
+ private _panelCache: KjOverlayPanel | null | undefined = undefined;
86
+ private get _panel(): KjOverlayPanel | null {
87
+ if (this._panelCache === undefined) {
88
+ this._panelCache = this._injector.get(KjOverlayPanel, null);
89
+ }
90
+ return this._panelCache;
91
+ }
92
+ /** @internal */
93
+ get controller(): KjOverlayController | null {
94
+ return this._panel?.controller ?? null;
95
+ }
96
+ /** @internal */
97
+ readonly ctx = inject(KJ_TREE_SELECT, { optional: true });
98
+ // The composed `KjListNavigator` owns the generic Up/Down/Home/End/
99
+ // Enter/Space/type-ahead contract. We read its `activeItem()` below to
100
+ // anchor ArrowLeft / ArrowRight off the currently focused tree node
101
+ // and to follow the active id with DOM focus (roving model).
102
+ private readonly nav = inject(KjListNavigator);
103
+
104
+ readonly kjSide = input<KjSide>('bottom');
105
+ readonly kjAlign = input<KjAlign>('start');
106
+ readonly kjOffset = input<number, unknown>(4, {
107
+ transform: v => Number(v) || 4,
108
+ });
109
+
110
+ constructor() {
111
+ const pos = inject(KJ_OVERLAY_POSITION_STRATEGY) as ReturnType<typeof anchoredTo>;
112
+ pos.configure({ side: this.kjSide, align: this.kjAlign, offset: this.kjOffset, matchTriggerWidth: 'min' });
113
+
114
+ // Roving focus follow: mirror the navigator's `activeId` to DOM
115
+ // focus. Equivalent to `KjListNavigator`'s internal focus effect,
116
+ // re-implemented here because the navigator's effect is gated on
117
+ // `kjFocusMode() === 'roving'` and we can't statically push that
118
+ // value into a hostDirective input signal.
119
+ effect(() => {
120
+ const item = this.nav.activeItem();
121
+ if (!item) return;
122
+ const host = item._host();
123
+ if (host && document.activeElement !== host) host.focus();
124
+ });
125
+ }
126
+
127
+ /** @internal */
128
+ onDocClick(event: MouseEvent): void {
129
+ const ctrl = this.controller;
130
+ if (!ctrl?.isOpen()) return;
131
+ const target = event.target as Node | null;
132
+ if (!target) return;
133
+ if (this.el.nativeElement.contains(target)) return;
134
+ const trigger = ctrl.triggerEl();
135
+ if (trigger && trigger.contains(target)) return;
136
+ ctrl.close('outside');
137
+ }
138
+
139
+ /**
140
+ * Tree-specific ArrowLeft / ArrowRight handling. Up/Down/Home/End/
141
+ * Enter/Space/type-ahead all flow through the composed
142
+ * `KjListNavigator` — they aren't reimplemented here.
143
+ *
144
+ * - ArrowRight on a collapsed branch → expand (toggle).
145
+ * - ArrowRight on an expanded branch → move active to first child.
146
+ * - ArrowLeft on an expanded branch → collapse (toggle).
147
+ * - ArrowLeft elsewhere → move active to the parent (prior item with
148
+ * `aria-level` less than current).
149
+ *
150
+ * @internal
151
+ */
152
+ onKeydown(event: KeyboardEvent): void {
153
+ if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return;
154
+ const active = this.nav.activeItem();
155
+ if (!active) return;
156
+ const node = active._host();
157
+ const items = this._domNodes();
158
+ const idx = items.indexOf(node);
159
+ if (idx === -1) return;
160
+ const hasChildren = node.getAttribute('data-has-children') === 'true';
161
+ const expanded = node.getAttribute('data-expanded') === 'true';
162
+
163
+ if (event.key === 'ArrowRight') {
164
+ event.preventDefault();
165
+ if (hasChildren && !expanded) {
166
+ const toggle = node.querySelector('[kjTreeSelectToggle]') as HTMLElement | null;
167
+ toggle?.click();
168
+ } else if (hasChildren && expanded) {
169
+ const next = items[idx + 1];
170
+ if (next) this.nav.setActive(next.id);
171
+ }
172
+ return;
173
+ }
174
+
175
+ // ArrowLeft
176
+ event.preventDefault();
177
+ if (hasChildren && expanded) {
178
+ const toggle = node.querySelector('[kjTreeSelectToggle]') as HTMLElement | null;
179
+ toggle?.click();
180
+ return;
181
+ }
182
+ const currentLevel = parseInt(node.getAttribute('aria-level') ?? '1', 10);
183
+ for (let i = idx - 1; i >= 0; i--) {
184
+ const lvl = parseInt(items[i]?.getAttribute('aria-level') ?? '1', 10);
185
+ if (lvl < currentLevel) {
186
+ this.nav.setActive(items[i].id);
187
+ break;
188
+ }
189
+ }
190
+ }
191
+
192
+ /** DOM-order list of tree nodes scoped to this panel. */
193
+ private _domNodes(): HTMLElement[] {
194
+ return Array.from(
195
+ this.el.nativeElement.querySelectorAll('[kjTreeSelectNode]'),
196
+ ) as HTMLElement[];
197
+ }
198
+ }
199
+
@@ -0,0 +1,132 @@
1
+ import {
2
+ booleanAttribute,
3
+ computed,
4
+ Directive,
5
+ inject,
6
+ input,
7
+ } from '@angular/core';
8
+ import { KjListItem, injectListItem } from '../primitives/list';
9
+ import { KJ_TREE_SELECT } from './tree-select.context';
10
+
11
+ /**
12
+ * Individual tree node (treeitem). Composes `KjListItem` via
13
+ * `hostDirectives` — the list item owns the stable id, the
14
+ * click/Enter/Space activation, the `aria-selected` / `aria-disabled`
15
+ * bindings, and the shared `KjSelectionModel` toggle. This directive
16
+ * contributes only tree-specific semantics: `role="treeitem"`,
17
+ * `aria-level` / `aria-setsize` / `aria-posinset` from the parent's
18
+ * derived metadata, `aria-expanded` from the tree context's
19
+ * expanded-id set, and the `data-*` styling hooks.
20
+ *
21
+ * @example
22
+ * ```html
23
+ * <div
24
+ * kjTreeSelectNode
25
+ * [kjValue]="node.value"
26
+ * [kjLabel]="node.label"
27
+ * [kjNodeLevel]="1"
28
+ * [kjHasChildren]="true"
29
+ * >
30
+ * <button kjTreeSelectToggle>▶</button>
31
+ * <span>{{ node.label }}</span>
32
+ * </div>
33
+ * ```
34
+ * @doc-category Core/Inputs
35
+ * @doc
36
+ * @doc-name tree-select
37
+ * @doc-description Renders one tree node with accessible treeitem semantics; selection + activation owned by the composed KjListItem.
38
+ */
39
+ @Directive({
40
+ selector: '[kjTreeSelectNode]',
41
+ standalone: true,
42
+ exportAs: 'kjTreeSelectNode',
43
+ hostDirectives: [
44
+ {
45
+ directive: KjListItem,
46
+ inputs: [
47
+ 'kjItemValue:kjValue',
48
+ 'kjItemLabel:kjLabel',
49
+ 'kjDisabled:kjDisabled',
50
+ ],
51
+ },
52
+ ],
53
+ host: {
54
+ 'class': 'kj-tree-select-node',
55
+ 'role': 'treeitem',
56
+ '[attr.aria-level]': 'kjNodeLevel()',
57
+ '[attr.aria-setsize]': 'kjNodeSize()',
58
+ '[attr.aria-posinset]': 'kjNodePos()',
59
+ '[attr.aria-expanded]': 'kjHasChildren() ? (isExpanded() ? "true" : "false") : null',
60
+ '[attr.data-expanded]': 'isExpanded() ? "true" : "false"',
61
+ '[attr.data-has-children]': 'kjHasChildren() ? "true" : "false"',
62
+ // `aria-selected` is owned by the composed `KjListItem` (it binds
63
+ // off the shared `KjSelectionModel`). We surface it as a
64
+ // `data-selected` styling hook here so consumer CSS doesn't have to
65
+ // care that the source of truth is one directive layer down.
66
+ '[attr.data-selected]': 'item.ariaSelected() === "true" ? "" : null',
67
+ },
68
+ })
69
+ export class KjTreeSelectNode {
70
+ /** Nesting depth (1-based). Maps to `aria-level`. */
71
+ readonly kjNodeLevel = input<number>(1);
72
+ /** Number of sibling nodes at the same level. Maps to `aria-setsize`. */
73
+ readonly kjNodeSize = input<number>(1);
74
+ /** 1-based position among siblings. Maps to `aria-posinset`. */
75
+ readonly kjNodePos = input<number>(1);
76
+ /** Whether this node has child nodes (branch vs. leaf). */
77
+ readonly kjHasChildren = input(false, { transform: booleanAttribute });
78
+
79
+ private readonly ctx = inject(KJ_TREE_SELECT);
80
+ /** @internal — composed list-item primitive providing id/value/disabled/activation. */
81
+ readonly item = injectListItem<unknown>();
82
+
83
+ /** Stable id minted by the composed `KjListItem` (used by toggle + aria refs). */
84
+ get id(): string { return this.item.id; }
85
+ /** Current value carried by this node (from the composed `KjListItem`). */
86
+ get value(): unknown { return this.item.value(); }
87
+ /** Whether this node is disabled (delegated to the composed `KjListItem`). */
88
+ readonly disabled = this.item.disabled;
89
+ /** Whether the node is currently expanded (reactive computed from context). */
90
+ readonly isExpanded = computed(() => this.ctx.isExpanded(this.id));
91
+ }
92
+
93
+ /**
94
+ * Expand/collapse toggle button inside a `KjTreeSelectNode`. Intercepts
95
+ * clicks before they bubble to the node (preventing accidental
96
+ * activation through the composed `KjListItem`) and toggles the
97
+ * tree-context expansion state by both the node's id and its value.
98
+ * Only meaningful on branch nodes (`[kjHasChildren]="true"`).
99
+ *
100
+ * Carries `aria-label` that reflects the current state.
101
+ *
102
+ * @example
103
+ * ```html
104
+ * <button kjTreeSelectToggle aria-hidden="true"></button>
105
+ * ```
106
+ * @doc-category Core/Inputs
107
+ */
108
+ @Directive({
109
+ selector: '[kjTreeSelectToggle]',
110
+ standalone: true,
111
+ host: {
112
+ '[attr.aria-label]': 'isExpanded() ? "Collapse" : "Expand"',
113
+ '[attr.data-expanded]': 'isExpanded() ? "true" : "false"',
114
+ '(click)': 'handleClick($event)',
115
+ },
116
+ })
117
+ export class KjTreeSelectToggle {
118
+ private readonly ctx = inject(KJ_TREE_SELECT);
119
+ /** @internal — parent node context */
120
+ private readonly node = inject(KjTreeSelectNode);
121
+
122
+ /** Whether the parent node is currently expanded (reactive computed). */
123
+ readonly isExpanded = this.node.isExpanded;
124
+
125
+ /** @internal */
126
+ handleClick(event: Event): void {
127
+ event.stopPropagation();
128
+ if (this.node.disabled()) return;
129
+ this.ctx.toggleNode(this.node.id);
130
+ this.ctx.toggleNodeByValue(this.node.value);
131
+ }
132
+ }
@@ -0,0 +1,272 @@
1
+ import {
2
+ computed,
3
+ contentChildren,
4
+ Directive,
5
+ effect,
6
+ forwardRef,
7
+ inject,
8
+ input,
9
+ model,
10
+ output,
11
+ signal,
12
+ untracked,
13
+ type Signal,
14
+ type WritableSignal,
15
+ } from '@angular/core';
16
+ import { KjOverlayController } from '../primitives/overlay/controller';
17
+ import {
18
+ KJ_LIST_NAVIGATOR_CONFIG,
19
+ KjListItem,
20
+ KjSelectionModel,
21
+ type KjCompareFn,
22
+ type KjListNavigatorConfig,
23
+ type KjListSelectionMode,
24
+ type KjTreeShape,
25
+ } from '../primitives/list';
26
+ import { KJ_TREE_SELECT, type KjTreeNode, type KjTreeSelectContext } from './tree-select.context';
27
+
28
+ let _treeSelectIdCounter = 0;
29
+
30
+ function setsEqual(a: ReadonlySet<unknown>, b: ReadonlySet<unknown>): boolean {
31
+ if (a.size !== b.size) return false;
32
+ for (const v of a) if (!b.has(v)) return false;
33
+ return true;
34
+ }
35
+
36
+ /**
37
+ * Root Tree Select container. Owns selection model, expanded node state,
38
+ * and the shared `KjOverlayController` so the trigger, content, and
39
+ * individual nodes all wire to the same overlay state.
40
+ *
41
+ * Mirrors the `[kjSelect]` pattern: umbrella root holds context +
42
+ * controller; leaf directives read from it via DI.
43
+ *
44
+ * Supports two selection modes:
45
+ * - `'single'` (default) — selecting a node writes that value and closes the panel.
46
+ * - `'multiple'` — selecting toggles the value in the selection set; the panel
47
+ * stays open.
48
+ *
49
+ * Implements {@link KjListNavigatorConfig}: descendants (`KjListNavigator`,
50
+ * `KjSelectionModel`) read the canonical `value`, `mode`, `compareBy`, and
51
+ * optional `treeShape` from the same root via `KJ_LIST_NAVIGATOR_CONFIG`.
52
+ * The legacy {@link KjTreeSelectContext} surface is preserved as a thin
53
+ * delegate so existing node/toggle directives keep working.
54
+ *
55
+ * @example
56
+ * ```html
57
+ * <div kjTreeSelect [(kjValue)]="selected" [kjNodes]="categories">
58
+ * <button kjTreeSelectTrigger #t="kjTreeSelectTrigger">Select category</button>
59
+ * <kj-tree-select-content [kjFor]="t"></kj-tree-select-content>
60
+ * </div>
61
+ * ```
62
+ * @doc-category Core/Inputs
63
+ * @doc
64
+ * @doc-name tree-select
65
+ * @doc-is-main
66
+ * @doc-description Unstyled tree-select root that holds the selection model and shares state with trigger, content, and nodes.
67
+ */
68
+ @Directive({
69
+ selector: '[kjTreeSelect]',
70
+ standalone: true,
71
+ providers: [
72
+ { provide: KJ_TREE_SELECT, useExisting: KjTreeSelect },
73
+ { provide: KJ_LIST_NAVIGATOR_CONFIG, useExisting: forwardRef(() => KjTreeSelect) },
74
+ KjSelectionModel,
75
+ KjOverlayController,
76
+ ],
77
+ })
78
+ export class KjTreeSelect implements KjListNavigatorConfig, KjTreeSelectContext {
79
+ // Note: selection state (current value, isSelected membership) lives
80
+ // on the shared `KjSelectionModel` injected by `KjListItem`. The
81
+ // tree-select context here only owns tree-specific surface:
82
+ // expansion, the node-data signal, and the public `selectionMode`
83
+ // input name. Programmatic selection routes through the same
84
+ // `kjValue` signal that the selection model writes to — see
85
+ // `afterSelect` below.
86
+ /** @internal — shared overlay controller; trigger + content + nodes all see this same instance. */
87
+ private readonly controller = inject(KjOverlayController);
88
+
89
+ /** Tree node data. Each node may carry children, forming a hierarchy. */
90
+ readonly kjNodes = input<readonly KjTreeNode[]>([]);
91
+
92
+ /** Selection mode: `'single'` closes the panel on select; `'multiple'` toggles. */
93
+ readonly kjSelectionMode = input<'single' | 'multiple'>('single');
94
+
95
+ /**
96
+ * Two-way model for the selected value(s). In `'single'` mode this holds
97
+ * one value (or `undefined`); in `'multiple'` mode it holds an array.
98
+ */
99
+ readonly kjValue = model<unknown>(undefined);
100
+
101
+ /**
102
+ * Optional tree topology used by `KjSelectionModel` in `'leaf'` /
103
+ * `'cascade'` selection modes. Exposed as a signal so the model can
104
+ * follow it reactively without `KjTreeSelect` having to inject the
105
+ * model (that would re-introduce the NG0200 cycle).
106
+ */
107
+ readonly kjTreeShape = input<KjTreeShape<unknown> | null>(null);
108
+
109
+ /** Set of node IDs that are currently expanded (two-way bindable). */
110
+ readonly kjExpandedKeys = model<readonly unknown[]>([]);
111
+
112
+ /** Emitted when a node is selected. */
113
+ readonly kjNodeSelect = output<unknown>();
114
+
115
+ /** Emitted when a node branch is expanded. */
116
+ readonly kjNodeExpand = output<unknown>();
117
+
118
+ /** Emitted when a node branch is collapsed. */
119
+ readonly kjNodeCollapse = output<unknown>();
120
+
121
+ /** Stable panel id used for `aria-controls`. */
122
+ readonly panelId = `kj-tree-select-panel-${++_treeSelectIdCounter}`;
123
+
124
+ private readonly _expandedIds = signal<Set<string>>(new Set());
125
+ private readonly _expandedValues = signal<Set<unknown>>(new Set());
126
+
127
+ // ── KjListNavigatorConfig implementation ────────────────────────────
128
+
129
+ /**
130
+ * Items contributed by `KjListItem` instances under this root. Empty
131
+ * until node-level wiring lands (Task 3 of the migration plan); kept
132
+ * here now so the config contract is satisfied today.
133
+ */
134
+ readonly items = contentChildren(KjListItem, { descendants: true });
135
+
136
+ /**
137
+ * Single canonical value signal. Shared with the legacy `kjValue`
138
+ * model — `KjSelectionModel` reads / writes through this signal.
139
+ */
140
+ readonly value = this.kjValue as unknown as WritableSignal<
141
+ unknown | readonly unknown[] | null
142
+ >;
143
+
144
+ /**
145
+ * `KjListSelectionMode` view of {@link kjSelectionMode}:
146
+ * - `'single'` → `'single'` (one value, closes panel)
147
+ * - `'multiple'` → `'cascade'` (tri-state tree selection: toggling a
148
+ * parent selects/deselects every leaf descendant; parent renders
149
+ * `aria-checked="mixed"` when only some descendants are selected).
150
+ *
151
+ * Cascade mode requires a tree shape; {@link treeShape} falls back to
152
+ * a shape auto-derived from {@link kjNodes} when the consumer does
153
+ * not supply one.
154
+ */
155
+ readonly mode: Signal<KjListSelectionMode> = computed(() =>
156
+ this.kjSelectionMode() === 'multiple' ? 'cascade' : 'single',
157
+ );
158
+
159
+ /** Equality fn used by `KjSelectionModel`. Defaults to `Object.is`. */
160
+ readonly compareBy = signal<KjCompareFn<unknown>>(Object.is as KjCompareFn<unknown>);
161
+
162
+ /**
163
+ * Effective tree topology. Consumer-supplied `kjTreeShape` takes
164
+ * precedence; otherwise an auto-built shape is derived from the
165
+ * `kjNodes` input so cascade mode works out of the box for the
166
+ * canonical data-driven usage.
167
+ */
168
+ readonly treeShape = computed<KjTreeShape<unknown> | null>(() =>
169
+ this.kjTreeShape() ?? this._nodeShape(),
170
+ );
171
+
172
+ /**
173
+ * @internal Tree shape built from the `kjNodes` data structure. Walks
174
+ * each branch once to populate parent / children / leaf maps keyed by
175
+ * `node.value`.
176
+ */
177
+ private readonly _nodeShape = computed<KjTreeShape<unknown> | null>(() => {
178
+ const roots = this.kjNodes();
179
+ if (roots.length === 0) return null;
180
+ const parentOf = new Map<unknown, unknown | null>();
181
+ const childrenOf = new Map<unknown, unknown[]>();
182
+ const walk = (node: KjTreeNode, parent: unknown | null): void => {
183
+ parentOf.set(node.value, parent);
184
+ const kids = node.children ?? [];
185
+ childrenOf.set(node.value, kids.map(c => c.value));
186
+ for (const c of kids) walk(c, node.value);
187
+ };
188
+ for (const r of roots) walk(r, null);
189
+ return {
190
+ getParent: (n) => parentOf.get(n) ?? null,
191
+ getChildren:(n) => childrenOf.get(n) ?? [],
192
+ isLeaf: (n) => (childrenOf.get(n)?.length ?? 0) === 0,
193
+ };
194
+ });
195
+
196
+ /**
197
+ * Hook called by `KjListItem` after it toggles the shared selection
198
+ * model. Re-emits the public `kjNodeSelect` output so consumers see
199
+ * the same notification they saw before the migration (the legacy
200
+ * per-node `handleClick` emitted it; activation now happens inside
201
+ * `KjListItem._activate`, so the root is the only place left to
202
+ * forward it). In single mode (the only mode that returns
203
+ * `closeRequested: true`) we then close the overlay; multi keeps the
204
+ * panel open.
205
+ */
206
+ afterSelect(value: unknown, closeRequested: boolean): void {
207
+ if (value !== undefined) this.kjNodeSelect.emit(value);
208
+ if (closeRequested) this.controller.close('programmatic');
209
+ }
210
+
211
+ // ── KjTreeSelectContext implementation ──────────────────────────────
212
+
213
+ readonly selectionMode = computed(() => this.kjSelectionMode());
214
+ readonly expandedIds = computed(() =>
215
+ new Set(this._expandedIds()) as ReadonlySet<string>,
216
+ );
217
+ readonly expandedValues = computed(() =>
218
+ new Set(this._expandedValues()) as ReadonlySet<unknown>,
219
+ );
220
+ readonly nodes = computed(() => this.kjNodes());
221
+
222
+ /** Directly-injected selection model; wired via `bind()` below. */
223
+ private readonly _selection = inject(KjSelectionModel);
224
+
225
+ constructor() {
226
+ this._selection.bind({
227
+ value: this.value,
228
+ items: this.items,
229
+ mode: this.mode,
230
+ compareBy: this.compareBy,
231
+ treeShape: this.treeShape,
232
+ });
233
+
234
+ // Sync kjExpandedKeys (controlled input) → _expandedValues
235
+ effect(() => {
236
+ const keys = this.kjExpandedKeys();
237
+ const next = new Set(keys);
238
+ const current = untracked(this._expandedValues);
239
+ if (!setsEqual(current, next)) {
240
+ this._expandedValues.set(next);
241
+ }
242
+ });
243
+ }
244
+
245
+ // ── Expansion ops ────────────────────────────────────────────────────
246
+
247
+ toggleNode(nodeId: string): void {
248
+ const set = new Set(this._expandedIds());
249
+ if (set.has(nodeId)) set.delete(nodeId);
250
+ else set.add(nodeId);
251
+ this._expandedIds.set(set);
252
+ }
253
+
254
+ toggleNodeByValue(value: unknown): void {
255
+ const set = new Set(this._expandedValues());
256
+ const wasExpanded = set.has(value);
257
+ if (wasExpanded) set.delete(value);
258
+ else set.add(value);
259
+ this._expandedValues.set(set);
260
+ this.kjExpandedKeys.set(Array.from(set));
261
+ if (wasExpanded) this.kjNodeCollapse.emit(value);
262
+ else this.kjNodeExpand.emit(value);
263
+ }
264
+
265
+ isExpanded(nodeId: string): boolean {
266
+ return this._expandedIds().has(nodeId);
267
+ }
268
+
269
+ isValueExpanded(value: unknown): boolean {
270
+ return this._expandedValues().has(value);
271
+ }
272
+ }
@@ -0,0 +1,90 @@
1
+ import { Directive, booleanAttribute, inject, input } from '@angular/core';
2
+ import { KjFocusRing } from '../primitives';
3
+ import { KjOverlayTrigger } from '../primitives/overlay/trigger';
4
+ import type { KjOverlayPanel } from '../primitives/overlay/panel';
5
+ import { KjOverlayController } from '../primitives/overlay/controller';
6
+ import {
7
+ KJ_OVERLAY_TRIGGER_EVENT_STRATEGY,
8
+ KJ_OVERLAY_PANEL_ROLE,
9
+ type KjTriggerEventStrategy,
10
+ } from '../primitives/overlay/tokens';
11
+ import { onClick } from '../primitives/overlay/strategies/trigger-event/on-click';
12
+
13
+ /**
14
+ * Wraps `onClick()` and forces `ariaHasPopup` to `'tree'` so the trigger
15
+ * advertises its panel role to assistive tech.
16
+ */
17
+ function treeClickTrigger(): KjTriggerEventStrategy {
18
+ const inner = onClick();
19
+ return {
20
+ ariaHasPopup: 'tree',
21
+ attach: c => inner.attach(c),
22
+ bindToggle: t => inner.bindToggle(t),
23
+ onOpen: () => inner.onOpen?.(),
24
+ onClose: () => inner.onClose?.(),
25
+ detach: () => inner.detach(),
26
+ };
27
+ }
28
+
29
+ /**
30
+ * Trigger button that opens/closes the Tree Select panel. Composes the
31
+ * overlay `KjOverlayTrigger` host directive (which wires `aria-expanded`,
32
+ * `aria-controls`, `aria-haspopup`, and click handling) and forces
33
+ * `role="combobox"` on the host element.
34
+ *
35
+ * @example
36
+ * ```html
37
+ * <button kjTreeSelectTrigger>Select category</button>
38
+ * ```
39
+ * @doc-category Core/Inputs
40
+ */
41
+ @Directive({
42
+ selector: '[kjTreeSelectTrigger]',
43
+ exportAs: 'kjTreeSelectTrigger',
44
+ standalone: true,
45
+ hostDirectives: [
46
+ KjFocusRing,
47
+ { directive: KjOverlayTrigger, inputs: ['kjOpen'] },
48
+ ],
49
+ providers: [
50
+ {
51
+ provide: KJ_OVERLAY_TRIGGER_EVENT_STRATEGY,
52
+ useFactory: () => treeClickTrigger(),
53
+ },
54
+ { provide: KJ_OVERLAY_PANEL_ROLE, useValue: 'tree' as const },
55
+ ],
56
+ host: {
57
+ 'role': 'combobox',
58
+ '(keydown)': 'onKeydown($event)',
59
+ },
60
+ })
61
+ export class KjTreeSelectTrigger {
62
+ /** @internal */
63
+ readonly controller = inject(KjOverlayController);
64
+
65
+ /** Disabled state. */
66
+ readonly kjDisabled = input(false, { transform: booleanAttribute });
67
+
68
+ /** @internal */
69
+ onKeydown(event: KeyboardEvent): void {
70
+ switch (event.key) {
71
+ case 'ArrowDown':
72
+ case ' ':
73
+ case 'Enter':
74
+ event.preventDefault();
75
+ if (!this.controller.isOpen()) this.controller.open();
76
+ break;
77
+ case 'Escape':
78
+ if (this.controller.isOpen()) {
79
+ event.preventDefault();
80
+ this.controller.close('programmatic');
81
+ }
82
+ break;
83
+ }
84
+ }
85
+
86
+ private readonly _overlayTrigger = inject(KjOverlayTrigger, { self: true });
87
+ attachPanel(panel: KjOverlayPanel): void {
88
+ this._overlayTrigger.attachPanel(panel);
89
+ }
90
+ }