@kouji-ui/core 0.0.4 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (534) hide show
  1. package/.turbo/turbo-build.log +7 -4
  2. package/CHANGELOG.md +169 -0
  3. package/ng-package.json +5 -1
  4. package/package.json +18 -6
  5. package/src/a11y/aria-describedby.ts +7 -3
  6. package/src/a11y/aria-labelledby.spec.ts +55 -0
  7. package/src/a11y/aria-labelledby.ts +38 -0
  8. package/src/a11y/focus-trap.ts +4 -2
  9. package/src/a11y/index.ts +1 -0
  10. package/src/a11y/live-region.ts +4 -2
  11. package/src/a11y/roving-tabindex.spec.ts +139 -0
  12. package/src/a11y/roving-tabindex.ts +56 -7
  13. package/src/a11y/visually-hidden.ts +4 -2
  14. package/src/accordion/accordion.context.ts +22 -0
  15. package/src/accordion/accordion.spec.ts +233 -23
  16. package/src/accordion/accordion.ts +311 -39
  17. package/src/accordion/index.ts +8 -2
  18. package/src/alert/alert.context.ts +28 -0
  19. package/src/alert/alert.spec.ts +217 -0
  20. package/src/alert/alert.ts +417 -0
  21. package/src/alert/config.ts +46 -0
  22. package/src/alert/index.ts +19 -0
  23. package/src/avatar/avatar-group.context.ts +45 -0
  24. package/src/avatar/avatar-group.spec.ts +358 -0
  25. package/src/avatar/avatar-group.ts +259 -0
  26. package/src/avatar/avatar.ts +17 -4
  27. package/src/avatar/index.ts +11 -1
  28. package/src/badge/badge.spec.ts +8 -0
  29. package/src/badge/badge.ts +24 -3
  30. package/src/breadcrumb/breadcrumb-current.ts +38 -0
  31. package/src/breadcrumb/breadcrumb-ellipsis.ts +71 -0
  32. package/src/breadcrumb/breadcrumb-item.ts +41 -0
  33. package/src/breadcrumb/breadcrumb-link.ts +44 -0
  34. package/src/breadcrumb/breadcrumb-list.ts +42 -0
  35. package/src/breadcrumb/breadcrumb-separator.ts +45 -0
  36. package/src/breadcrumb/breadcrumb.context.ts +45 -0
  37. package/src/breadcrumb/breadcrumb.spec.ts +354 -0
  38. package/src/breadcrumb/breadcrumb.ts +239 -0
  39. package/src/breadcrumb/config.ts +84 -0
  40. package/src/breadcrumb/index.ts +18 -0
  41. package/src/button/_examples/button.example.ts +71 -0
  42. package/src/button/_examples/button.finance.example.ts +83 -0
  43. package/src/button/_examples/button.retro.example.ts +91 -0
  44. package/src/button/_examples/button.sizes.example.ts +60 -0
  45. package/src/button/_examples/index.ts +6 -0
  46. package/src/button/button.spec.ts +139 -8
  47. package/src/button/button.ts +114 -16
  48. package/src/button/config.spec.ts +33 -0
  49. package/src/button/config.ts +45 -0
  50. package/src/button/index.ts +2 -1
  51. package/src/button-group/button-group.context.ts +28 -0
  52. package/src/button-group/button-group.spec.ts +99 -0
  53. package/src/button-group/button-group.ts +99 -0
  54. package/src/button-group/index.ts +2 -0
  55. package/src/calendar/calendar-day.ts +102 -0
  56. package/src/calendar/calendar-grid.ts +90 -0
  57. package/src/calendar/calendar-header.ts +65 -0
  58. package/src/calendar/calendar.context.ts +53 -0
  59. package/src/calendar/calendar.spec.ts +225 -0
  60. package/src/calendar/calendar.ts +233 -0
  61. package/src/calendar/date-utils.ts +225 -0
  62. package/src/calendar/index.ts +28 -0
  63. package/src/carousel/carousel.context.ts +81 -0
  64. package/src/carousel/carousel.spec.ts +337 -0
  65. package/src/carousel/carousel.ts +919 -0
  66. package/src/carousel/index.ts +22 -0
  67. package/src/cascade-select/cascade-select-option.ts +205 -0
  68. package/src/cascade-select/cascade-select-panel.ts +106 -0
  69. package/src/cascade-select/cascade-select-root.ts +265 -0
  70. package/src/cascade-select/cascade-select-sub-panel.ts +168 -0
  71. package/src/cascade-select/cascade-select-trigger.ts +44 -0
  72. package/src/cascade-select/cascade-select.context.ts +71 -0
  73. package/src/cascade-select/cascade-select.spec.ts +241 -0
  74. package/src/cascade-select/cascade-select.ts +5 -0
  75. package/src/cascade-select/index.ts +11 -0
  76. package/src/chart/chart.ts +6 -2
  77. package/src/chat/chat-avatar.ts +31 -0
  78. package/src/chat/chat-bubble.ts +49 -0
  79. package/src/chat/chat-footer.ts +67 -0
  80. package/src/chat/chat-header.ts +41 -0
  81. package/src/chat/chat-log.ts +43 -0
  82. package/src/chat/chat.context.ts +64 -0
  83. package/src/chat/chat.spec.ts +341 -0
  84. package/src/chat/chat.ts +191 -0
  85. package/src/chat/config.ts +53 -0
  86. package/src/chat/index.ts +21 -0
  87. package/src/checkbox/checkbox.ts +9 -2
  88. package/src/color-picker/color-picker.context.ts +74 -0
  89. package/src/color-picker/color-picker.spec.ts +241 -0
  90. package/src/color-picker/color-picker.ts +698 -0
  91. package/src/color-picker/color-picker.utils.ts +140 -0
  92. package/src/color-picker/index.ts +28 -0
  93. package/src/combobox/combobox-input.ts +143 -0
  94. package/src/combobox/combobox-listbox.ts +49 -0
  95. package/src/combobox/combobox-option.ts +43 -0
  96. package/src/combobox/combobox-root.ts +281 -0
  97. package/src/combobox/combobox.spec.ts +75 -0
  98. package/src/combobox/combobox.ts +4 -0
  99. package/src/combobox/index.ts +4 -0
  100. package/src/command-palette/command-empty.ts +36 -0
  101. package/src/command-palette/command-group.ts +21 -0
  102. package/src/command-palette/command-input.ts +70 -0
  103. package/src/command-palette/command-item.ts +60 -0
  104. package/src/command-palette/command-list.ts +25 -0
  105. package/src/command-palette/command-palette-dialog.ts +74 -0
  106. package/src/command-palette/command-palette-trigger.ts +64 -0
  107. package/src/command-palette/command-palette.filters.ts +53 -0
  108. package/src/command-palette/command-palette.spec.ts +349 -0
  109. package/src/command-palette/command-palette.ts +204 -0
  110. package/src/command-palette/command-separator.ts +18 -0
  111. package/src/command-palette/index.ts +15 -0
  112. package/src/confirm-popup/confirm-popup-action.ts +39 -0
  113. package/src/confirm-popup/confirm-popup-cancel.ts +38 -0
  114. package/src/confirm-popup/confirm-popup-content.ts +113 -0
  115. package/src/confirm-popup/confirm-popup-message.ts +36 -0
  116. package/src/confirm-popup/confirm-popup-trigger.ts +31 -0
  117. package/src/confirm-popup/confirm-popup.context.ts +41 -0
  118. package/src/confirm-popup/confirm-popup.spec.ts +337 -0
  119. package/src/confirm-popup/confirm-popup.ts +109 -0
  120. package/src/confirm-popup/index.ts +12 -0
  121. package/src/date-picker/date-picker-calendar.ts +65 -0
  122. package/src/date-picker/date-picker-trigger.ts +215 -0
  123. package/src/date-picker/date-picker.context.ts +50 -0
  124. package/src/date-picker/date-picker.spec.ts +117 -0
  125. package/src/date-picker/date-picker.ts +127 -0
  126. package/src/date-picker/index.ts +7 -0
  127. package/src/dialog/_examples/dialog.confirm.example.ts +145 -0
  128. package/src/dialog/_examples/dialog.example.ts +106 -0
  129. package/src/dialog/_examples/dialog.finance.example.ts +121 -0
  130. package/src/dialog/_examples/dialog.retro.example.ts +126 -0
  131. package/src/dialog/_examples/index.ts +6 -0
  132. package/src/dialog/dialog.ref.ts +42 -0
  133. package/src/dialog/dialog.service.ts +57 -167
  134. package/src/dialog/dialog.spec.ts +35 -120
  135. package/src/dialog/dialog.ts +17 -192
  136. package/src/dialog/index.ts +3 -9
  137. package/src/divider/divider.spec.ts +223 -0
  138. package/src/divider/divider.ts +137 -0
  139. package/src/divider/index.ts +5 -0
  140. package/src/drawer/drawer.ref.ts +63 -0
  141. package/src/drawer/drawer.service.ts +106 -0
  142. package/src/drawer/drawer.spec.ts +117 -0
  143. package/src/drawer/drawer.ts +122 -0
  144. package/src/drawer/index.ts +10 -0
  145. package/src/dropdown-menu/dropdown-menu-content.ts +258 -0
  146. package/src/dropdown-menu/dropdown-menu-group.ts +31 -0
  147. package/src/dropdown-menu/dropdown-menu-item.ts +51 -0
  148. package/src/dropdown-menu/dropdown-menu-label.ts +41 -0
  149. package/src/dropdown-menu/dropdown-menu-separator.ts +27 -0
  150. package/src/dropdown-menu/dropdown-menu-trigger.ts +140 -0
  151. package/src/dropdown-menu/dropdown-menu.spec.ts +265 -0
  152. package/src/dropdown-menu/dropdown-menu.ts +60 -0
  153. package/src/dropdown-menu/index.ts +15 -0
  154. package/src/example-components.ts +39 -30
  155. package/src/field/field-error.ts +49 -0
  156. package/src/field/field-group.ts +39 -0
  157. package/src/field/field-help.ts +47 -0
  158. package/src/field/field-label.ts +30 -0
  159. package/src/field/field.context.ts +28 -0
  160. package/src/field/field.spec.ts +179 -0
  161. package/src/field/field.ts +121 -0
  162. package/src/field/index.ts +6 -0
  163. package/src/file-upload/file-upload.context.ts +89 -0
  164. package/src/file-upload/file-upload.spec.ts +290 -0
  165. package/src/file-upload/file-upload.ts +639 -0
  166. package/src/file-upload/file-upload.types.ts +59 -0
  167. package/src/file-upload/index.ts +23 -0
  168. package/src/form/form-error-summary.ts +87 -0
  169. package/src/form/form-field.context.ts +3 -0
  170. package/src/form/form-field.spec.ts +25 -0
  171. package/src/form/form-field.ts +52 -0
  172. package/src/form/form.context.ts +52 -2
  173. package/src/form/form.spec.ts +155 -18
  174. package/src/form/form.ts +315 -35
  175. package/src/form/index.ts +11 -2
  176. package/src/icon/E2E.md +14 -0
  177. package/src/icon/icon.css +40 -0
  178. package/src/icon/icon.directive.spec.ts +198 -0
  179. package/src/icon/icon.directive.ts +113 -0
  180. package/src/icon/icon.mode.spec.ts +29 -0
  181. package/src/icon/icon.mode.ts +23 -0
  182. package/src/icon/icon.providers.spec.ts +63 -0
  183. package/src/icon/icon.providers.ts +47 -0
  184. package/src/icon/icon.resolver.spec.ts +96 -0
  185. package/src/icon/icon.resolver.ts +49 -0
  186. package/src/icon/icon.tokens.ts +88 -0
  187. package/src/icon/icon.types.ts +48 -0
  188. package/src/icon/index.ts +21 -0
  189. package/src/input/input.ts +12 -3
  190. package/src/input-group/index.ts +3 -0
  191. package/src/input-group/input-group-addon.ts +92 -0
  192. package/src/input-group/input-group.context.ts +27 -0
  193. package/src/input-group/input-group.spec.ts +186 -0
  194. package/src/input-group/input-group.ts +136 -0
  195. package/src/input-mask/index.ts +11 -0
  196. package/src/input-mask/input-mask.compile.ts +69 -0
  197. package/src/input-mask/input-mask.engine.ts +376 -0
  198. package/src/input-mask/input-mask.presets.ts +24 -0
  199. package/src/input-mask/input-mask.spec.ts +304 -0
  200. package/src/input-mask/input-mask.tokens.ts +49 -0
  201. package/src/input-mask/input-mask.ts +280 -0
  202. package/src/input-otp/index.ts +2 -0
  203. package/src/input-otp/input-otp.context.ts +41 -0
  204. package/src/input-otp/input-otp.spec.ts +308 -0
  205. package/src/input-otp/input-otp.ts +470 -0
  206. package/src/kbd/index.ts +1 -0
  207. package/src/kbd/kbd.spec.ts +127 -0
  208. package/src/kbd/kbd.ts +132 -0
  209. package/src/link/config.ts +44 -0
  210. package/src/link/index.ts +2 -0
  211. package/src/link/link.spec.ts +246 -0
  212. package/src/link/link.ts +226 -0
  213. package/src/list/index.ts +11 -0
  214. package/src/list/list.spec.ts +237 -0
  215. package/src/list/list.ts +277 -0
  216. package/src/menubar/index.ts +7 -0
  217. package/src/menubar/menubar-item.ts +204 -0
  218. package/src/menubar/menubar.context.ts +43 -0
  219. package/src/menubar/menubar.spec.ts +433 -0
  220. package/src/menubar/menubar.ts +309 -0
  221. package/src/number-input/index.ts +4 -0
  222. package/src/number-input/number-input-group.ts +64 -0
  223. package/src/number-input/number-input.context.ts +31 -0
  224. package/src/number-input/number-input.format.ts +148 -0
  225. package/src/number-input/number-input.spec.ts +170 -0
  226. package/src/number-input/number-input.ts +457 -0
  227. package/src/number-input/number-stepper.ts +193 -0
  228. package/src/overlay-badge/index.ts +7 -0
  229. package/src/overlay-badge/overlay-badge-content.ts +73 -0
  230. package/src/overlay-badge/overlay-badge.context.ts +37 -0
  231. package/src/overlay-badge/overlay-badge.spec.ts +220 -0
  232. package/src/overlay-badge/overlay-badge.ts +128 -0
  233. package/src/pagination/config.ts +82 -0
  234. package/src/pagination/index.ts +19 -0
  235. package/src/pagination/page-tokens.ts +78 -0
  236. package/src/pagination/pagination-ellipsis.ts +69 -0
  237. package/src/pagination/pagination-first.ts +84 -0
  238. package/src/pagination/pagination-info.ts +70 -0
  239. package/src/pagination/pagination-item.ts +81 -0
  240. package/src/pagination/pagination-last.ts +85 -0
  241. package/src/pagination/pagination-next.ts +86 -0
  242. package/src/pagination/pagination-previous.ts +90 -0
  243. package/src/pagination/pagination.context.ts +52 -0
  244. package/src/pagination/pagination.spec.ts +388 -0
  245. package/src/pagination/pagination.ts +288 -0
  246. package/src/password-input/index.ts +15 -0
  247. package/src/password-input/password-input.context.ts +65 -0
  248. package/src/password-input/password-input.scorer.ts +34 -0
  249. package/src/password-input/password-input.spec.ts +274 -0
  250. package/src/password-input/password-input.ts +488 -0
  251. package/src/popover/_examples/index.ts +5 -0
  252. package/src/popover/_examples/popover.example.ts +92 -0
  253. package/src/popover/_examples/popover.finance.example.ts +106 -0
  254. package/src/popover/_examples/popover.retro.example.ts +103 -0
  255. package/src/popover/index.ts +5 -12
  256. package/src/popover/popover-arrow.ts +30 -0
  257. package/src/popover/popover-close.ts +36 -0
  258. package/src/popover/popover-content.ts +41 -0
  259. package/src/popover/popover-title.ts +39 -0
  260. package/src/popover/popover-trigger.ts +40 -0
  261. package/src/popover/popover.spec.ts +51 -44
  262. package/src/popover/popover.ts +5 -124
  263. package/src/presets/bind-presets.spec.ts +50 -0
  264. package/src/presets/bind-presets.ts +43 -0
  265. package/src/presets/index.ts +3 -0
  266. package/src/presets/size.spec.ts +67 -0
  267. package/src/presets/size.ts +59 -0
  268. package/src/presets/variant.spec.ts +72 -0
  269. package/src/presets/variant.ts +62 -0
  270. package/src/primitives/directionality/directionality.spec.ts +112 -0
  271. package/src/primitives/directionality/directionality.ts +89 -0
  272. package/src/primitives/directionality/index.ts +1 -0
  273. package/src/primitives/forms/form-control.spec.ts +11 -3
  274. package/src/primitives/forms/form-control.ts +6 -2
  275. package/src/primitives/index.ts +3 -0
  276. package/src/primitives/interaction/disabled.ts +12 -5
  277. package/src/primitives/interaction/focus-ring.ts +4 -2
  278. package/src/primitives/list/filterable-list.spec.ts +114 -0
  279. package/src/primitives/list/filterable-list.ts +126 -0
  280. package/src/primitives/list/group.spec.ts +99 -0
  281. package/src/primitives/list/group.ts +139 -0
  282. package/src/primitives/list/index.ts +8 -0
  283. package/src/primitives/list/inject-helpers.ts +8 -0
  284. package/src/primitives/list/item.spec.ts +173 -0
  285. package/src/primitives/list/item.ts +222 -0
  286. package/src/primitives/list/navigator.spec.ts +286 -0
  287. package/src/primitives/list/navigator.ts +238 -0
  288. package/src/primitives/list/selection.spec.ts +214 -0
  289. package/src/primitives/list/selection.ts +339 -0
  290. package/src/primitives/list/tokens.ts +106 -0
  291. package/src/primitives/list/type-ahead.spec.ts +58 -0
  292. package/src/primitives/list/type-ahead.ts +46 -0
  293. package/src/primitives/overlay/backdrop.spec.ts +54 -0
  294. package/src/primitives/overlay/backdrop.ts +31 -0
  295. package/src/primitives/overlay/builder.spec.ts +38 -0
  296. package/src/primitives/overlay/builder.ts +151 -0
  297. package/src/primitives/overlay/container.ts +42 -0
  298. package/src/primitives/overlay/context.ts +14 -0
  299. package/src/primitives/overlay/controller.spec.ts +86 -0
  300. package/src/primitives/overlay/controller.ts +190 -0
  301. package/src/primitives/overlay/id.spec.ts +24 -0
  302. package/src/primitives/overlay/id.ts +17 -0
  303. package/src/primitives/overlay/index.ts +18 -1
  304. package/src/primitives/overlay/overlay.css +42 -0
  305. package/src/primitives/overlay/panel.spec.ts +52 -0
  306. package/src/primitives/overlay/panel.ts +102 -0
  307. package/src/primitives/overlay/stack.spec.ts +55 -0
  308. package/src/primitives/overlay/stack.ts +113 -0
  309. package/src/primitives/overlay/strategies/backdrop/blurred.spec.ts +11 -0
  310. package/src/primitives/overlay/strategies/backdrop/blurred.ts +5 -0
  311. package/src/primitives/overlay/strategies/backdrop/none.spec.ts +10 -0
  312. package/src/primitives/overlay/strategies/backdrop/none.ts +9 -0
  313. package/src/primitives/overlay/strategies/backdrop/solid.spec.ts +18 -0
  314. package/src/primitives/overlay/strategies/backdrop/solid.ts +23 -0
  315. package/src/primitives/overlay/strategies/focus-trap/inert-based.spec.ts +28 -0
  316. package/src/primitives/overlay/strategies/focus-trap/inert-based.ts +38 -0
  317. package/src/primitives/overlay/strategies/focus-trap/no-trap.spec.ts +9 -0
  318. package/src/primitives/overlay/strategies/focus-trap/no-trap.ts +8 -0
  319. package/src/primitives/overlay/strategies/focus-trap/tab-cycle.spec.ts +49 -0
  320. package/src/primitives/overlay/strategies/focus-trap/tab-cycle.ts +85 -0
  321. package/src/primitives/overlay/strategies/index.ts +34 -0
  322. package/src/primitives/overlay/strategies/live-announcer/_announce.ts +35 -0
  323. package/src/primitives/overlay/strategies/live-announcer/assertive.spec.ts +21 -0
  324. package/src/primitives/overlay/strategies/live-announcer/assertive.ts +11 -0
  325. package/src/primitives/overlay/strategies/live-announcer/polite.spec.ts +21 -0
  326. package/src/primitives/overlay/strategies/live-announcer/polite.ts +11 -0
  327. package/src/primitives/overlay/strategies/live-announcer/silent.spec.ts +12 -0
  328. package/src/primitives/overlay/strategies/live-announcer/silent.ts +5 -0
  329. package/src/primitives/overlay/strategies/mount/body-portal.spec.ts +40 -0
  330. package/src/primitives/overlay/strategies/mount/body-portal.ts +91 -0
  331. package/src/primitives/overlay/strategies/mount/in-container.spec.ts +43 -0
  332. package/src/primitives/overlay/strategies/mount/in-container.ts +39 -0
  333. package/src/primitives/overlay/strategies/mount/in-place.spec.ts +40 -0
  334. package/src/primitives/overlay/strategies/mount/in-place.ts +20 -0
  335. package/src/primitives/overlay/strategies/position/anchored-to.spec.ts +56 -0
  336. package/src/primitives/overlay/strategies/position/anchored-to.ts +248 -0
  337. package/src/primitives/overlay/strategies/position/corner.spec.ts +36 -0
  338. package/src/primitives/overlay/strategies/position/corner.ts +33 -0
  339. package/src/primitives/overlay/strategies/position/edge-sheet.spec.ts +32 -0
  340. package/src/primitives/overlay/strategies/position/edge-sheet.ts +36 -0
  341. package/src/primitives/overlay/strategies/position/in-place-sibling.spec.ts +25 -0
  342. package/src/primitives/overlay/strategies/position/in-place-sibling.ts +13 -0
  343. package/src/primitives/overlay/strategies/position/point-at.spec.ts +40 -0
  344. package/src/primitives/overlay/strategies/position/point-at.ts +34 -0
  345. package/src/primitives/overlay/strategies/position/viewport-centered.spec.ts +31 -0
  346. package/src/primitives/overlay/strategies/position/viewport-centered.ts +31 -0
  347. package/src/primitives/overlay/strategies/scroll-lock/css-clip.spec.ts +15 -0
  348. package/src/primitives/overlay/strategies/scroll-lock/css-clip.ts +39 -0
  349. package/src/primitives/overlay/strategies/scroll-lock/html-overflow.spec.ts +21 -0
  350. package/src/primitives/overlay/strategies/scroll-lock/html-overflow.ts +44 -0
  351. package/src/primitives/overlay/strategies/scroll-lock/none.spec.ts +9 -0
  352. package/src/primitives/overlay/strategies/scroll-lock/none.ts +5 -0
  353. package/src/primitives/overlay/strategies/trigger-event/on-click.spec.ts +32 -0
  354. package/src/primitives/overlay/strategies/trigger-event/on-click.ts +28 -0
  355. package/src/primitives/overlay/strategies/trigger-event/on-context-menu.spec.ts +45 -0
  356. package/src/primitives/overlay/strategies/trigger-event/on-context-menu.ts +70 -0
  357. package/src/primitives/overlay/strategies/trigger-event/on-focus-or-input.spec.ts +35 -0
  358. package/src/primitives/overlay/strategies/trigger-event/on-focus-or-input.ts +34 -0
  359. package/src/primitives/overlay/strategies/trigger-event/on-focus.spec.ts +64 -0
  360. package/src/primitives/overlay/strategies/trigger-event/on-focus.ts +37 -0
  361. package/src/primitives/overlay/strategies/trigger-event/on-hotkey.spec.ts +37 -0
  362. package/src/primitives/overlay/strategies/trigger-event/on-hotkey.ts +59 -0
  363. package/src/primitives/overlay/strategies/trigger-event/on-hover.spec.ts +34 -0
  364. package/src/primitives/overlay/strategies/trigger-event/on-hover.ts +86 -0
  365. package/src/primitives/overlay/strategies/trigger-event/programmatic.spec.ts +16 -0
  366. package/src/primitives/overlay/strategies/trigger-event/programmatic.ts +12 -0
  367. package/src/primitives/overlay/tokens.ts +54 -0
  368. package/src/primitives/overlay/trigger.spec.ts +103 -0
  369. package/src/primitives/overlay/trigger.ts +75 -0
  370. package/src/primitives/overlay/types.ts +24 -0
  371. package/src/primitives/overlay/wrapper.ts +50 -0
  372. package/src/primitives/signals/deep-signal.ts +109 -0
  373. package/src/primitives/signals/index.ts +1 -0
  374. package/src/progress-bar/config.ts +48 -0
  375. package/src/progress-bar/index.ts +12 -0
  376. package/src/progress-bar/progress-bar-fill.ts +37 -0
  377. package/src/progress-bar/progress-bar.context.ts +34 -0
  378. package/src/progress-bar/progress-bar.spec.ts +423 -0
  379. package/src/progress-bar/progress-bar.ts +217 -0
  380. package/src/public-api.ts +58 -9
  381. package/src/radio/radio.ts +14 -3
  382. package/src/select/index.ts +4 -2
  383. package/src/select/select-content.ts +69 -0
  384. package/src/select/select-option.ts +35 -0
  385. package/src/select/select-root.ts +129 -0
  386. package/src/select/select-trigger.ts +93 -0
  387. package/src/select/select.spec.ts +133 -140
  388. package/src/select/select.ts +4 -250
  389. package/src/skeleton/index.ts +5 -0
  390. package/src/skeleton/skeleton.spec.ts +115 -0
  391. package/src/skeleton/skeleton.ts +56 -0
  392. package/src/slider/index.ts +10 -0
  393. package/src/slider/slider-range.ts +49 -0
  394. package/src/slider/slider-thumb.ts +335 -0
  395. package/src/slider/slider-track.ts +70 -0
  396. package/src/slider/slider.context.ts +79 -0
  397. package/src/slider/slider.geometry.ts +73 -0
  398. package/src/slider/slider.spec.ts +216 -0
  399. package/src/slider/slider.ts +443 -0
  400. package/src/speed-dial/index.ts +10 -0
  401. package/src/speed-dial/speed-dial-action.ts +40 -0
  402. package/src/speed-dial/speed-dial-actions.ts +31 -0
  403. package/src/speed-dial/speed-dial-trigger.ts +55 -0
  404. package/src/speed-dial/speed-dial.context.ts +36 -0
  405. package/src/speed-dial/speed-dial.spec.ts +143 -0
  406. package/src/speed-dial/speed-dial.ts +127 -0
  407. package/src/spinner/config.ts +48 -0
  408. package/src/spinner/index.ts +8 -0
  409. package/src/spinner/spinner.spec.ts +262 -0
  410. package/src/spinner/spinner.ts +129 -0
  411. package/src/stepper/index.ts +15 -0
  412. package/src/stepper/stepper.context.ts +63 -0
  413. package/src/stepper/stepper.spec.ts +403 -0
  414. package/src/stepper/stepper.ts +544 -0
  415. package/src/styles.css +17 -0
  416. package/src/table/column-helpers.spec.ts +74 -0
  417. package/src/table/column-helpers.ts +68 -0
  418. package/src/table/filter-fns.ts +230 -0
  419. package/src/table/filter-models.spec.ts +240 -0
  420. package/src/table/filter-models.ts +124 -0
  421. package/src/table/filter-params.ts +90 -0
  422. package/src/table/grid-api-impl.ts +321 -0
  423. package/src/table/grid-api.spec.ts +178 -0
  424. package/src/table/grid-api.ts +285 -0
  425. package/src/table/index.ts +73 -1
  426. package/src/table/table-cell.spec.ts +43 -0
  427. package/src/table/table-cell.ts +38 -0
  428. package/src/table/table-filter-outlet.ts +120 -0
  429. package/src/table/table-header.spec.ts +54 -0
  430. package/src/table/table-header.ts +63 -0
  431. package/src/table/table-keyboard.spec.ts +68 -0
  432. package/src/table/table-keyboard.ts +107 -0
  433. package/src/table/table-resource.spec.ts +37 -0
  434. package/src/table/table-resource.ts +36 -0
  435. package/src/table/table-row.spec.ts +54 -0
  436. package/src/table/table-row.ts +31 -0
  437. package/src/table/table-storage.spec.ts +67 -0
  438. package/src/table/table-storage.ts +76 -0
  439. package/src/table/table.spec.ts +101 -42
  440. package/src/table/table.ts +228 -73
  441. package/src/table/table.types.ts +83 -0
  442. package/src/tabs/index.ts +7 -2
  443. package/src/tabs/tabs.context.ts +27 -4
  444. package/src/tabs/tabs.spec.ts +336 -24
  445. package/src/tabs/tabs.ts +313 -79
  446. package/src/tag/config.ts +53 -0
  447. package/src/tag/index.ts +7 -0
  448. package/src/tag/tag-list.ts +76 -0
  449. package/src/tag/tag-remove.ts +61 -0
  450. package/src/tag/tag.context.ts +36 -0
  451. package/src/tag/tag.spec.ts +240 -0
  452. package/src/tag/tag.ts +203 -0
  453. package/src/textarea/config.ts +42 -0
  454. package/src/textarea/index.ts +8 -0
  455. package/src/textarea/textarea.spec.ts +176 -0
  456. package/src/textarea/textarea.ts +235 -0
  457. package/src/time-picker/index.ts +15 -0
  458. package/src/time-picker/time-picker-meridiem.ts +123 -0
  459. package/src/time-picker/time-picker-segment.ts +318 -0
  460. package/src/time-picker/time-picker.context.ts +38 -0
  461. package/src/time-picker/time-picker.format.ts +181 -0
  462. package/src/time-picker/time-picker.spec.ts +189 -0
  463. package/src/time-picker/time-picker.ts +274 -0
  464. package/src/toast/_examples/index.ts +5 -0
  465. package/src/toast/_examples/toast.example.ts +282 -0
  466. package/src/toast/_examples/toast.finance.example.ts +180 -0
  467. package/src/toast/_examples/toast.retro.example.ts +161 -0
  468. package/src/toast/index.ts +8 -1
  469. package/src/toast/toast.ref.ts +26 -0
  470. package/src/toast/toast.service.ts +227 -41
  471. package/src/toast/toast.spec.ts +166 -14
  472. package/src/toast/toast.strategy.ts +10 -0
  473. package/src/toast/toast.ts +177 -21
  474. package/src/toggle/toggle.ts +10 -2
  475. package/src/tooltip/_examples/index.ts +6 -0
  476. package/src/tooltip/_examples/tooltip.example.ts +82 -0
  477. package/src/tooltip/_examples/tooltip.finance.example.ts +97 -0
  478. package/src/tooltip/_examples/tooltip.placements.example.ts +83 -0
  479. package/src/tooltip/_examples/tooltip.retro.example.ts +107 -0
  480. package/src/tooltip/index.ts +4 -5
  481. package/src/tooltip/tooltip-arrow.ts +31 -0
  482. package/src/tooltip/tooltip-content.ts +38 -0
  483. package/src/tooltip/tooltip-group.ts +32 -0
  484. package/src/tooltip/tooltip-trigger.ts +47 -0
  485. package/src/tooltip/tooltip.spec.ts +38 -102
  486. package/src/tooltip/tooltip.ts +5 -129
  487. package/src/tree-select/index.ts +12 -0
  488. package/src/tree-select/tree-select-content.ts +199 -0
  489. package/src/tree-select/tree-select-node.ts +132 -0
  490. package/src/tree-select/tree-select-root.ts +272 -0
  491. package/src/tree-select/tree-select-trigger.ts +90 -0
  492. package/src/tree-select/tree-select.context.ts +63 -0
  493. package/src/tree-select/tree-select.spec.ts +433 -0
  494. package/src/tree-select/tree-select.ts +2 -0
  495. package/src/typography/blockquote.ts +57 -0
  496. package/src/typography/code.ts +53 -0
  497. package/src/typography/index.ts +22 -0
  498. package/src/typography/lead.ts +49 -0
  499. package/src/typography/muted.ts +29 -0
  500. package/src/typography/prose.css +312 -0
  501. package/src/typography/truncate.ts +99 -0
  502. package/src/typography/typography.spec.ts +203 -0
  503. package/tsconfig.lib.json +9 -1
  504. package/tsconfig.lib.prod.json +7 -1
  505. package/tsconfig.spec.json +9 -1
  506. package/vite.config.ts +8 -0
  507. package/src/button/button.example.ts +0 -30
  508. package/src/button/button.finance.example.ts +0 -37
  509. package/src/button/button.retro.example.ts +0 -47
  510. package/src/button/button.sizes.example.ts +0 -25
  511. package/src/dialog/dialog.confirm.example.ts +0 -57
  512. package/src/dialog/dialog.context.ts +0 -11
  513. package/src/dialog/dialog.example.ts +0 -40
  514. package/src/dialog/dialog.finance.example.ts +0 -47
  515. package/src/dialog/dialog.retro.example.ts +0 -46
  516. package/src/menu/index.ts +0 -2
  517. package/src/menu/menu.context.ts +0 -10
  518. package/src/menu/menu.example.ts +0 -39
  519. package/src/menu/menu.spec.ts +0 -168
  520. package/src/menu/menu.ts +0 -176
  521. package/src/popover/popover.context.ts +0 -18
  522. package/src/popover/popover.example.ts +0 -50
  523. package/src/popover/popover.finance.example.ts +0 -52
  524. package/src/popover/popover.retro.example.ts +0 -53
  525. package/src/primitives/overlay/overlay.spec.ts +0 -90
  526. package/src/primitives/overlay/overlay.ts +0 -106
  527. package/src/select/select.context.ts +0 -3
  528. package/src/toast/toast.example.ts +0 -134
  529. package/src/toast/toast.finance.example.ts +0 -68
  530. package/src/toast/toast.retro.example.ts +0 -69
  531. package/src/tooltip/tooltip.example.ts +0 -46
  532. package/src/tooltip/tooltip.finance.example.ts +0 -54
  533. package/src/tooltip/tooltip.placements.example.ts +0 -56
  534. package/src/tooltip/tooltip.retro.example.ts +0 -65
@@ -1,145 +1,138 @@
1
- import { render, fireEvent } from '@testing-library/angular';
2
- import { axe, toHaveNoViolations } from 'jest-axe';
1
+ import { Component } from '@angular/core';
2
+ import { render } from '@testing-library/angular';
3
+ import { describe, it, expect } from 'vitest';
3
4
  import { KjSelect, KjSelectTrigger, KjSelectContent, KjOption } from './select';
4
5
 
5
- expect.extend(toHaveNoViolations);
6
-
7
- const imports = [KjSelect, KjSelectTrigger, KjSelectContent, KjOption];
8
- const template = `
9
- <div kjSelect [(kjSelectValue)]="val">
10
- <button kjSelectTrigger aria-haspopup="listbox">Select fruit</button>
11
- <div kjSelectContent>
12
- <div kjOption [kjOptionValue]="'apple'">Apple</div>
13
- <div kjOption [kjOptionValue]="'banana'">Banana</div>
14
- <div kjOption [kjOptionValue]="'cherry'">Cherry</div>
15
- </div>
16
- </div>`;
17
-
18
6
  describe('KjSelect', () => {
19
- it('listbox hidden by default', async () => {
20
- const { container } = await render(template, { imports, componentProperties: { val: '' } });
21
- expect(container.querySelector('[kjSelectContent]')).toHaveAttribute('hidden', '');
22
- });
23
-
24
- it('trigger opens listbox', async () => {
25
- const { container } = await render(template, { imports, componentProperties: { val: '' } });
26
- fireEvent.click(container.querySelector('[kjSelectTrigger]')!);
27
- expect(container.querySelector('[kjSelectContent]')).not.toHaveAttribute('hidden');
28
- });
29
-
30
- it('trigger sets aria-expanded to false by default', async () => {
31
- const { container } = await render(template, { imports, componentProperties: { val: '' } });
32
- expect(container.querySelector('[kjSelectTrigger]')).toHaveAttribute('aria-expanded', 'false');
33
- });
34
-
35
- it('trigger sets aria-expanded to true when open', async () => {
36
- const { container } = await render(template, { imports, componentProperties: { val: '' } });
37
- fireEvent.click(container.querySelector('[kjSelectTrigger]')!);
38
- expect(container.querySelector('[kjSelectTrigger]')).toHaveAttribute('aria-expanded', 'true');
39
- });
40
-
41
- it('clicking option selects and closes', async () => {
42
- const { container } = await render(template, { imports, componentProperties: { val: '' } });
43
- fireEvent.click(container.querySelector('[kjSelectTrigger]')!);
44
- fireEvent.click(container.querySelector('[kjOption]')!);
45
- expect(container.querySelector('[kjSelectContent]')).toHaveAttribute('hidden', '');
46
- });
47
-
48
- it('selected option has aria-selected="true"', async () => {
49
- const { container } = await render(template, { imports, componentProperties: { val: 'apple' } });
50
- const options = container.querySelectorAll('[kjOption]');
51
- expect(options[0]).toHaveAttribute('aria-selected', 'true');
52
- expect(options[1]).toHaveAttribute('aria-selected', 'false');
53
- });
54
-
55
- it('listbox has role="listbox"', async () => {
56
- const { container } = await render(template, { imports, componentProperties: { val: '' } });
57
- expect(container.querySelector('[kjSelectContent]')).toHaveAttribute('role', 'listbox');
58
- });
59
-
60
- it('options have role="option"', async () => {
61
- const { container } = await render(template, { imports, componentProperties: { val: '' } });
62
- const options = container.querySelectorAll('[kjOption]');
63
- options.forEach(opt => expect(opt).toHaveAttribute('role', 'option'));
64
- });
65
-
66
- it('ArrowDown moves focus to next option', async () => {
67
- const { container } = await render(template, { imports, componentProperties: { val: '' } });
68
- fireEvent.click(container.querySelector('[kjSelectTrigger]')!);
69
- const content = container.querySelector('[kjSelectContent]')!;
70
- fireEvent.keyDown(content, { key: 'ArrowDown' });
71
- const options = container.querySelectorAll('[kjOption]');
72
- expect(document.activeElement).toBe(options[0]);
73
- });
74
-
75
- it('ArrowDown then ArrowDown moves to second option', async () => {
76
- const { container } = await render(template, { imports, componentProperties: { val: '' } });
77
- fireEvent.click(container.querySelector('[kjSelectTrigger]')!);
78
- const content = container.querySelector('[kjSelectContent]')!;
79
- fireEvent.keyDown(content, { key: 'ArrowDown' });
80
- fireEvent.keyDown(content, { key: 'ArrowDown' });
81
- const options = container.querySelectorAll('[kjOption]');
82
- expect(document.activeElement).toBe(options[1]);
83
- });
84
-
85
- it('Home key moves focus to first option', async () => {
86
- const { container } = await render(template, { imports, componentProperties: { val: '' } });
87
- fireEvent.click(container.querySelector('[kjSelectTrigger]')!);
88
- const content = container.querySelector('[kjSelectContent]')!;
89
- // Move down first
90
- fireEvent.keyDown(content, { key: 'ArrowDown' });
91
- fireEvent.keyDown(content, { key: 'ArrowDown' });
92
- // Then Home
93
- fireEvent.keyDown(content, { key: 'Home' });
94
- const options = container.querySelectorAll('[kjOption]');
95
- expect(document.activeElement).toBe(options[0]);
96
- });
97
-
98
- it('End key moves focus to last option', async () => {
99
- const { container } = await render(template, { imports, componentProperties: { val: '' } });
100
- fireEvent.click(container.querySelector('[kjSelectTrigger]')!);
101
- const content = container.querySelector('[kjSelectContent]')!;
102
- fireEvent.keyDown(content, { key: 'End' });
103
- const options = container.querySelectorAll('[kjOption]');
104
- expect(document.activeElement).toBe(options[2]);
105
- });
106
-
107
- it('Enter selects focused option', async () => {
108
- const { container } = await render(template, { imports, componentProperties: { val: '' } });
109
- fireEvent.click(container.querySelector('[kjSelectTrigger]')!);
110
- const content = container.querySelector('[kjSelectContent]')!;
111
- fireEvent.keyDown(content, { key: 'ArrowDown' });
112
- fireEvent.keyDown(content, { key: 'Enter' });
113
- expect(container.querySelector('[kjSelectContent]')).toHaveAttribute('hidden', '');
114
- });
115
-
116
- it('Space selects focused option', async () => {
117
- const { container } = await render(template, { imports, componentProperties: { val: '' } });
118
- fireEvent.click(container.querySelector('[kjSelectTrigger]')!);
119
- const content = container.querySelector('[kjSelectContent]')!;
120
- fireEvent.keyDown(content, { key: 'ArrowDown' });
121
- fireEvent.keyDown(content, { key: ' ' });
122
- expect(container.querySelector('[kjSelectContent]')).toHaveAttribute('hidden', '');
123
- });
124
-
125
- it('type-ahead jumps to matching option', async () => {
126
- const { container } = await render(template, { imports, componentProperties: { val: '' } });
127
- fireEvent.click(container.querySelector('[kjSelectTrigger]')!);
128
- const content = container.querySelector('[kjSelectContent]')!;
129
- fireEvent.keyDown(content, { key: 'b' });
130
- const options = container.querySelectorAll('[kjOption]');
131
- expect(document.activeElement).toBe(options[1]);
132
- });
133
-
134
- it('Escape closes the listbox', async () => {
135
- const { container } = await render(template, { imports, componentProperties: { val: '' } });
136
- fireEvent.click(container.querySelector('[kjSelectTrigger]')!);
137
- fireEvent.keyDown(document, { key: 'Escape' });
138
- expect(container.querySelector('[kjSelectContent]')).toHaveAttribute('hidden', '');
139
- });
140
-
141
- it('passes axe audit', async () => {
142
- const { container } = await render(template, { imports, componentProperties: { val: '' } });
143
- expect(await axe(container)).toHaveNoViolations();
7
+ it('panel exposes role=listbox', async () => {
8
+ @Component({
9
+ selector: 'kj-sel-host',
10
+ standalone: true,
11
+ imports: [KjSelect, KjSelectTrigger, KjSelectContent, KjOption],
12
+ template: `
13
+ <div kjSelect>
14
+ <button kjSelectTrigger #t="kjSelectTrigger">Open</button>
15
+ <kj-select-content [kjFor]="t">
16
+ <div kjOption [kjOptionValue]="'a'">A</div>
17
+ </kj-select-content>
18
+ </div>
19
+ `,
20
+ })
21
+ class Host {}
22
+ const { container } = await render(Host);
23
+ const panel = container.querySelector('kj-select-content') as HTMLElement;
24
+ expect(panel.getAttribute('role')).toBe('listbox');
25
+ });
26
+
27
+ it('trigger advertises aria-haspopup=listbox', async () => {
28
+ @Component({
29
+ selector: 'kj-sel-host',
30
+ standalone: true,
31
+ imports: [KjSelect, KjSelectTrigger, KjSelectContent, KjOption],
32
+ template: `
33
+ <div kjSelect>
34
+ <button kjSelectTrigger #t="kjSelectTrigger">Open</button>
35
+ <kj-select-content [kjFor]="t">
36
+ <div kjOption [kjOptionValue]="'a'">A</div>
37
+ </kj-select-content>
38
+ </div>
39
+ `,
40
+ })
41
+ class Host {}
42
+ const { container } = await render(Host);
43
+ const btn = container.querySelector('button')!;
44
+ expect(btn.getAttribute('aria-haspopup')).toBe('listbox');
45
+ expect(btn.getAttribute('aria-expanded')).toBe('false');
46
+ });
47
+
48
+ it('panel reflects aria-multiselectable when kjMultiple is set on trigger', async () => {
49
+ @Component({
50
+ selector: 'kj-sel-host',
51
+ standalone: true,
52
+ imports: [KjSelect, KjSelectTrigger, KjSelectContent, KjOption],
53
+ template: `
54
+ <div kjSelect>
55
+ <button kjSelectTrigger kjMultiple #t="kjSelectTrigger">Open</button>
56
+ <kj-select-content [kjFor]="t">
57
+ <div kjOption [kjOptionValue]="'a'">A</div>
58
+ <div kjOption [kjOptionValue]="'b'">B</div>
59
+ </kj-select-content>
60
+ </div>
61
+ `,
62
+ })
63
+ class Host {}
64
+ const { container } = await render(Host);
65
+ const panel = container.querySelector('kj-select-content') as HTMLElement;
66
+ expect(panel.getAttribute('aria-multiselectable')).toBe('true');
67
+ });
68
+
69
+ it('panel omits aria-multiselectable in single mode', async () => {
70
+ @Component({
71
+ selector: 'kj-sel-host',
72
+ standalone: true,
73
+ imports: [KjSelect, KjSelectTrigger, KjSelectContent, KjOption],
74
+ template: `
75
+ <div kjSelect>
76
+ <button kjSelectTrigger #t="kjSelectTrigger">Open</button>
77
+ <kj-select-content [kjFor]="t">
78
+ <div kjOption [kjOptionValue]="'a'">A</div>
79
+ </kj-select-content>
80
+ </div>
81
+ `,
82
+ })
83
+ class Host {}
84
+ const { container } = await render(Host);
85
+ const panel = container.querySelector('kj-select-content') as HTMLElement;
86
+ expect(panel.hasAttribute('aria-multiselectable')).toBe(false);
87
+ });
88
+
89
+ it('options carry role=option', async () => {
90
+ @Component({
91
+ selector: 'kj-sel-host',
92
+ standalone: true,
93
+ imports: [KjSelect, KjSelectTrigger, KjSelectContent, KjOption],
94
+ template: `
95
+ <div kjSelect>
96
+ <button kjSelectTrigger #t="kjSelectTrigger">Open</button>
97
+ <kj-select-content [kjFor]="t">
98
+ <div kjOption [kjOptionValue]="'a'">A</div>
99
+ <div kjOption [kjOptionValue]="'b'">B</div>
100
+ </kj-select-content>
101
+ </div>
102
+ `,
103
+ })
104
+ class Host {}
105
+ const { container } = await render(Host);
106
+ const opts = container.querySelectorAll('[kjOption]');
107
+ expect(opts.length).toBe(2);
108
+ opts.forEach(o => expect(o.getAttribute('role')).toBe('option'));
109
+ });
110
+
111
+ it('ArrowDown sets aria-activedescendant on the listbox panel', async () => {
112
+ @Component({
113
+ standalone: true,
114
+ imports: [KjSelect, KjSelectTrigger, KjSelectContent, KjOption],
115
+ template: `
116
+ <div kjSelect>
117
+ <button kjSelectTrigger #t="kjSelectTrigger">Open</button>
118
+ <kj-select-content [kjFor]="t">
119
+ <div kjOption [kjOptionValue]="'a'">A</div>
120
+ <div kjOption [kjOptionValue]="'b'">B</div>
121
+ </kj-select-content>
122
+ </div>
123
+ `,
124
+ })
125
+ class Host {}
126
+ const { container, fixture } = await render(Host);
127
+ // open the overlay
128
+ const trigger = container.querySelector('button') as HTMLElement;
129
+ trigger.click();
130
+ fixture.detectChanges();
131
+ // body-portal puts panel into document.body; fall back to container if null
132
+ let panel = document.querySelector('kj-select-content') as HTMLElement | null;
133
+ if (!panel) panel = container.querySelector('kj-select-content') as HTMLElement;
134
+ panel.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }));
135
+ fixture.detectChanges();
136
+ expect(panel.getAttribute('aria-activedescendant')).toMatch(/^kj-list-item-\d+$/);
144
137
  });
145
138
  });
@@ -1,250 +1,4 @@
1
- import {
2
- afterNextRender,
3
- computed,
4
- Directive,
5
- effect,
6
- ElementRef,
7
- inject,
8
- input,
9
- model,
10
- signal,
11
- } from '@angular/core';
12
- import { KjDisabled, KjFocusRing } from '../primitives';
13
- import { KJ_SELECT } from './select.context';
14
-
15
- /**
16
- * Root select container. Manages open/close state via `kjSelectValue` model.
17
- *
18
- * @example
19
- * ```html
20
- * <div kjSelect [(kjSelectValue)]="selected">
21
- * <button kjSelectTrigger aria-haspopup="listbox">Choose fruit</button>
22
- * <div kjSelectContent>
23
- * <div kjOption [kjOptionValue]="'apple'">Apple</div>
24
- * </div>
25
- * </div>
26
- * ```
27
- * @category Core/Inputs
28
- */
29
- @Directive({
30
- selector: '[kjSelect]',
31
- standalone: true,
32
- providers: [{ provide: KJ_SELECT, useExisting: KjSelect }],
33
- })
34
- export class KjSelect {
35
- /** The currently selected value. Supports two-way binding. */
36
- readonly kjSelectValue = model<unknown>(undefined);
37
-
38
- /** Read-only signal for the current value. */
39
- readonly value = this.kjSelectValue.asReadonly();
40
-
41
- private readonly _open = signal(false);
42
-
43
- /** Whether the listbox is currently open. */
44
- readonly open = this._open.asReadonly();
45
-
46
- /**
47
- * Selects a value and closes the listbox.
48
- * @param val The value to select.
49
- */
50
- select(val: unknown): void {
51
- this.kjSelectValue.set(val);
52
- this._open.set(false);
53
- }
54
-
55
- /** Toggles the open state of the listbox. */
56
- toggle(): void {
57
- this._open.update(v => !v);
58
- }
59
-
60
- /** Closes the listbox. */
61
- hide(): void {
62
- this._open.set(false);
63
- }
64
- }
65
-
66
- /**
67
- * Trigger button that opens/closes the select listbox.
68
- * Automatically binds `aria-expanded` to reflect open state.
69
- *
70
- * @example
71
- * ```html
72
- * <button kjSelectTrigger aria-haspopup="listbox">Choose fruit</button>
73
- * ```
74
- * @category Core/Inputs
75
- */
76
- @Directive({
77
- selector: '[kjSelectTrigger]',
78
- standalone: true,
79
- hostDirectives: [KjFocusRing],
80
- host: {
81
- '[attr.aria-expanded]': 'ctx.open().toString()',
82
- '(click)': '$event.stopPropagation(); ctx.toggle()',
83
- },
84
- })
85
- export class KjSelectTrigger {
86
- /** @internal */
87
- readonly ctx = inject(KJ_SELECT);
88
- }
89
-
90
- /**
91
- * Listbox container. Provides keyboard navigation (Arrow keys, Home, End,
92
- * Enter, Space, type-ahead) and ARIA listbox semantics. Hidden when closed.
93
- *
94
- * @example
95
- * ```html
96
- * <div kjSelectContent>
97
- * <div kjOption [kjOptionValue]="'apple'">Apple</div>
98
- * </div>
99
- * ```
100
- * @category Core/Inputs
101
- */
102
- @Directive({
103
- selector: '[kjSelectContent]',
104
- standalone: true,
105
- host: {
106
- 'role': 'listbox',
107
- '[attr.hidden]': '!ctx.open() ? "" : null',
108
- '[attr.aria-activedescendant]': 'activeId()',
109
- '(keydown)': 'onKeydown($event)',
110
- '(document:keydown.escape)': 'ctx.hide()',
111
- '(document:click)': 'onDocClick()',
112
- '(click)': '$event.stopPropagation()',
113
- },
114
- })
115
- export class KjSelectContent {
116
- private readonly el = inject(ElementRef<HTMLElement>);
117
- /** @internal */
118
- readonly ctx = inject(KJ_SELECT);
119
-
120
- private readonly _activeIndex = signal(-1);
121
-
122
- /** @internal */
123
- readonly activeId = computed(() => {
124
- const items = this.getOptions();
125
- const idx = this._activeIndex();
126
- return items[idx]?.id ?? null;
127
- });
128
-
129
- /** @internal */
130
- onDocClick(): void {
131
- if (this.ctx.open()) this.ctx.hide();
132
- }
133
-
134
- /** @internal */
135
- onKeydown(e: KeyboardEvent): void {
136
- const items = this.getOptions();
137
- if (!items.length) return;
138
- let idx = this._activeIndex();
139
-
140
- switch (e.key) {
141
- case 'ArrowDown':
142
- e.preventDefault();
143
- idx = Math.min(idx + 1, items.length - 1);
144
- this._activeIndex.set(idx);
145
- items[idx]?.el.focus();
146
- break;
147
- case 'ArrowUp':
148
- e.preventDefault();
149
- idx = Math.max(idx - 1, 0);
150
- this._activeIndex.set(idx);
151
- items[idx]?.el.focus();
152
- break;
153
- case 'Home':
154
- e.preventDefault();
155
- this._activeIndex.set(0);
156
- items[0]?.el.focus();
157
- break;
158
- case 'End':
159
- e.preventDefault();
160
- this._activeIndex.set(items.length - 1);
161
- items[items.length - 1]?.el.focus();
162
- break;
163
- case 'Enter':
164
- case ' ':
165
- e.preventDefault();
166
- if (idx >= 0) {
167
- const val = items[idx]?.value;
168
- if (val !== undefined) this.ctx.select(val);
169
- }
170
- break;
171
- default: {
172
- const char = e.key.length === 1 ? e.key.toLowerCase() : null;
173
- if (char) {
174
- const match = items.findIndex(item =>
175
- item.el.textContent?.trim().toLowerCase().startsWith(char)
176
- );
177
- if (match >= 0) {
178
- this._activeIndex.set(match);
179
- items[match].el.focus();
180
- }
181
- }
182
- }
183
- }
184
- }
185
-
186
- private getOptions(): Array<{ el: HTMLElement; value: unknown; id: string }> {
187
- return (Array.from(
188
- this.el.nativeElement.querySelectorAll('[kjOption]')
189
- ) as HTMLElement[]).map((el, i) => ({
190
- el,
191
- value: (el as HTMLElement & { __kjOptionValue?: unknown }).__kjOptionValue,
192
- id: el.id || `kj-option-${i}`,
193
- }));
194
- }
195
- }
196
-
197
- /**
198
- * Individual option inside a `kjSelectContent`. Clicking or pressing Enter/Space
199
- * calls `select()` on the parent context.
200
- *
201
- * @example
202
- * ```html
203
- * <div kjOption [kjOptionValue]="'apple'">Apple</div>
204
- * ```
205
- * @category Core/Inputs
206
- */
207
- @Directive({
208
- selector: '[kjOption]',
209
- standalone: true,
210
- hostDirectives: [
211
- { directive: KjDisabled, inputs: ['kjDisabled'] },
212
- ],
213
- host: {
214
- 'role': 'option',
215
- '[attr.tabindex]': '"0"',
216
- '[attr.aria-selected]': 'selected().toString()',
217
- '[attr.aria-disabled]': 'disabled() ? "true" : null',
218
- '(click)': 'handleClick()',
219
- '(keydown.enter)': '$event.preventDefault(); handleClick()',
220
- '(keydown.space)': '$event.preventDefault(); handleClick()',
221
- },
222
- })
223
- export class KjOption {
224
- private readonly ctx = inject(KJ_SELECT);
225
- private readonly el = inject(ElementRef<HTMLElement>);
226
- /** @internal */
227
- readonly disabled = inject(KjDisabled).disabled;
228
-
229
- /** The value this option represents. */
230
- readonly kjOptionValue = input.required<unknown>();
231
-
232
- /** @internal */
233
- readonly selected = computed(() => this.ctx.value() === this.kjOptionValue());
234
-
235
- constructor() {
236
- effect(() => {
237
- (this.el.nativeElement as HTMLElement & { __kjOptionValue?: unknown }).__kjOptionValue = this.kjOptionValue();
238
- });
239
- afterNextRender(() => {
240
- if (!this.el.nativeElement.id) {
241
- this.el.nativeElement.id = `kj-option-${Math.random().toString(36).slice(2, 7)}`;
242
- }
243
- });
244
- }
245
-
246
- /** @internal */
247
- handleClick(): void {
248
- if (!this.disabled()) this.ctx.select(this.kjOptionValue());
249
- }
250
- }
1
+ export * from './select-root';
2
+ export * from './select-trigger';
3
+ export * from './select-content';
4
+ export * from './select-option';
@@ -0,0 +1,5 @@
1
+ export {
2
+ KjSkeleton,
3
+ type KjSkeletonAnimation,
4
+ type KjSkeletonShape,
5
+ } from './skeleton';
@@ -0,0 +1,115 @@
1
+ import { render } from '@testing-library/angular';
2
+ import { axe, toHaveNoViolations } from 'jest-axe';
3
+ import { describe, expect, it } from 'vitest';
4
+ import { KjSkeleton } from './skeleton';
5
+
6
+ expect.extend(toHaveNoViolations);
7
+
8
+ describe('KjSkeleton', () => {
9
+ describe('aria-hidden enforcement (load-bearing contract)', () => {
10
+ it('sets aria-hidden="true" on the host', async () => {
11
+ const { container } = await render(`<div kjSkeleton></div>`, {
12
+ imports: [KjSkeleton],
13
+ });
14
+ expect(container.querySelector('div')).toHaveAttribute('aria-hidden', 'true');
15
+ });
16
+
17
+ it('sets aria-hidden="true" on a <span> host', async () => {
18
+ const { container } = await render(`<span kjSkeleton></span>`, {
19
+ imports: [KjSkeleton],
20
+ });
21
+ expect(container.querySelector('span')).toHaveAttribute('aria-hidden', 'true');
22
+ });
23
+
24
+ it('host binding wins over a consumer-supplied [attr.aria-hidden]="false"', async () => {
25
+ // The directive's host binding `[attr.aria-hidden]="\"true\""` is a
26
+ // static expression evaluated by the directive, and Angular's binding
27
+ // semantics give the directive's host binding precedence over a
28
+ // template attribute literal on the same element. The consumer cannot
29
+ // turn the skeleton into something AT will read by writing
30
+ // `aria-hidden="false"` on the tag — this is the entire reason a
31
+ // directive exists rather than a CSS class. (Per analysis: not
32
+ // user-overridable.)
33
+ const { container } = await render(
34
+ `<div kjSkeleton aria-hidden="false"></div>`,
35
+ { imports: [KjSkeleton] },
36
+ );
37
+ expect(container.querySelector('div')).toHaveAttribute('aria-hidden', 'true');
38
+ });
39
+ });
40
+
41
+ describe('shape reflection', () => {
42
+ it('defaults to data-shape="rectangle"', async () => {
43
+ const { container } = await render(`<div kjSkeleton></div>`, {
44
+ imports: [KjSkeleton],
45
+ });
46
+ expect(container.querySelector('div')).toHaveAttribute('data-shape', 'rectangle');
47
+ });
48
+
49
+ it('reflects kjSkeletonShape="circle" to data-shape', async () => {
50
+ const { container } = await render(
51
+ `<div kjSkeleton [kjSkeletonShape]="'circle'"></div>`,
52
+ { imports: [KjSkeleton] },
53
+ );
54
+ expect(container.querySelector('div')).toHaveAttribute('data-shape', 'circle');
55
+ });
56
+
57
+ it('reflects kjSkeletonShape="text" to data-shape', async () => {
58
+ const { container } = await render(
59
+ `<div kjSkeleton [kjSkeletonShape]="'text'"></div>`,
60
+ { imports: [KjSkeleton] },
61
+ );
62
+ expect(container.querySelector('div')).toHaveAttribute('data-shape', 'text');
63
+ });
64
+
65
+ it('reflects kjSkeletonShape="text-block" to data-shape', async () => {
66
+ const { container } = await render(
67
+ `<div kjSkeleton [kjSkeletonShape]="'text-block'"></div>`,
68
+ { imports: [KjSkeleton] },
69
+ );
70
+ expect(container.querySelector('div')).toHaveAttribute('data-shape', 'text-block');
71
+ });
72
+ });
73
+
74
+ describe('animation reflection', () => {
75
+ it('defaults to data-animation="shimmer"', async () => {
76
+ const { container } = await render(`<div kjSkeleton></div>`, {
77
+ imports: [KjSkeleton],
78
+ });
79
+ expect(container.querySelector('div')).toHaveAttribute('data-animation', 'shimmer');
80
+ });
81
+
82
+ it('reflects kjSkeletonAnimation="pulse" to data-animation', async () => {
83
+ const { container } = await render(
84
+ `<div kjSkeleton [kjSkeletonAnimation]="'pulse'"></div>`,
85
+ { imports: [KjSkeleton] },
86
+ );
87
+ expect(container.querySelector('div')).toHaveAttribute('data-animation', 'pulse');
88
+ });
89
+
90
+ it('reflects kjSkeletonAnimation="none" to data-animation', async () => {
91
+ const { container } = await render(
92
+ `<div kjSkeleton [kjSkeletonAnimation]="'none'"></div>`,
93
+ { imports: [KjSkeleton] },
94
+ );
95
+ expect(container.querySelector('div')).toHaveAttribute('data-animation', 'none');
96
+ });
97
+ });
98
+
99
+ describe('a11y audits', () => {
100
+ it('a default skeleton passes axe', async () => {
101
+ const { container } = await render(`<div kjSkeleton></div>`, {
102
+ imports: [KjSkeleton],
103
+ });
104
+ expect(await axe(container)).toHaveNoViolations();
105
+ });
106
+
107
+ it('a circle skeleton with explicit dimensions passes axe', async () => {
108
+ const { container } = await render(
109
+ `<div kjSkeleton [kjSkeletonShape]="'circle'" style="width: 40px; height: 40px"></div>`,
110
+ { imports: [KjSkeleton] },
111
+ );
112
+ expect(await axe(container)).toHaveNoViolations();
113
+ });
114
+ });
115
+ });