@kouji-ui/core 0.0.3 → 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 +6 -3
  2. package/CHANGELOG.md +177 -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 +14 -6
  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 +102 -43
  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 +178 -25
  472. package/src/toast/toast.strategy.ts +10 -0
  473. package/src/toast/toast.ts +178 -22
  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 -102
  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,22 @@
1
+ export {
2
+ KjCarousel,
3
+ KjCarouselViewport,
4
+ KjCarouselSlide,
5
+ KjCarouselPrevious,
6
+ KjCarouselNext,
7
+ KjCarouselIndicators,
8
+ KjCarouselIndicator,
9
+ KjCarouselAutoplay,
10
+ KjCarouselPauseToggle,
11
+ } from './carousel';
12
+ export {
13
+ KJ_CAROUSEL,
14
+ KJ_CAROUSEL_SLIDE,
15
+ KJ_CAROUSEL_INDICATORS,
16
+ type KjCarouselContext,
17
+ type KjCarouselSlideContext,
18
+ type KjCarouselIndicatorsContext,
19
+ type KjCarouselOrientation,
20
+ type KjCarouselAlign,
21
+ type KjCarouselControlPattern,
22
+ } from './carousel.context';
@@ -0,0 +1,205 @@
1
+ import {
2
+ computed,
3
+ contentChildren,
4
+ Directive,
5
+ forwardRef,
6
+ inject,
7
+ OnDestroy,
8
+ } from '@angular/core';
9
+ import { KjListItem, injectListItem } from '../primitives/list';
10
+ import { KJ_CASCADE_SELECT } from './cascade-select.context';
11
+ import { KjCascadeSelectSubPanel } from './cascade-select-sub-panel';
12
+
13
+ /**
14
+ * Single row inside a `[kjCascadeSelectPanel]` or
15
+ * `[kjCascadeSelectSubPanel]`. When projected `[kjCascadeSelectSubPanel]`
16
+ * content is present the option becomes a **sub-trigger** (branch node);
17
+ * otherwise it is a **leaf** that commits a value on activation.
18
+ *
19
+ * Composes `KjListItem` via `hostDirectives` — the list item owns the
20
+ * stable `id`, the click / Enter / Space activation, `aria-selected`
21
+ * (via the shared `KjSelectionModel` in `'leaf'` mode), and
22
+ * `aria-disabled`. This directive contributes cascade-specific
23
+ * semantics: `role="treeitem"`, branch `aria-haspopup` / `aria-expanded`,
24
+ * and hover-open / hover-close timers for sub-panels.
25
+ *
26
+ * Branch detection is signal-driven: `KjCascadeSelectSubPanel` instances
27
+ * declared inside this option call `_registerSubPanel()` from their
28
+ * constructor (they inject this directive via the element injector).
29
+ * No `contentChildren` + binding effect — straightforward DI both ways.
30
+ *
31
+ * Branch clicks are no-ops at the selection-model level (leaf mode
32
+ * filters them out when a `treeShape` is provided on the root) but
33
+ * still toggle their own sub-panel via this directive's
34
+ * `handleClick()`.
35
+ *
36
+ * Role: `treeitem`.
37
+ *
38
+ * @example Leaf
39
+ * ```html
40
+ * <div kjCascadeSelectOption [kjValue]="'sf'" kjLabel="San Francisco" />
41
+ * ```
42
+ *
43
+ * @example Branch
44
+ * ```html
45
+ * <div kjCascadeSelectOption [kjValue]="'us'" kjLabel="USA">
46
+ * <div kjCascadeSelectSubPanel>…</div>
47
+ * </div>
48
+ * ```
49
+ * @doc-category Core/Data input
50
+ * @doc
51
+ * @doc-name cascade-select
52
+ */
53
+ @Directive({
54
+ selector: '[kjCascadeSelectOption]',
55
+ exportAs: 'kjCascadeSelectOption',
56
+ standalone: true,
57
+ hostDirectives: [
58
+ {
59
+ directive: KjListItem,
60
+ inputs: [
61
+ 'kjItemValue:kjValue',
62
+ 'kjItemLabel:kjLabel',
63
+ 'kjDisabled:kjDisabled',
64
+ ],
65
+ },
66
+ ],
67
+ host: {
68
+ 'role': 'treeitem',
69
+ '[attr.aria-haspopup]': 'isBranch() ? "tree" : null',
70
+ '[attr.aria-expanded]': 'isBranch() ? subPanelOpen().toString() : null',
71
+ '[attr.data-label]': 'item.label()',
72
+ '[attr.data-leaf]': 'isLeaf().toString()',
73
+ '[attr.data-selected]': 'item.ariaSelected() === "true" ? "" : null',
74
+ '[attr.data-active-path]': 'isOnActivePath() ? "" : null',
75
+ '(click)': 'handleClick()',
76
+ '(mouseenter)': 'handleMouseEnter()',
77
+ '(mouseleave)': 'handleMouseLeave()',
78
+ },
79
+ })
80
+ export class KjCascadeSelectOption implements OnDestroy {
81
+ /** @internal — composed list-item primitive providing id/value/disabled/activation. */
82
+ readonly item = injectListItem<unknown>();
83
+ /** @internal */
84
+ readonly ctx = inject(KJ_CASCADE_SELECT);
85
+
86
+ /**
87
+ * @internal Closest ancestor sub-panel (if any). `null` when this
88
+ * option sits in the root cascade panel. Used by `KjCascadeSelect`'s
89
+ * `openSubPanel` chain rebuild — walking parent panels up to the
90
+ * root produces the exact open-chain that should be set, so opening
91
+ * a sibling at any level closes its siblings automatically (instead
92
+ * of leaving stale entries that a late-firing close timer can then
93
+ * slice through).
94
+ *
95
+ * `forwardRef` defers the class lookup so the option ↔ sub-panel
96
+ * import cycle doesn't trip on first evaluation.
97
+ */
98
+ readonly parentSubPanel = inject<KjCascadeSelectSubPanel | null>(
99
+ forwardRef(() => KjCascadeSelectSubPanel),
100
+ { optional: true },
101
+ );
102
+
103
+ // `kjValue`, `kjLabel`, `kjDisabled` are exposed via the composed
104
+ // `KjListItem` host directive above. Don't redeclare them on this
105
+ // class — duplicate public-name inputs route to the local declaration
106
+ // and leave `KjListItem.kjItemValue` unset. Read values back through
107
+ // `this.item.value()` / `this.item.label()`.
108
+
109
+ /**
110
+ * @internal Sub-panels projected as content of this option. Any
111
+ * `[kjCascadeSelectSubPanel]` declared inside the option's content
112
+ * makes it a branch — Angular keeps this signal in sync.
113
+ */
114
+ private readonly _subPanels = contentChildren(
115
+ forwardRef(() => KjCascadeSelectSubPanel),
116
+ { descendants: true },
117
+ );
118
+
119
+ /** @internal Whether this option is a branch node. */
120
+ readonly isBranch = computed(() => this._subPanels().length > 0);
121
+
122
+ /** @internal Inverse of `isBranch` for the `data-leaf` attribute. */
123
+ readonly isLeaf = computed(() => !this.isBranch());
124
+
125
+ private _openTimer: ReturnType<typeof setTimeout> | undefined;
126
+ private _closeTimer: ReturnType<typeof setTimeout> | undefined;
127
+
128
+ /** @internal True when this option's sub-panel is open. */
129
+ readonly subPanelOpen = computed(() =>
130
+ this.isBranch()
131
+ ? this.ctx.openSubPanels().includes(this.item.id)
132
+ : false,
133
+ );
134
+
135
+ /** @internal True when the selected value is in this option's sub-tree. */
136
+ readonly isOnActivePath = computed(() => {
137
+ const path = this.ctx.path();
138
+ const v = this.item.value();
139
+ return v !== undefined && path.includes(v);
140
+ });
141
+
142
+ ngOnDestroy(): void {
143
+ clearTimeout(this._openTimer);
144
+ clearTimeout(this._closeTimer);
145
+ }
146
+
147
+ /**
148
+ * @internal Click handler — branch-only sub-panel toggle. Leaf clicks
149
+ * are handled by the composed `KjListItem` (which calls
150
+ * `KjSelectionModel.toggle` and then `KjCascadeSelect.afterSelect`).
151
+ * In leaf-mode `KjSelectionModel` no-ops on branches when a tree
152
+ * shape is provided, so we don't need to gate that here.
153
+ */
154
+ handleClick(): void {
155
+ if (this.item.disabled()) return;
156
+ if (!this.isBranch()) return;
157
+ clearTimeout(this._openTimer);
158
+ clearTimeout(this._closeTimer);
159
+ if (this.subPanelOpen()) this.ctx.closeSubPanel(this.item.id);
160
+ else this.ctx.openSubPanel(this.item.id);
161
+ }
162
+
163
+ /** @internal */
164
+ handleMouseEnter(): void {
165
+ if (this.item.disabled() || !this.isBranch()) return;
166
+ clearTimeout(this._closeTimer);
167
+ const delay = this.ctx.subPanelOpenDelayMs();
168
+ this._openTimer = setTimeout(() => {
169
+ this.ctx.openSubPanel(this.item.id);
170
+ }, delay);
171
+ }
172
+
173
+ /** @internal */
174
+ handleMouseLeave(): void {
175
+ if (this.item.disabled() || !this.isBranch()) return;
176
+ clearTimeout(this._openTimer);
177
+ this._scheduleClose();
178
+ }
179
+
180
+ /**
181
+ * @internal Called by `KjCascadeSelectSubPanel` when the cursor enters
182
+ * the sub-panel. The option's `mouseleave` fires the moment the cursor
183
+ * crosses out of the option's bounding box — including the case where
184
+ * the cursor moves *into* the adjacent sub-panel. Without this bridge,
185
+ * the close timer kept ticking and dismissed an actively-hovered
186
+ * sub-panel after `subPanelCloseDelayMs`.
187
+ */
188
+ _cancelCloseTimer(): void {
189
+ clearTimeout(this._closeTimer);
190
+ this._closeTimer = undefined;
191
+ }
192
+
193
+ /**
194
+ * @internal Called by `KjCascadeSelectSubPanel` when the cursor leaves
195
+ * the sub-panel (to anywhere except back into the parent option).
196
+ * Restarts the same close timer the option's own `mouseleave` would.
197
+ */
198
+ _scheduleClose(): void {
199
+ const delay = this.ctx.subPanelCloseDelayMs();
200
+ this._closeTimer = setTimeout(() => {
201
+ if (!this.subPanelOpen()) return;
202
+ this.ctx.closeSubPanel(this.item.id);
203
+ }, delay);
204
+ }
205
+ }
@@ -0,0 +1,106 @@
1
+ import {
2
+ Directive,
3
+ inject,
4
+ input,
5
+ } from '@angular/core';
6
+ import { KjOverlayPanel } from '../primitives/overlay/panel';
7
+ import {
8
+ KJ_OVERLAY_MOUNT_STRATEGY,
9
+ KJ_OVERLAY_POSITION_STRATEGY,
10
+ KJ_OVERLAY_PANEL_ROLE,
11
+ } from '../primitives/overlay/tokens';
12
+ import type { KjAlign, KjSide } from '../primitives/overlay/types';
13
+ import { bodyPortal } from '../primitives/overlay/strategies/mount/body-portal';
14
+ import { anchoredTo } from '../primitives/overlay/strategies/position/anchored-to';
15
+ import { KjListNavigator } from '../primitives/list';
16
+ import { KjCascadeSelect } from './cascade-select-root';
17
+
18
+ /**
19
+ * Root panel for a Cascade Select. Composes:
20
+ * - `KjOverlayPanel` — mount / position / aria wiring (rendered with
21
+ * `role="tree"` via the panel-role provider).
22
+ * - `KjListNavigator` (vertical + activedescendant) — generic Up/Down /
23
+ * Home/End / Enter / Space / type-ahead navigation across the
24
+ * level-0 options.
25
+ *
26
+ * Cascade-specific ArrowRight (open the active option's sub-panel) and
27
+ * Escape / Tab (close everything) remain handled here because they
28
+ * carry cascade-specific semantics the generic navigator doesn't
29
+ * cover.
30
+ *
31
+ * @doc-category Core/Data input
32
+ * @doc
33
+ * @doc-name cascade-select
34
+ */
35
+ @Directive({
36
+ selector: '[kjCascadeSelectPanel]',
37
+ exportAs: 'kjCascadeSelectPanel',
38
+ standalone: true,
39
+ hostDirectives: [
40
+ { directive: KjOverlayPanel, inputs: ['kjFor'] },
41
+ {
42
+ directive: KjListNavigator,
43
+ inputs: ['kjOrientation', 'kjFocusMode'],
44
+ },
45
+ ],
46
+ providers: [
47
+ { provide: KJ_OVERLAY_PANEL_ROLE, useValue: 'tree' as const },
48
+ { provide: KJ_OVERLAY_MOUNT_STRATEGY, useFactory: () => bodyPortal() },
49
+ { provide: KJ_OVERLAY_POSITION_STRATEGY, useFactory: () => anchoredTo() },
50
+ ],
51
+ host: {
52
+ 'role': 'tree',
53
+ 'aria-orientation': 'horizontal',
54
+ 'aria-multiselectable': 'false',
55
+ 'tabindex': '-1',
56
+ 'kjOrientation': 'vertical',
57
+ 'kjFocusMode': 'activedescendant',
58
+ '(keydown)': 'onKeydown($event)',
59
+ '(click)': '$event.stopPropagation()',
60
+ },
61
+ })
62
+ export class KjCascadeSelectPanel {
63
+ /** @internal — root directive (typed). Owns sub-panel state + option lookup. */
64
+ private readonly root = inject(KjCascadeSelect);
65
+ /** @internal — generic list navigator composed via `hostDirectives`. */
66
+ private readonly nav = inject(KjListNavigator);
67
+
68
+ /** Anchored side relative to the trigger. */
69
+ readonly kjSide = input<KjSide>('bottom');
70
+ /** Cross-axis alignment relative to the trigger. */
71
+ readonly kjAlign = input<KjAlign>('start');
72
+
73
+ constructor() {
74
+ const pos = inject(KJ_OVERLAY_POSITION_STRATEGY) as ReturnType<typeof anchoredTo>;
75
+ pos.configure({ side: this.kjSide, align: this.kjAlign, matchTriggerWidth: 'min' });
76
+ }
77
+
78
+ /**
79
+ * @internal Cascade-specific keys — ArrowRight opens the active
80
+ * branch's sub-panel; Escape / Tab dismiss everything. Up/Down /
81
+ * Home/End / Enter / Space / typeahead all flow through the composed
82
+ * `KjListNavigator`.
83
+ */
84
+ onKeydown(e: KeyboardEvent): void {
85
+ switch (e.key) {
86
+ case 'ArrowRight': {
87
+ e.preventDefault();
88
+ const activeId = this.nav.activeId();
89
+ if (!activeId) return;
90
+ const option = this.root.findOption(activeId);
91
+ if (option?.isBranch()) this.root.openSubPanel(option.item.id);
92
+ return;
93
+ }
94
+ case 'Escape':
95
+ e.preventDefault();
96
+ e.stopPropagation();
97
+ this.root.hide();
98
+ this.root.closeAll();
99
+ return;
100
+ case 'Tab':
101
+ this.root.hide();
102
+ this.root.closeAll();
103
+ return;
104
+ }
105
+ }
106
+ }
@@ -0,0 +1,265 @@
1
+ import {
2
+ Directive,
3
+ computed,
4
+ contentChildren,
5
+ forwardRef,
6
+ inject,
7
+ input,
8
+ model,
9
+ output,
10
+ signal,
11
+ type Signal,
12
+ type WritableSignal,
13
+ } from '@angular/core';
14
+ import { KjOverlayController } from '../primitives/overlay/controller';
15
+ import { KJ_SELECT } from '../select/select-root';
16
+ import {
17
+ KJ_LIST_NAVIGATOR_CONFIG,
18
+ KjListItem,
19
+ KjSelectionModel,
20
+ type KjCompareFn,
21
+ type KjListNavigatorConfig,
22
+ type KjListSelectionMode,
23
+ type KjTreeShape,
24
+ } from '../primitives/list';
25
+ import {
26
+ KJ_CASCADE_SELECT,
27
+ type KjCascadeSelectContext,
28
+ } from './cascade-select.context';
29
+ import { KjCascadeSelectOption } from './cascade-select-option';
30
+
31
+ /**
32
+ * Root cascade-select container. Implements
33
+ * {@link KjListNavigatorConfig} (items, value, mode, compareBy,
34
+ * treeShape, afterSelect) and provides itself under
35
+ * `KJ_LIST_NAVIGATOR_CONFIG` via `forwardRef`. Each cascade panel +
36
+ * sub-panel hosts its own `KjListNavigator` (vertical +
37
+ * activedescendant). All tree-walking logic — branch gating, path
38
+ * derivation, isLeaf — lives in the shared `KjSelectionModel`, which
39
+ * auto-derives the tree shape from each `KjListItem.parent` DI pointer.
40
+ * The root just owns cascade-specific UX: the sub-panel open-chain and
41
+ * the overlay close-on-commit behavior.
42
+ *
43
+ * Like `KjSelect`, the root does NOT inject `KjSelectionModel` at
44
+ * construction — doing so creates a runtime cycle (the model's
45
+ * `inject(KJ_LIST_NAVIGATOR_CONFIG)` would resolve to a still-
46
+ * constructing root). Lazy access via the element `Injector` resolves
47
+ * the model after construction finishes.
48
+ *
49
+ * @example
50
+ * ```html
51
+ * <div kjCascadeSelect [(kjValue)]="city">
52
+ * <button kjCascadeSelectTrigger #t="kjCascadeSelectTrigger">Pick</button>
53
+ * <div kjCascadeSelectPanel [kjFor]="t">
54
+ * <div kjCascadeSelectOption [kjValue]="'us'" kjLabel="USA">…</div>
55
+ * </div>
56
+ * </div>
57
+ * ```
58
+ *
59
+ * @doc-category Core/Data input
60
+ * @doc
61
+ * @doc-name cascade-select
62
+ * @doc-is-main
63
+ * @doc-description Unstyled cascade-select root that implements KjListNavigatorConfig and owns the shared selection model.
64
+ */
65
+ @Directive({
66
+ selector: '[kjCascadeSelect]',
67
+ exportAs: 'kjCascadeSelect',
68
+ standalone: true,
69
+ providers: [
70
+ { provide: KJ_CASCADE_SELECT, useExisting: forwardRef(() => KjCascadeSelect) },
71
+ { provide: KJ_SELECT, useExisting: forwardRef(() => KjCascadeSelect) },
72
+ { provide: KJ_LIST_NAVIGATOR_CONFIG, useExisting: forwardRef(() => KjCascadeSelect) },
73
+ KjSelectionModel,
74
+ KjOverlayController,
75
+ ],
76
+ })
77
+ export class KjCascadeSelect implements KjListNavigatorConfig, KjCascadeSelectContext {
78
+ /** @internal — shared overlay controller; trigger + panel + options share this. */
79
+ private readonly _controller = inject(KjOverlayController);
80
+
81
+ /** Two-way bindable selected leaf value. */
82
+ readonly kjValue = model<unknown>(undefined);
83
+
84
+ /** Two-way bindable path of values from root to selected leaf. */
85
+ readonly kjCascadePath = model<readonly unknown[]>([]);
86
+
87
+ /**
88
+ * Optional consumer-supplied tree topology. When omitted, the
89
+ * selection model auto-derives one from each `KjListItem.parent`
90
+ * pointer (which Angular resolves via the element injector — the
91
+ * outer cascade option of a sub-panel's contents). Supply this only
92
+ * when the consumer's data model isn't a faithful match for the
93
+ * projected DOM (rare).
94
+ */
95
+ readonly kjTreeShape = input<KjTreeShape<unknown> | null>(null);
96
+
97
+ /** Sub-panel hover-open delay in ms. Default 100 (matches Dropdown Menu). */
98
+ readonly kjSubPanelOpenDelayMs = input<number>(100);
99
+
100
+ /** Sub-panel hover-out close delay in ms. Default 300 (prevents flicker). */
101
+ readonly kjSubPanelCloseDelayMs = input<number>(300);
102
+
103
+ /** Emitted when the active-descendant crosses a level boundary. */
104
+ readonly kjLevelChange = output<{ levelIndex: number }>();
105
+
106
+ // ── KjListNavigatorConfig implementation ──────────────────────────
107
+ /** All `KjListItem`s under this cascade — source for the navigators. */
108
+ readonly items = contentChildren(KjListItem, { descendants: true });
109
+
110
+ /**
111
+ * Every projected `KjCascadeSelectOption`. Used by {@link findOption}
112
+ * to resolve a `KjListItem` id (typically the navigator's active id)
113
+ * to its wrapping option directive — no `document.getElementById` /
114
+ * `querySelector` lookups in panel keydown handlers.
115
+ */
116
+ private readonly _options = contentChildren(KjCascadeSelectOption, { descendants: true });
117
+
118
+ private readonly _optionsById = computed(() => {
119
+ const map = new Map<string, KjCascadeSelectOption>();
120
+ for (const o of this._options()) map.set(o.item.id, o);
121
+ return map;
122
+ });
123
+
124
+ /**
125
+ * Resolve the `KjCascadeSelectOption` wrapping the given
126
+ * `KjListItem.id`. Returns `undefined` when no projected option
127
+ * matches.
128
+ */
129
+ findOption(itemId: string): KjCascadeSelectOption | undefined {
130
+ return this._optionsById().get(itemId);
131
+ }
132
+
133
+ /**
134
+ * Implements `KjListNavigatorConfig.value`. Shared with `kjValue` so
135
+ * `KjSelectionModel` reads / writes through the same signal — one
136
+ * source of truth.
137
+ */
138
+ readonly value = this.kjValue as unknown as WritableSignal<
139
+ unknown | readonly unknown[] | null
140
+ >;
141
+
142
+ /**
143
+ * Cascade is single-leaf select: exactly one leaf value lives in
144
+ * `kjValue`. The selection model's `'single'` + tree-shape branch
145
+ * gate blocks non-leaf commits automatically — branches only toggle
146
+ * their own sub-panel via `KjCascadeSelectOption.handleClick`.
147
+ */
148
+ readonly mode: Signal<KjListSelectionMode> = signal<KjListSelectionMode>('single');
149
+
150
+ /** Implements `KjListNavigatorConfig.compareBy`. */
151
+ readonly compareBy = signal<KjCompareFn<unknown>>(Object.is as KjCompareFn<unknown>);
152
+
153
+ /** Re-exposes `kjTreeShape` under the {@link KjListNavigatorConfig} key. */
154
+ readonly treeShape = this.kjTreeShape;
155
+
156
+ /**
157
+ * Selection model is injected directly. The model uses a {@link
158
+ * KjSelectionModel.bind} call to receive this root's canonical
159
+ * signals — that pattern keeps the DI graph acyclic (the model does
160
+ * not inject `KJ_LIST_NAVIGATOR_CONFIG` back, so providing both on
161
+ * the same element no longer collides).
162
+ */
163
+ private readonly _selection = inject(KjSelectionModel);
164
+
165
+ constructor() {
166
+ this._selection.bind({
167
+ value: this.value,
168
+ items: this.items,
169
+ mode: this.mode,
170
+ compareBy: this.compareBy,
171
+ treeShape: this.treeShape,
172
+ });
173
+ }
174
+
175
+ // ── KjSelectContext-compatible surface ──────────────────────────────
176
+ /** Open state — drives `KjSelect.open` for downstream consumers. */
177
+ readonly open = this._controller.isOpen;
178
+
179
+ /** Compatibility shim — KjSelect-style single setter. Closes the panel. */
180
+ select(val: unknown): void {
181
+ this.kjValue.set(val);
182
+ this._controller.close('programmatic');
183
+ }
184
+
185
+ /** Compatibility shim — toggles the overlay. */
186
+ toggle(): void {
187
+ if (this._controller.isOpen()) this._controller.close('programmatic');
188
+ else this._controller.open();
189
+ }
190
+
191
+ // ── KjCascadeSelectContext implementation ──────────────────────────
192
+ private readonly _openSubPanels = signal<readonly string[]>([]);
193
+
194
+ /** Path of values from root option to the selected leaf. */
195
+ readonly path = computed(() => this.kjCascadePath());
196
+
197
+ /** Open sub-panel ownerOptionIds (level → level chain). */
198
+ readonly openSubPanels = this._openSubPanels.asReadonly();
199
+
200
+ /** Hover-open delay (ms). */
201
+ readonly subPanelOpenDelayMs = computed(() => this.kjSubPanelOpenDelayMs());
202
+ /** Hover-out close delay (ms). */
203
+ readonly subPanelCloseDelayMs = computed(() => this.kjSubPanelCloseDelayMs());
204
+
205
+ /**
206
+ * Implements `KjListNavigatorConfig.afterSelect`. The selection model
207
+ * already gated branches before reaching here (see
208
+ * `KjSelectionModel.canActivate`), so any defined `value` is a leaf
209
+ * commit: derive the path from the model, surface it on
210
+ * `kjCascadePath`, close every sub-panel, and dismiss the overlay.
211
+ */
212
+ afterSelect(value: unknown, _closeRequested: boolean): void {
213
+ if (value === undefined) return;
214
+ this.kjCascadePath.set(this._selection.pathTo(value));
215
+ this._openSubPanels.set([]);
216
+ this._controller.close('programmatic');
217
+ }
218
+
219
+ /**
220
+ * Open the sub-panel chain ending at `ownerOptionId`. Walking the
221
+ * target's ancestry on every open is the trick that keeps sibling
222
+ * sub-panels mutually exclusive: any stale entries from a prior
223
+ * branch get replaced atomically with the fresh chain, so a
224
+ * mouseleave-queued close timer on a sibling can no longer slice
225
+ * through the new chain after it fires. The walk also cancels any
226
+ * pending close timers along the new chain so a brief inter-option
227
+ * cursor crossing doesn't dismiss a freshly-opened panel.
228
+ */
229
+ openSubPanel(ownerOptionId: string): void {
230
+ const target = this.findOption(ownerOptionId);
231
+ if (!target) {
232
+ // Fallback: option not yet registered (rare — pre-content-init).
233
+ // Append to keep behavior monotonic; the close path is tolerant.
234
+ this._openSubPanels.update(panels =>
235
+ panels.includes(ownerOptionId) ? panels : [...panels, ownerOptionId],
236
+ );
237
+ return;
238
+ }
239
+ const chain: string[] = [];
240
+ let current: typeof target | null = target;
241
+ while (current) {
242
+ current._cancelCloseTimer();
243
+ chain.unshift(current.item.id);
244
+ current = current.parentSubPanel?.parentOption ?? null;
245
+ }
246
+ this._openSubPanels.set(chain);
247
+ }
248
+
249
+ closeSubPanel(ownerOptionId: string): void {
250
+ this._openSubPanels.update(panels => {
251
+ const idx = panels.indexOf(ownerOptionId);
252
+ if (idx < 0) return panels;
253
+ return panels.slice(0, idx);
254
+ });
255
+ }
256
+
257
+ closeAll(): void {
258
+ this._openSubPanels.set([]);
259
+ }
260
+
261
+ /** Closes the root cascade panel. */
262
+ hide(): void {
263
+ this._controller.close('programmatic');
264
+ }
265
+ }