@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
package/src/tabs/tabs.ts CHANGED
@@ -1,130 +1,364 @@
1
- import { Directive, computed, inject, input, model } from '@angular/core';
2
- import { KJ_TABS, KjTabsContext } from './tabs.context';
1
+ import {
2
+ computed,
3
+ Directive,
4
+ effect,
5
+ ElementRef,
6
+ inject,
7
+ input,
8
+ model,
9
+ OnDestroy,
10
+ OnInit,
11
+ output,
12
+ signal,
13
+ Signal,
14
+ untracked,
15
+ } from '@angular/core';
16
+ import { KjRovingTabindex, KjRovingTabindexItemDirective } from '../a11y/roving-tabindex';
17
+ import { KjDisabled } from '../primitives/interaction/disabled';
18
+ import { KjFocusRing } from '../primitives/interaction/focus-ring';
19
+ import {
20
+ KJ_TABS,
21
+ KjTabsActivationMode,
22
+ KjTabsContext,
23
+ KjTabsOrientation,
24
+ } from './tabs.context';
25
+
26
+ let kjTabsSeedCounter = 0;
27
+ function nextSeed(): string {
28
+ // Try crypto.randomUUID where available (browser, jsdom 22+, node 19+).
29
+ // Fall back to a counter so SSR / older environments still produce a stable string.
30
+ const cryptoLike = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;
31
+ if (cryptoLike?.randomUUID) return cryptoLike.randomUUID().slice(0, 8);
32
+ return `kj${(++kjTabsSeedCounter).toString(36)}`;
33
+ }
3
34
 
4
35
  /**
5
- * Root tabs container. Manages active tab value.
36
+ * Root tabs container. Owns the active value, orientation, activation mode,
37
+ * and the registration list of child tabs/panels. Provides `KJ_TABS` to its
38
+ * descendants for ARIA wiring and selection coordination.
39
+ *
6
40
  * @example
7
41
  * ```html
8
- * <div kjTabs [(kjTabsValue)]="activeTab"><div kjTabList>...</div></div>
42
+ * <div kjTabs [(kjValue)]="active" kjOrientation="horizontal" kjActivationMode="automatic">
43
+ * <div kjTabList>
44
+ * <button kjTab kjTabValue="overview">Overview</button>
45
+ * <button kjTab kjTabValue="billing">Billing</button>
46
+ * </div>
47
+ * <div kjTabPanel kjPanelValue="overview">…</div>
48
+ * <div kjTabPanel kjPanelValue="billing">…</div>
49
+ * </div>
9
50
  * ```
51
+ * @doc-category Core/Navigation
10
52
  * @doc
11
- * @doc-file tabs.example.ts
12
- * ```typescript
13
- * import { Component, signal } from '@angular/core';
14
- * import { KjTabs, KjTabList, KjTab, KjTabPanel } from '@kouji-ui/core';
15
- *
16
- * @Component({
17
- * standalone: true,
18
- * imports: [KjTabs, KjTabList, KjTab, KjTabPanel],
19
- * styles: [`
20
- * .tabs { background: #0c0c0c; padding: 2rem; }
21
- * [kjTabList] { display: flex; border-bottom: 1px solid #1e1e1e; margin-bottom: 1.5rem; }
22
- * [kjTab] { background: none; border: none; border-bottom: 2px solid transparent; color: #555; font-family: 'JetBrains Mono', monospace; font-size: 0.875rem; padding: 0.625rem 1.25rem; cursor: pointer; transition: color 0.15s, border-color 0.15s; margin-bottom: -1px; }
23
- * [kjTab][aria-selected="true"] { color: #f0ede6; border-bottom-color: #b8f500; }
24
- * [kjTab]:hover { color: #f0ede6; }
25
- * [kjTabPanel] { color: #666; font-family: 'JetBrains Mono', monospace; font-size: 0.875rem; line-height: 1.6; }
26
- * [hidden] { display: none; }
27
- * `],
28
- * template: `
29
- * <div class="tabs">
30
- * <div kjTabs [(kjTabsValue)]="activeTab" aria-label="Example tabs">
31
- * <div kjTabList>
32
- * <button kjTab [kjTabValue]="'tab1'">Overview</button>
33
- * <button kjTab [kjTabValue]="'tab2'">API</button>
34
- * <button kjTab [kjTabValue]="'tab3'">Examples</button>
35
- * </div>
36
- * <div kjTabPanel [kjPanelFor]="'tab1'">The overview tab content goes here.</div>
37
- * <div kjTabPanel [kjPanelFor]="'tab2'">API reference content goes here.</div>
38
- * <div kjTabPanel [kjPanelFor]="'tab3'">Code examples go here.</div>
39
- * </div>
40
- * </div>
41
- * `,
42
- * })
43
- * export class TabsExampleComponent {
44
- * activeTab = signal('tab1');
45
- * }
46
- * ```
47
- * @category Core/Navigation
53
+ * @doc-name tabs
54
+ * @doc-description Unstyled tabs root with active-value, orientation, and activation mode for tab and panel children.
55
+ * @doc-is-main
48
56
  */
49
57
  @Directive({
50
58
  selector: '[kjTabs]',
51
59
  standalone: true,
52
60
  providers: [{ provide: KJ_TABS, useExisting: KjTabs }],
61
+ host: {
62
+ '[attr.data-orientation]': 'kjOrientation()',
63
+ },
53
64
  })
54
65
  export class KjTabs implements KjTabsContext {
55
- /** Two-way bound active tab value. */
56
- kjTabsValue = model<string>('');
66
+ /** Two-way bound active tab value. String-keyed; `''` means "no tab active yet". */
67
+ readonly kjValue = model<string>('');
68
+
69
+ /** Tabs orientation. Drives `aria-orientation` on the tablist and the arrow-key axis. */
70
+ readonly kjOrientation = input<KjTabsOrientation>('horizontal');
71
+
72
+ /**
73
+ * Activation mode per WAI-ARIA APG.
74
+ * - `'automatic'` (default): focus moves and the focused tab is activated immediately.
75
+ * - `'manual'`: focus moves only; Enter or Space activates the focused tab.
76
+ */
77
+ readonly kjActivationMode = input<KjTabsActivationMode>('automatic');
78
+
79
+ /** Read-only view of the active tab value. */
80
+ readonly value: Signal<string> = this.kjValue.asReadonly();
81
+
82
+ /** Read-only view of the orientation. */
83
+ readonly orientation: Signal<KjTabsOrientation> = this.kjOrientation;
84
+
85
+ /** Read-only view of the activation mode. */
86
+ readonly activationMode: Signal<KjTabsActivationMode> = this.kjActivationMode;
87
+
88
+ private readonly idSeed = nextSeed();
89
+
90
+ /** @internal Map of value → registered tab; keeps document order via insertion. */
91
+ private readonly _tabs = signal<readonly KjTab[]>([]);
92
+
93
+ /** Public read-only registration list, in document order. */
94
+ readonly tabs = this._tabs.asReadonly();
95
+
96
+ /** Currently resolved active tab (may be undefined when value matches no tab). */
97
+ readonly activeTab = computed<KjTab | undefined>(() =>
98
+ this._tabs().find((t) => t.kjTabValue() === this.kjValue()),
99
+ );
57
100
 
58
- /** Read-only signal of the current active tab value. */
59
- readonly value = this.kjTabsValue.asReadonly();
101
+ constructor() {
102
+ // Default-value reconciliation: when kjValue is unset (`''`) and at least one
103
+ // non-disabled tab is registered, activate the first non-disabled tab.
104
+ effect(() => {
105
+ const list = this._tabs();
106
+ const current = this.kjValue();
107
+ if (current === '' && list.length > 0) {
108
+ const first = untracked(() => list.find((t) => !t.kjTabDisabled()) ?? list[0]);
109
+ if (first) {
110
+ // Defer to avoid writing during a parent change-detection pass.
111
+ queueMicrotask(() => {
112
+ if (this.kjValue() === '') this.kjValue.set(first.kjTabValue());
113
+ });
114
+ }
115
+ }
116
+ });
117
+ }
118
+
119
+ /** @internal Called by `KjTab.ngOnInit` to register itself in document order. */
120
+ register(tab: KjTab): void {
121
+ this._tabs.update((list) => (list.includes(tab) ? list : [...list, tab]));
122
+ }
123
+
124
+ /** @internal Called by `KjTab` on destroy. */
125
+ unregister(tab: KjTab): void {
126
+ this._tabs.update((list) => list.filter((t) => t !== tab));
127
+ }
128
+
129
+ /** Returns whether the tab/panel pair for `value` is the active one. */
130
+ isActive(value: string): boolean {
131
+ return this.kjValue() === value;
132
+ }
133
+
134
+ /** Stable id for the tab element associated with `value`. */
135
+ tabId(value: string): string {
136
+ return `kj-tab-${value}-${this.idSeed}`;
137
+ }
138
+
139
+ /** Stable id for the panel element associated with `value`. */
140
+ panelId(value: string): string {
141
+ return `kj-panel-${value}-${this.idSeed}`;
142
+ }
143
+
144
+ /** Activates the tab/panel pair for `value`. No-op if the matching tab is disabled. */
145
+ select(value: string): void {
146
+ const target = this._tabs().find((t) => t.kjTabValue() === value);
147
+ if (target?.kjTabDisabled()) return;
148
+ if (this.kjValue() !== value) this.kjValue.set(value);
149
+ }
150
+
151
+ /** Imperative: advance to the next non-disabled tab. Clamps at the end. */
152
+ next(): void {
153
+ const list = this._tabs();
154
+ const idx = list.findIndex((t) => t.kjTabValue() === this.kjValue());
155
+ for (let i = idx + 1; i < list.length; i++) {
156
+ if (!list[i].kjTabDisabled()) {
157
+ this.select(list[i].kjTabValue());
158
+ return;
159
+ }
160
+ }
161
+ }
60
162
 
61
- /** Activates the tab with the given value. */
62
- activate(val: string): void {
63
- this.kjTabsValue.set(val);
163
+ /** Imperative: advance to the previous non-disabled tab. Clamps at the start. */
164
+ previous(): void {
165
+ const list = this._tabs();
166
+ const idx = list.findIndex((t) => t.kjTabValue() === this.kjValue());
167
+ for (let i = idx - 1; i >= 0; i--) {
168
+ if (!list[i].kjTabDisabled()) {
169
+ this.select(list[i].kjTabValue());
170
+ return;
171
+ }
172
+ }
64
173
  }
65
174
  }
66
175
 
67
176
  /**
68
- * Container for tab triggers. Sets `role="tablist"`.
69
- * @category Core/Navigation
177
+ * Tab list container. Hosts `role="tablist"` + `aria-orientation` and composes
178
+ * `KjRovingTabindex` to manage the single-tab-stop arrow-key navigation.
179
+ *
180
+ * Orientation is read from the parent `KJ_TABS` context and forwarded to the
181
+ * roving primitive through its `kjRovingOrientation` input via a host binding
182
+ * on the exposed input.
183
+ *
184
+ * @doc-category Core/Navigation
185
+ * @doc
186
+ * @doc-name tabs
70
187
  */
71
188
  @Directive({
72
189
  selector: '[kjTabList]',
73
190
  standalone: true,
74
- host: { role: 'tablist' },
191
+ hostDirectives: [KjRovingTabindex],
192
+ host: {
193
+ '[attr.role]': '"tablist"',
194
+ '[attr.aria-orientation]': 'tabs.orientation()',
195
+ '[attr.data-orientation]': 'tabs.orientation()',
196
+ },
75
197
  })
76
- export class KjTabList {}
198
+ export class KjTabList implements OnInit, OnDestroy {
199
+ /** @internal */
200
+ readonly tabs = inject(KJ_TABS);
201
+ private readonly el = inject<ElementRef<HTMLElement>>(ElementRef);
202
+
203
+ private readonly keydownFilter = (event: KeyboardEvent): void => {
204
+ const orientation = this.tabs.orientation();
205
+ if (orientation === 'horizontal') {
206
+ if (event.key === 'ArrowUp' || event.key === 'ArrowDown') {
207
+ // Off-axis: prevent the composed KjRovingTabindex from acting.
208
+ event.stopImmediatePropagation();
209
+ }
210
+ } else if (orientation === 'vertical') {
211
+ if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') {
212
+ event.stopImmediatePropagation();
213
+ }
214
+ }
215
+ };
216
+
217
+ ngOnInit(): void {
218
+ // Capture phase + `addEventListener` directly on the host: this listener
219
+ // fires before the composed KjRovingTabindex's `(keydown)` host binding,
220
+ // so we can swallow off-axis arrow keys per the parent KjTabs orientation
221
+ // (the WAI-ARIA APG contract for tab strips).
222
+ this.el.nativeElement.addEventListener('keydown', this.keydownFilter, true);
223
+ }
224
+
225
+ ngOnDestroy(): void {
226
+ this.el.nativeElement.removeEventListener('keydown', this.keydownFilter, true);
227
+ }
228
+ }
77
229
 
78
230
  /**
79
- * Individual tab trigger. Sets `role="tab"` and manages `aria-selected`.
80
- * @example `<button kjTab [kjTabValue]="'profile'">Profile</button>`
81
- * @category Core/Navigation
231
+ * Tab trigger. Hosts `role="tab"` + the ARIA wiring (`aria-selected`,
232
+ * `aria-controls`, `id`) and composes `KjRovingTabindexItem`, `KjFocusRing`
233
+ * and `KjDisabled`. Click activates the tab; Enter/Space activate (always
234
+ * honoured, required in manual mode); Delete fires `kjClose` when
235
+ * `kjClosable` is true.
236
+ *
237
+ * @doc-category Core/Navigation
238
+ * @doc
239
+ * @doc-name tabs
82
240
  */
83
241
  @Directive({
84
242
  selector: '[kjTab]',
85
243
  standalone: true,
244
+ hostDirectives: [KjRovingTabindexItemDirective, KjFocusRing, KjDisabled],
86
245
  host: {
87
- role: 'tab',
88
- '[attr.aria-selected]': 'active().toString()',
89
- '[attr.data-active]': 'active() ? "" : null',
90
- '[attr.tabindex]': 'active() ? "0" : "-1"',
91
- '(click)': 'activate()',
246
+ '[attr.role]': '"tab"',
247
+ '[attr.id]': 'tabs.tabId(kjTabValue())',
248
+ '[attr.aria-controls]': 'tabs.panelId(kjTabValue())',
249
+ '[attr.aria-selected]': 'isActive() ? "true" : "false"',
250
+ '[attr.aria-disabled]': 'kjTabDisabled() ? "true" : null',
251
+ '[attr.data-state]': 'isActive() ? "active" : "inactive"',
252
+ '[attr.data-disabled]': 'kjTabDisabled() ? "" : null',
253
+ '(click)': 'onClick()',
254
+ '(keydown)': 'onKeydown($event)',
255
+ '(focus)': 'onFocus()',
92
256
  },
93
257
  })
94
- export class KjTab {
95
- private readonly ctx = inject(KJ_TABS);
258
+ export class KjTab implements OnInit, OnDestroy {
259
+ /** @internal */
260
+ readonly tabs = inject(KJ_TABS) as KjTabs;
261
+ /** @internal Native host element. */
262
+ readonly el = inject<ElementRef<HTMLElement>>(ElementRef);
263
+
264
+ /** Required string key wiring this tab to its corresponding panel. */
265
+ readonly kjTabValue = input.required<string>();
266
+
267
+ /** When true, the tab cannot be activated but remains in the focus ring. */
268
+ readonly kjTabDisabled = input<boolean>(false);
96
269
 
97
- /** The value this tab represents. */
98
- kjTabValue = input.required<string>();
270
+ /** When true, Delete on the focused tab fires `kjClose`. */
271
+ readonly kjClosable = input<boolean>(false);
99
272
 
100
- /** Whether this tab is currently active. */
101
- readonly active = computed(() => this.ctx.value() === this.kjTabValue());
273
+ /** Fires when the user closes this tab (Delete key). Emits the tab's value. */
274
+ readonly kjClose = output<string>();
102
275
 
103
- /** Activates this tab by updating the shared context. */
104
- activate(): void {
105
- this.ctx.activate(this.kjTabValue());
276
+ /** Whether this tab is the currently active one. */
277
+ readonly isActive = computed(() => this.tabs.isActive(this.kjTabValue()));
278
+
279
+ ngOnInit(): void {
280
+ this.tabs.register(this);
281
+ }
282
+
283
+ ngOnDestroy(): void {
284
+ this.tabs.unregister(this);
285
+ }
286
+
287
+ /** @internal Click handler. */
288
+ onClick(): void {
289
+ if (this.kjTabDisabled()) return;
290
+ this.tabs.select(this.kjTabValue());
291
+ }
292
+
293
+ /** @internal Focus handler — activates immediately in `automatic` mode. */
294
+ onFocus(): void {
295
+ if (this.kjTabDisabled()) return;
296
+ if (this.tabs.activationMode() === 'automatic') {
297
+ this.tabs.select(this.kjTabValue());
298
+ }
299
+ }
300
+
301
+ /** @internal Keydown — Enter/Space activate (always honoured); Delete closes when closable. */
302
+ onKeydown(event: KeyboardEvent): void {
303
+ if (event.key === 'Enter' || event.key === ' ' || event.key === 'Spacebar') {
304
+ if (this.kjTabDisabled()) return;
305
+ event.preventDefault();
306
+ this.tabs.select(this.kjTabValue());
307
+ return;
308
+ }
309
+ if (event.key === 'Delete' && this.kjClosable()) {
310
+ event.preventDefault();
311
+ this.kjClose.emit(this.kjTabValue());
312
+ return;
313
+ }
106
314
  }
107
315
  }
108
316
 
109
317
  /**
110
- * Tab panel content. Hidden when not active.
111
- * @example `<div kjTabPanel [kjPanelFor]="'profile'">Profile content</div>`
112
- * @category Core/Navigation
318
+ * Tab panel. Hosts `role="tabpanel"` + `aria-labelledby` + `id` + `hidden`.
319
+ * Tracks first-activation history via `mounted`: it flips `true` on first
320
+ * activation and stays `true` thereafter, so consumers can gate the
321
+ * projected content with `@if (panel.mounted())` to implement the
322
+ * lazy-then-persistent mount posture from the analysis.
323
+ *
324
+ * @doc-category Core/Navigation
325
+ * @doc
326
+ * @doc-name tabs
113
327
  */
114
328
  @Directive({
115
329
  selector: '[kjTabPanel]',
116
330
  standalone: true,
331
+ exportAs: 'kjTabPanel',
117
332
  host: {
118
- role: 'tabpanel',
119
- '[attr.hidden]': 'hidden() ? "" : null',
333
+ '[attr.role]': '"tabpanel"',
334
+ '[attr.id]': 'tabs.panelId(kjPanelValue())',
335
+ '[attr.aria-labelledby]': 'tabs.tabId(kjPanelValue())',
336
+ '[attr.hidden]': 'isActive() ? null : ""',
337
+ '[attr.data-state]': 'isActive() ? "active" : "inactive"',
120
338
  },
121
339
  })
122
340
  export class KjTabPanel {
123
- private readonly ctx = inject(KJ_TABS);
341
+ /** @internal */
342
+ readonly tabs = inject(KJ_TABS);
343
+
344
+ /** Required string key wiring this panel to its corresponding tab. */
345
+ readonly kjPanelValue = input.required<string>();
124
346
 
125
- /** The tab value this panel corresponds to. */
126
- kjPanelFor = input.required<string>();
347
+ /** Whether this panel is the active one. */
348
+ readonly isActive = computed(() => this.tabs.isActive(this.kjPanelValue()));
127
349
 
128
- /** Whether this panel is currently hidden. */
129
- readonly hidden = computed(() => this.ctx.value() !== this.kjPanelFor());
350
+ private readonly _mounted = signal(false);
351
+
352
+ /**
353
+ * `true` after this panel has been activated at least once. Consumers wrap
354
+ * the panel's projected content in `@if (panel.mounted())` to mount the
355
+ * content lazily on first activation and keep it mounted thereafter.
356
+ */
357
+ readonly mounted = this._mounted.asReadonly();
358
+
359
+ constructor() {
360
+ effect(() => {
361
+ if (this.isActive() && !this._mounted()) this._mounted.set(true);
362
+ });
363
+ }
130
364
  }
@@ -0,0 +1,53 @@
1
+ import { InjectionToken, Provider } from '@angular/core';
2
+
3
+ /** Preset configuration consumed by `KjTag` via `bindPresets`. */
4
+ export interface KjTagConfig {
5
+ variants: string[];
6
+ sizes: string[];
7
+ defaults: { variant: string; size: string };
8
+ }
9
+
10
+ /**
11
+ * Default Tag presets shipped by kouji-ui. Variant list is intentionally
12
+ * kept in lock-step with `KjBadge` (see analysis §"Variant parity with
13
+ * Badge") — a non-interactive Tag and a Badge with the same `kjVariant`
14
+ * must look identical.
15
+ */
16
+ export const KJ_TAG_DEFAULTS: KjTagConfig = {
17
+ variants: [
18
+ 'default',
19
+ 'primary',
20
+ 'secondary',
21
+ 'success',
22
+ 'warning',
23
+ 'danger',
24
+ 'info',
25
+ 'outline',
26
+ 'ghost',
27
+ ],
28
+ sizes: ['xs', 'sm', 'md', 'lg'],
29
+ defaults: { variant: 'default', size: 'md' },
30
+ };
31
+
32
+ /**
33
+ * DI token for the active Tag presets. Default factory yields
34
+ * `KJ_TAG_DEFAULTS`. Override via `provideKjTag(...)` at the application
35
+ * scope or at a component scope.
36
+ */
37
+ export const KJ_TAG_CONFIG = new InjectionToken<KjTagConfig>('kj.tag.config', {
38
+ factory: () => KJ_TAG_DEFAULTS,
39
+ });
40
+
41
+ /**
42
+ * Configures the Tag presets for the enclosing injector. Replaces (does
43
+ * not merge) `variants` and `sizes`; spread `KJ_TAG_DEFAULTS.variants` to
44
+ * extend.
45
+ */
46
+ export function provideKjTag(config: Partial<KjTagConfig>): Provider[] {
47
+ return [
48
+ {
49
+ provide: KJ_TAG_CONFIG,
50
+ useValue: { ...KJ_TAG_DEFAULTS, ...config },
51
+ },
52
+ ];
53
+ }
@@ -0,0 +1,7 @@
1
+ export { KjTag } from './tag';
2
+ export { KjTagRemove } from './tag-remove';
3
+ export { KjTagList } from './tag-list';
4
+ export { KJ_TAG, KJ_TAG_LIST } from './tag.context';
5
+ export type { KjTagContext, KjTagListContext, KjTagListRole } from './tag.context';
6
+ export { KJ_TAG_CONFIG, KJ_TAG_DEFAULTS, provideKjTag } from './config';
7
+ export type { KjTagConfig } from './config';
@@ -0,0 +1,76 @@
1
+ import { Directive, Signal, computed, input } from '@angular/core';
2
+ import { KjRovingTabindex } from '../a11y/roving-tabindex';
3
+ import { KJ_TAG_LIST, KjTagListContext, KjTagListRole } from './tag.context';
4
+
5
+ /**
6
+ * Optional container that coordinates a group of `KjTag` chips. Provides
7
+ * the chip-group keyboard story (roving tabindex via `KjRovingTabindex`)
8
+ * and the ARIA wiring for the `listbox` / `grid` / `group` shapes called
9
+ * out in the analysis.
10
+ *
11
+ * Standalone tags work fine without this container — the container is the
12
+ * opt-in surface that turns a pile of independent chips into a single
13
+ * keyboardable composite.
14
+ *
15
+ * @example
16
+ * ```html
17
+ * <div kjTagList kjTagListRole="listbox" [kjTagListMultiple]="true" aria-label="Filters">
18
+ * <span kjTag kjTagSelectable>One</span>
19
+ * <span kjTag kjTagSelectable>Two</span>
20
+ * </div>
21
+ * ```
22
+ * @doc-category Core/Data display
23
+ * @doc
24
+ * @doc-name tag
25
+ */
26
+ @Directive({
27
+ selector: '[kjTagList]',
28
+ standalone: true,
29
+ providers: [{ provide: KJ_TAG_LIST, useExisting: KjTagList }],
30
+ hostDirectives: [
31
+ { directive: KjRovingTabindex, inputs: ['kjRovingOrientation: kjTagListOrientation'] },
32
+ ],
33
+ host: {
34
+ '[attr.role]': 'kjTagListRole()',
35
+ '[attr.aria-orientation]': 'ariaOrientation()',
36
+ '[attr.aria-multiselectable]': 'ariaMultiSelectable()',
37
+ '[attr.aria-disabled]': 'kjTagListDisabled() ? "true" : null',
38
+ '[attr.tabindex]': 'kjTagListRole() !== "group" ? "-1" : null',
39
+ },
40
+ })
41
+ export class KjTagList implements KjTagListContext {
42
+ /** Container ARIA role — drives chip role selection (option / row / none). */
43
+ readonly kjTagListRole = input<KjTagListRole>('group');
44
+
45
+ /** Roving tabindex axis. Forwarded to `KjRovingTabindex`. */
46
+ readonly kjTagListOrientation = input<'horizontal' | 'vertical' | 'both'>('horizontal');
47
+
48
+ /** Only meaningful in `listbox` mode. Drives `aria-multiselectable`. */
49
+ readonly kjTagListMultiple = input(false);
50
+
51
+ /** Cascading disabled flag. Each chip's effective disabled OR-merges this. */
52
+ readonly kjTagListDisabled = input(false);
53
+
54
+ /** Read-only role view used by `KjTag` to compute its own role. */
55
+ readonly role: Signal<KjTagListRole> = this.kjTagListRole;
56
+
57
+ /** Read-only disabled view used by `KjTag` to merge into its effective disabled. */
58
+ readonly disabled: Signal<boolean> = this.kjTagListDisabled;
59
+
60
+ /** Read-only multi-selectable view (listbox-only meaning). */
61
+ readonly multiple: Signal<boolean> = this.kjTagListMultiple;
62
+
63
+ /** Only emit `aria-orientation` when the container has a real role. */
64
+ protected readonly ariaOrientation = computed<string | null>(() => {
65
+ if (this.kjTagListRole() === 'group') return null;
66
+ return this.kjTagListOrientation() === 'both'
67
+ ? 'horizontal'
68
+ : this.kjTagListOrientation();
69
+ });
70
+
71
+ /** `aria-multiselectable` only set in listbox mode. */
72
+ protected readonly ariaMultiSelectable = computed<string | null>(() => {
73
+ if (this.kjTagListRole() !== 'listbox') return null;
74
+ return this.kjTagListMultiple() ? 'true' : 'false';
75
+ });
76
+ }
@@ -0,0 +1,61 @@
1
+ import { Directive, computed, inject, input } from '@angular/core';
2
+ import { KjButton } from '../button/button';
3
+ import { KJ_TAG } from './tag.context';
4
+
5
+ /**
6
+ * Trailing remove control for a `KjTag`. Composes `KjButton` so the remove
7
+ * action inherits native `<button>` semantics, ARIA-disabled handling,
8
+ * capture-phase click suppression, and the focus ring — there is no second
9
+ * keyboard / a11y story to maintain.
10
+ *
11
+ * The accessible name is derived automatically from the parent tag's
12
+ * projected text content (`"Remove {label}"`) via a `MutationObserver` on
13
+ * `KjTag`. Override with `kjTagRemoveLabel` when the auto-derived name is
14
+ * insufficient (e.g. icon-only tag, custom phrasing).
15
+ *
16
+ * @example
17
+ * ```html
18
+ * <span kjTag>
19
+ * Acme
20
+ * <button kjTagRemove>×</button>
21
+ * </span>
22
+ * ```
23
+ * @doc-category Core/Data display
24
+ * @doc
25
+ * @doc-name tag
26
+ */
27
+ @Directive({
28
+ selector: '[kjTagRemove]',
29
+ standalone: true,
30
+ hostDirectives: [KjButton],
31
+ host: {
32
+ 'type': 'button',
33
+ '[attr.aria-label]': 'effectiveLabel()',
34
+ '(click)': 'onClick($event)',
35
+ },
36
+ })
37
+ export class KjTagRemove {
38
+ private readonly tag = inject(KJ_TAG);
39
+
40
+ /** Override the auto-derived `aria-label` for the remove button. */
41
+ readonly kjTagRemoveLabel = input<string | undefined>(undefined);
42
+
43
+ /** Computed accessible name: explicit override wins, else `"Remove {label}"`. */
44
+ protected readonly effectiveLabel = computed<string>(() => {
45
+ const override = this.kjTagRemoveLabel();
46
+ if (override) return override;
47
+ const label = this.tag.textContent();
48
+ return label ? `Remove ${label}` : 'Remove';
49
+ });
50
+
51
+ /** @internal Click handler — fires the parent tag's `(kjTagRemoved)`. */
52
+ onClick(event: Event): void {
53
+ if (this.tag.disabled()) {
54
+ event.preventDefault();
55
+ event.stopImmediatePropagation();
56
+ return;
57
+ }
58
+ event.stopPropagation();
59
+ this.tag.remove();
60
+ }
61
+ }
@@ -0,0 +1,36 @@
1
+ import { InjectionToken, Signal } from '@angular/core';
2
+
3
+ /**
4
+ * Public context exposed by `KjTag` to its descendants (notably
5
+ * `KjTagRemove`). Mirrors the minimal surface called out in the analysis:
6
+ * three signals + a `remove()` callback.
7
+ */
8
+ export interface KjTagContext {
9
+ /** Live read of the projected text content (label) for the tag. */
10
+ readonly textContent: Signal<string>;
11
+ /** Whether the tag is currently selected (selectable shape only). */
12
+ readonly selected: Signal<boolean>;
13
+ /** Whether the tag is disabled (own + inherited from `KjTagList`). */
14
+ readonly disabled: Signal<boolean>;
15
+ /** Triggers removal: emits the parent's `(kjTagRemoved)` output. */
16
+ remove(): void;
17
+ }
18
+
19
+ /** Role that a `KjTagList` container exposes. Drives chip role selection. */
20
+ export type KjTagListRole = 'listbox' | 'grid' | 'group';
21
+
22
+ /** Public context exposed by `KjTagList` so nested tags can self-configure. */
23
+ export interface KjTagListContext {
24
+ /** Active container role; chips read this to compute their own role. */
25
+ readonly role: Signal<KjTagListRole>;
26
+ /** Whether the entire tag list is disabled (cascades into chips). */
27
+ readonly disabled: Signal<boolean>;
28
+ /** Whether the listbox container allows multi-selection. */
29
+ readonly multiple: Signal<boolean>;
30
+ }
31
+
32
+ /** Injection token providing the parent `KjTag` to projected children. */
33
+ export const KJ_TAG = new InjectionToken<KjTagContext>('KjTag');
34
+
35
+ /** Injection token providing the optional `KjTagList` container. */
36
+ export const KJ_TAG_LIST = new InjectionToken<KjTagListContext>('KjTagList');