@kouji-ui/core 0.0.3 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (534) hide show
  1. package/.turbo/turbo-build.log +6 -3
  2. package/CHANGELOG.md +177 -0
  3. package/ng-package.json +5 -1
  4. package/package.json +18 -6
  5. package/src/a11y/aria-describedby.ts +7 -3
  6. package/src/a11y/aria-labelledby.spec.ts +55 -0
  7. package/src/a11y/aria-labelledby.ts +38 -0
  8. package/src/a11y/focus-trap.ts +4 -2
  9. package/src/a11y/index.ts +1 -0
  10. package/src/a11y/live-region.ts +4 -2
  11. package/src/a11y/roving-tabindex.spec.ts +139 -0
  12. package/src/a11y/roving-tabindex.ts +56 -7
  13. package/src/a11y/visually-hidden.ts +4 -2
  14. package/src/accordion/accordion.context.ts +22 -0
  15. package/src/accordion/accordion.spec.ts +233 -23
  16. package/src/accordion/accordion.ts +311 -39
  17. package/src/accordion/index.ts +8 -2
  18. package/src/alert/alert.context.ts +28 -0
  19. package/src/alert/alert.spec.ts +217 -0
  20. package/src/alert/alert.ts +417 -0
  21. package/src/alert/config.ts +46 -0
  22. package/src/alert/index.ts +19 -0
  23. package/src/avatar/avatar-group.context.ts +45 -0
  24. package/src/avatar/avatar-group.spec.ts +358 -0
  25. package/src/avatar/avatar-group.ts +259 -0
  26. package/src/avatar/avatar.ts +17 -4
  27. package/src/avatar/index.ts +11 -1
  28. package/src/badge/badge.spec.ts +8 -0
  29. package/src/badge/badge.ts +24 -3
  30. package/src/breadcrumb/breadcrumb-current.ts +38 -0
  31. package/src/breadcrumb/breadcrumb-ellipsis.ts +71 -0
  32. package/src/breadcrumb/breadcrumb-item.ts +41 -0
  33. package/src/breadcrumb/breadcrumb-link.ts +44 -0
  34. package/src/breadcrumb/breadcrumb-list.ts +42 -0
  35. package/src/breadcrumb/breadcrumb-separator.ts +45 -0
  36. package/src/breadcrumb/breadcrumb.context.ts +45 -0
  37. package/src/breadcrumb/breadcrumb.spec.ts +354 -0
  38. package/src/breadcrumb/breadcrumb.ts +239 -0
  39. package/src/breadcrumb/config.ts +84 -0
  40. package/src/breadcrumb/index.ts +18 -0
  41. package/src/button/_examples/button.example.ts +71 -0
  42. package/src/button/_examples/button.finance.example.ts +83 -0
  43. package/src/button/_examples/button.retro.example.ts +91 -0
  44. package/src/button/_examples/button.sizes.example.ts +60 -0
  45. package/src/button/_examples/index.ts +6 -0
  46. package/src/button/button.spec.ts +139 -8
  47. package/src/button/button.ts +114 -16
  48. package/src/button/config.spec.ts +33 -0
  49. package/src/button/config.ts +45 -0
  50. package/src/button/index.ts +2 -1
  51. package/src/button-group/button-group.context.ts +28 -0
  52. package/src/button-group/button-group.spec.ts +99 -0
  53. package/src/button-group/button-group.ts +99 -0
  54. package/src/button-group/index.ts +2 -0
  55. package/src/calendar/calendar-day.ts +102 -0
  56. package/src/calendar/calendar-grid.ts +90 -0
  57. package/src/calendar/calendar-header.ts +65 -0
  58. package/src/calendar/calendar.context.ts +53 -0
  59. package/src/calendar/calendar.spec.ts +225 -0
  60. package/src/calendar/calendar.ts +233 -0
  61. package/src/calendar/date-utils.ts +225 -0
  62. package/src/calendar/index.ts +28 -0
  63. package/src/carousel/carousel.context.ts +81 -0
  64. package/src/carousel/carousel.spec.ts +337 -0
  65. package/src/carousel/carousel.ts +919 -0
  66. package/src/carousel/index.ts +22 -0
  67. package/src/cascade-select/cascade-select-option.ts +205 -0
  68. package/src/cascade-select/cascade-select-panel.ts +106 -0
  69. package/src/cascade-select/cascade-select-root.ts +265 -0
  70. package/src/cascade-select/cascade-select-sub-panel.ts +168 -0
  71. package/src/cascade-select/cascade-select-trigger.ts +44 -0
  72. package/src/cascade-select/cascade-select.context.ts +71 -0
  73. package/src/cascade-select/cascade-select.spec.ts +241 -0
  74. package/src/cascade-select/cascade-select.ts +5 -0
  75. package/src/cascade-select/index.ts +11 -0
  76. package/src/chart/chart.ts +6 -2
  77. package/src/chat/chat-avatar.ts +31 -0
  78. package/src/chat/chat-bubble.ts +49 -0
  79. package/src/chat/chat-footer.ts +67 -0
  80. package/src/chat/chat-header.ts +41 -0
  81. package/src/chat/chat-log.ts +43 -0
  82. package/src/chat/chat.context.ts +64 -0
  83. package/src/chat/chat.spec.ts +341 -0
  84. package/src/chat/chat.ts +191 -0
  85. package/src/chat/config.ts +53 -0
  86. package/src/chat/index.ts +21 -0
  87. package/src/checkbox/checkbox.ts +9 -2
  88. package/src/color-picker/color-picker.context.ts +74 -0
  89. package/src/color-picker/color-picker.spec.ts +241 -0
  90. package/src/color-picker/color-picker.ts +698 -0
  91. package/src/color-picker/color-picker.utils.ts +140 -0
  92. package/src/color-picker/index.ts +28 -0
  93. package/src/combobox/combobox-input.ts +143 -0
  94. package/src/combobox/combobox-listbox.ts +49 -0
  95. package/src/combobox/combobox-option.ts +43 -0
  96. package/src/combobox/combobox-root.ts +281 -0
  97. package/src/combobox/combobox.spec.ts +75 -0
  98. package/src/combobox/combobox.ts +4 -0
  99. package/src/combobox/index.ts +4 -0
  100. package/src/command-palette/command-empty.ts +36 -0
  101. package/src/command-palette/command-group.ts +21 -0
  102. package/src/command-palette/command-input.ts +70 -0
  103. package/src/command-palette/command-item.ts +60 -0
  104. package/src/command-palette/command-list.ts +25 -0
  105. package/src/command-palette/command-palette-dialog.ts +74 -0
  106. package/src/command-palette/command-palette-trigger.ts +64 -0
  107. package/src/command-palette/command-palette.filters.ts +53 -0
  108. package/src/command-palette/command-palette.spec.ts +349 -0
  109. package/src/command-palette/command-palette.ts +204 -0
  110. package/src/command-palette/command-separator.ts +18 -0
  111. package/src/command-palette/index.ts +15 -0
  112. package/src/confirm-popup/confirm-popup-action.ts +39 -0
  113. package/src/confirm-popup/confirm-popup-cancel.ts +38 -0
  114. package/src/confirm-popup/confirm-popup-content.ts +113 -0
  115. package/src/confirm-popup/confirm-popup-message.ts +36 -0
  116. package/src/confirm-popup/confirm-popup-trigger.ts +31 -0
  117. package/src/confirm-popup/confirm-popup.context.ts +41 -0
  118. package/src/confirm-popup/confirm-popup.spec.ts +337 -0
  119. package/src/confirm-popup/confirm-popup.ts +109 -0
  120. package/src/confirm-popup/index.ts +12 -0
  121. package/src/date-picker/date-picker-calendar.ts +65 -0
  122. package/src/date-picker/date-picker-trigger.ts +215 -0
  123. package/src/date-picker/date-picker.context.ts +50 -0
  124. package/src/date-picker/date-picker.spec.ts +117 -0
  125. package/src/date-picker/date-picker.ts +127 -0
  126. package/src/date-picker/index.ts +7 -0
  127. package/src/dialog/_examples/dialog.confirm.example.ts +145 -0
  128. package/src/dialog/_examples/dialog.example.ts +106 -0
  129. package/src/dialog/_examples/dialog.finance.example.ts +121 -0
  130. package/src/dialog/_examples/dialog.retro.example.ts +126 -0
  131. package/src/dialog/_examples/index.ts +6 -0
  132. package/src/dialog/dialog.ref.ts +42 -0
  133. package/src/dialog/dialog.service.ts +57 -167
  134. package/src/dialog/dialog.spec.ts +35 -120
  135. package/src/dialog/dialog.ts +17 -192
  136. package/src/dialog/index.ts +3 -9
  137. package/src/divider/divider.spec.ts +223 -0
  138. package/src/divider/divider.ts +137 -0
  139. package/src/divider/index.ts +5 -0
  140. package/src/drawer/drawer.ref.ts +63 -0
  141. package/src/drawer/drawer.service.ts +106 -0
  142. package/src/drawer/drawer.spec.ts +117 -0
  143. package/src/drawer/drawer.ts +122 -0
  144. package/src/drawer/index.ts +10 -0
  145. package/src/dropdown-menu/dropdown-menu-content.ts +258 -0
  146. package/src/dropdown-menu/dropdown-menu-group.ts +31 -0
  147. package/src/dropdown-menu/dropdown-menu-item.ts +51 -0
  148. package/src/dropdown-menu/dropdown-menu-label.ts +41 -0
  149. package/src/dropdown-menu/dropdown-menu-separator.ts +27 -0
  150. package/src/dropdown-menu/dropdown-menu-trigger.ts +140 -0
  151. package/src/dropdown-menu/dropdown-menu.spec.ts +265 -0
  152. package/src/dropdown-menu/dropdown-menu.ts +60 -0
  153. package/src/dropdown-menu/index.ts +15 -0
  154. package/src/example-components.ts +39 -30
  155. package/src/field/field-error.ts +49 -0
  156. package/src/field/field-group.ts +39 -0
  157. package/src/field/field-help.ts +47 -0
  158. package/src/field/field-label.ts +30 -0
  159. package/src/field/field.context.ts +28 -0
  160. package/src/field/field.spec.ts +179 -0
  161. package/src/field/field.ts +121 -0
  162. package/src/field/index.ts +6 -0
  163. package/src/file-upload/file-upload.context.ts +89 -0
  164. package/src/file-upload/file-upload.spec.ts +290 -0
  165. package/src/file-upload/file-upload.ts +639 -0
  166. package/src/file-upload/file-upload.types.ts +59 -0
  167. package/src/file-upload/index.ts +23 -0
  168. package/src/form/form-error-summary.ts +87 -0
  169. package/src/form/form-field.context.ts +3 -0
  170. package/src/form/form-field.spec.ts +25 -0
  171. package/src/form/form-field.ts +52 -0
  172. package/src/form/form.context.ts +52 -2
  173. package/src/form/form.spec.ts +155 -18
  174. package/src/form/form.ts +315 -35
  175. package/src/form/index.ts +11 -2
  176. package/src/icon/E2E.md +14 -0
  177. package/src/icon/icon.css +40 -0
  178. package/src/icon/icon.directive.spec.ts +198 -0
  179. package/src/icon/icon.directive.ts +113 -0
  180. package/src/icon/icon.mode.spec.ts +29 -0
  181. package/src/icon/icon.mode.ts +23 -0
  182. package/src/icon/icon.providers.spec.ts +63 -0
  183. package/src/icon/icon.providers.ts +47 -0
  184. package/src/icon/icon.resolver.spec.ts +96 -0
  185. package/src/icon/icon.resolver.ts +49 -0
  186. package/src/icon/icon.tokens.ts +88 -0
  187. package/src/icon/icon.types.ts +48 -0
  188. package/src/icon/index.ts +21 -0
  189. package/src/input/input.ts +12 -3
  190. package/src/input-group/index.ts +3 -0
  191. package/src/input-group/input-group-addon.ts +92 -0
  192. package/src/input-group/input-group.context.ts +27 -0
  193. package/src/input-group/input-group.spec.ts +186 -0
  194. package/src/input-group/input-group.ts +136 -0
  195. package/src/input-mask/index.ts +11 -0
  196. package/src/input-mask/input-mask.compile.ts +69 -0
  197. package/src/input-mask/input-mask.engine.ts +376 -0
  198. package/src/input-mask/input-mask.presets.ts +24 -0
  199. package/src/input-mask/input-mask.spec.ts +304 -0
  200. package/src/input-mask/input-mask.tokens.ts +49 -0
  201. package/src/input-mask/input-mask.ts +280 -0
  202. package/src/input-otp/index.ts +2 -0
  203. package/src/input-otp/input-otp.context.ts +41 -0
  204. package/src/input-otp/input-otp.spec.ts +308 -0
  205. package/src/input-otp/input-otp.ts +470 -0
  206. package/src/kbd/index.ts +1 -0
  207. package/src/kbd/kbd.spec.ts +127 -0
  208. package/src/kbd/kbd.ts +132 -0
  209. package/src/link/config.ts +44 -0
  210. package/src/link/index.ts +2 -0
  211. package/src/link/link.spec.ts +246 -0
  212. package/src/link/link.ts +226 -0
  213. package/src/list/index.ts +11 -0
  214. package/src/list/list.spec.ts +237 -0
  215. package/src/list/list.ts +277 -0
  216. package/src/menubar/index.ts +7 -0
  217. package/src/menubar/menubar-item.ts +204 -0
  218. package/src/menubar/menubar.context.ts +43 -0
  219. package/src/menubar/menubar.spec.ts +433 -0
  220. package/src/menubar/menubar.ts +309 -0
  221. package/src/number-input/index.ts +4 -0
  222. package/src/number-input/number-input-group.ts +64 -0
  223. package/src/number-input/number-input.context.ts +31 -0
  224. package/src/number-input/number-input.format.ts +148 -0
  225. package/src/number-input/number-input.spec.ts +170 -0
  226. package/src/number-input/number-input.ts +457 -0
  227. package/src/number-input/number-stepper.ts +193 -0
  228. package/src/overlay-badge/index.ts +7 -0
  229. package/src/overlay-badge/overlay-badge-content.ts +73 -0
  230. package/src/overlay-badge/overlay-badge.context.ts +37 -0
  231. package/src/overlay-badge/overlay-badge.spec.ts +220 -0
  232. package/src/overlay-badge/overlay-badge.ts +128 -0
  233. package/src/pagination/config.ts +82 -0
  234. package/src/pagination/index.ts +19 -0
  235. package/src/pagination/page-tokens.ts +78 -0
  236. package/src/pagination/pagination-ellipsis.ts +69 -0
  237. package/src/pagination/pagination-first.ts +84 -0
  238. package/src/pagination/pagination-info.ts +70 -0
  239. package/src/pagination/pagination-item.ts +81 -0
  240. package/src/pagination/pagination-last.ts +85 -0
  241. package/src/pagination/pagination-next.ts +86 -0
  242. package/src/pagination/pagination-previous.ts +90 -0
  243. package/src/pagination/pagination.context.ts +52 -0
  244. package/src/pagination/pagination.spec.ts +388 -0
  245. package/src/pagination/pagination.ts +288 -0
  246. package/src/password-input/index.ts +15 -0
  247. package/src/password-input/password-input.context.ts +65 -0
  248. package/src/password-input/password-input.scorer.ts +34 -0
  249. package/src/password-input/password-input.spec.ts +274 -0
  250. package/src/password-input/password-input.ts +488 -0
  251. package/src/popover/_examples/index.ts +5 -0
  252. package/src/popover/_examples/popover.example.ts +92 -0
  253. package/src/popover/_examples/popover.finance.example.ts +106 -0
  254. package/src/popover/_examples/popover.retro.example.ts +103 -0
  255. package/src/popover/index.ts +5 -12
  256. package/src/popover/popover-arrow.ts +30 -0
  257. package/src/popover/popover-close.ts +36 -0
  258. package/src/popover/popover-content.ts +41 -0
  259. package/src/popover/popover-title.ts +39 -0
  260. package/src/popover/popover-trigger.ts +40 -0
  261. package/src/popover/popover.spec.ts +51 -44
  262. package/src/popover/popover.ts +5 -124
  263. package/src/presets/bind-presets.spec.ts +50 -0
  264. package/src/presets/bind-presets.ts +43 -0
  265. package/src/presets/index.ts +3 -0
  266. package/src/presets/size.spec.ts +67 -0
  267. package/src/presets/size.ts +59 -0
  268. package/src/presets/variant.spec.ts +72 -0
  269. package/src/presets/variant.ts +62 -0
  270. package/src/primitives/directionality/directionality.spec.ts +112 -0
  271. package/src/primitives/directionality/directionality.ts +89 -0
  272. package/src/primitives/directionality/index.ts +1 -0
  273. package/src/primitives/forms/form-control.spec.ts +14 -6
  274. package/src/primitives/forms/form-control.ts +6 -2
  275. package/src/primitives/index.ts +3 -0
  276. package/src/primitives/interaction/disabled.ts +12 -5
  277. package/src/primitives/interaction/focus-ring.ts +4 -2
  278. package/src/primitives/list/filterable-list.spec.ts +114 -0
  279. package/src/primitives/list/filterable-list.ts +126 -0
  280. package/src/primitives/list/group.spec.ts +99 -0
  281. package/src/primitives/list/group.ts +139 -0
  282. package/src/primitives/list/index.ts +8 -0
  283. package/src/primitives/list/inject-helpers.ts +8 -0
  284. package/src/primitives/list/item.spec.ts +173 -0
  285. package/src/primitives/list/item.ts +222 -0
  286. package/src/primitives/list/navigator.spec.ts +286 -0
  287. package/src/primitives/list/navigator.ts +238 -0
  288. package/src/primitives/list/selection.spec.ts +214 -0
  289. package/src/primitives/list/selection.ts +339 -0
  290. package/src/primitives/list/tokens.ts +106 -0
  291. package/src/primitives/list/type-ahead.spec.ts +58 -0
  292. package/src/primitives/list/type-ahead.ts +46 -0
  293. package/src/primitives/overlay/backdrop.spec.ts +54 -0
  294. package/src/primitives/overlay/backdrop.ts +31 -0
  295. package/src/primitives/overlay/builder.spec.ts +38 -0
  296. package/src/primitives/overlay/builder.ts +151 -0
  297. package/src/primitives/overlay/container.ts +42 -0
  298. package/src/primitives/overlay/context.ts +14 -0
  299. package/src/primitives/overlay/controller.spec.ts +86 -0
  300. package/src/primitives/overlay/controller.ts +190 -0
  301. package/src/primitives/overlay/id.spec.ts +24 -0
  302. package/src/primitives/overlay/id.ts +17 -0
  303. package/src/primitives/overlay/index.ts +18 -1
  304. package/src/primitives/overlay/overlay.css +42 -0
  305. package/src/primitives/overlay/panel.spec.ts +52 -0
  306. package/src/primitives/overlay/panel.ts +102 -0
  307. package/src/primitives/overlay/stack.spec.ts +55 -0
  308. package/src/primitives/overlay/stack.ts +113 -0
  309. package/src/primitives/overlay/strategies/backdrop/blurred.spec.ts +11 -0
  310. package/src/primitives/overlay/strategies/backdrop/blurred.ts +5 -0
  311. package/src/primitives/overlay/strategies/backdrop/none.spec.ts +10 -0
  312. package/src/primitives/overlay/strategies/backdrop/none.ts +9 -0
  313. package/src/primitives/overlay/strategies/backdrop/solid.spec.ts +18 -0
  314. package/src/primitives/overlay/strategies/backdrop/solid.ts +23 -0
  315. package/src/primitives/overlay/strategies/focus-trap/inert-based.spec.ts +28 -0
  316. package/src/primitives/overlay/strategies/focus-trap/inert-based.ts +38 -0
  317. package/src/primitives/overlay/strategies/focus-trap/no-trap.spec.ts +9 -0
  318. package/src/primitives/overlay/strategies/focus-trap/no-trap.ts +8 -0
  319. package/src/primitives/overlay/strategies/focus-trap/tab-cycle.spec.ts +49 -0
  320. package/src/primitives/overlay/strategies/focus-trap/tab-cycle.ts +85 -0
  321. package/src/primitives/overlay/strategies/index.ts +34 -0
  322. package/src/primitives/overlay/strategies/live-announcer/_announce.ts +35 -0
  323. package/src/primitives/overlay/strategies/live-announcer/assertive.spec.ts +21 -0
  324. package/src/primitives/overlay/strategies/live-announcer/assertive.ts +11 -0
  325. package/src/primitives/overlay/strategies/live-announcer/polite.spec.ts +21 -0
  326. package/src/primitives/overlay/strategies/live-announcer/polite.ts +11 -0
  327. package/src/primitives/overlay/strategies/live-announcer/silent.spec.ts +12 -0
  328. package/src/primitives/overlay/strategies/live-announcer/silent.ts +5 -0
  329. package/src/primitives/overlay/strategies/mount/body-portal.spec.ts +40 -0
  330. package/src/primitives/overlay/strategies/mount/body-portal.ts +91 -0
  331. package/src/primitives/overlay/strategies/mount/in-container.spec.ts +43 -0
  332. package/src/primitives/overlay/strategies/mount/in-container.ts +39 -0
  333. package/src/primitives/overlay/strategies/mount/in-place.spec.ts +40 -0
  334. package/src/primitives/overlay/strategies/mount/in-place.ts +20 -0
  335. package/src/primitives/overlay/strategies/position/anchored-to.spec.ts +56 -0
  336. package/src/primitives/overlay/strategies/position/anchored-to.ts +248 -0
  337. package/src/primitives/overlay/strategies/position/corner.spec.ts +36 -0
  338. package/src/primitives/overlay/strategies/position/corner.ts +33 -0
  339. package/src/primitives/overlay/strategies/position/edge-sheet.spec.ts +32 -0
  340. package/src/primitives/overlay/strategies/position/edge-sheet.ts +36 -0
  341. package/src/primitives/overlay/strategies/position/in-place-sibling.spec.ts +25 -0
  342. package/src/primitives/overlay/strategies/position/in-place-sibling.ts +13 -0
  343. package/src/primitives/overlay/strategies/position/point-at.spec.ts +40 -0
  344. package/src/primitives/overlay/strategies/position/point-at.ts +34 -0
  345. package/src/primitives/overlay/strategies/position/viewport-centered.spec.ts +31 -0
  346. package/src/primitives/overlay/strategies/position/viewport-centered.ts +31 -0
  347. package/src/primitives/overlay/strategies/scroll-lock/css-clip.spec.ts +15 -0
  348. package/src/primitives/overlay/strategies/scroll-lock/css-clip.ts +39 -0
  349. package/src/primitives/overlay/strategies/scroll-lock/html-overflow.spec.ts +21 -0
  350. package/src/primitives/overlay/strategies/scroll-lock/html-overflow.ts +44 -0
  351. package/src/primitives/overlay/strategies/scroll-lock/none.spec.ts +9 -0
  352. package/src/primitives/overlay/strategies/scroll-lock/none.ts +5 -0
  353. package/src/primitives/overlay/strategies/trigger-event/on-click.spec.ts +32 -0
  354. package/src/primitives/overlay/strategies/trigger-event/on-click.ts +28 -0
  355. package/src/primitives/overlay/strategies/trigger-event/on-context-menu.spec.ts +45 -0
  356. package/src/primitives/overlay/strategies/trigger-event/on-context-menu.ts +70 -0
  357. package/src/primitives/overlay/strategies/trigger-event/on-focus-or-input.spec.ts +35 -0
  358. package/src/primitives/overlay/strategies/trigger-event/on-focus-or-input.ts +34 -0
  359. package/src/primitives/overlay/strategies/trigger-event/on-focus.spec.ts +64 -0
  360. package/src/primitives/overlay/strategies/trigger-event/on-focus.ts +37 -0
  361. package/src/primitives/overlay/strategies/trigger-event/on-hotkey.spec.ts +37 -0
  362. package/src/primitives/overlay/strategies/trigger-event/on-hotkey.ts +59 -0
  363. package/src/primitives/overlay/strategies/trigger-event/on-hover.spec.ts +34 -0
  364. package/src/primitives/overlay/strategies/trigger-event/on-hover.ts +86 -0
  365. package/src/primitives/overlay/strategies/trigger-event/programmatic.spec.ts +16 -0
  366. package/src/primitives/overlay/strategies/trigger-event/programmatic.ts +12 -0
  367. package/src/primitives/overlay/tokens.ts +54 -0
  368. package/src/primitives/overlay/trigger.spec.ts +103 -0
  369. package/src/primitives/overlay/trigger.ts +75 -0
  370. package/src/primitives/overlay/types.ts +24 -0
  371. package/src/primitives/overlay/wrapper.ts +50 -0
  372. package/src/primitives/signals/deep-signal.ts +109 -0
  373. package/src/primitives/signals/index.ts +1 -0
  374. package/src/progress-bar/config.ts +48 -0
  375. package/src/progress-bar/index.ts +12 -0
  376. package/src/progress-bar/progress-bar-fill.ts +37 -0
  377. package/src/progress-bar/progress-bar.context.ts +34 -0
  378. package/src/progress-bar/progress-bar.spec.ts +423 -0
  379. package/src/progress-bar/progress-bar.ts +217 -0
  380. package/src/public-api.ts +58 -9
  381. package/src/radio/radio.ts +14 -3
  382. package/src/select/index.ts +4 -2
  383. package/src/select/select-content.ts +69 -0
  384. package/src/select/select-option.ts +35 -0
  385. package/src/select/select-root.ts +129 -0
  386. package/src/select/select-trigger.ts +93 -0
  387. package/src/select/select.spec.ts +133 -140
  388. package/src/select/select.ts +4 -250
  389. package/src/skeleton/index.ts +5 -0
  390. package/src/skeleton/skeleton.spec.ts +115 -0
  391. package/src/skeleton/skeleton.ts +56 -0
  392. package/src/slider/index.ts +10 -0
  393. package/src/slider/slider-range.ts +49 -0
  394. package/src/slider/slider-thumb.ts +335 -0
  395. package/src/slider/slider-track.ts +70 -0
  396. package/src/slider/slider.context.ts +79 -0
  397. package/src/slider/slider.geometry.ts +73 -0
  398. package/src/slider/slider.spec.ts +216 -0
  399. package/src/slider/slider.ts +443 -0
  400. package/src/speed-dial/index.ts +10 -0
  401. package/src/speed-dial/speed-dial-action.ts +40 -0
  402. package/src/speed-dial/speed-dial-actions.ts +31 -0
  403. package/src/speed-dial/speed-dial-trigger.ts +55 -0
  404. package/src/speed-dial/speed-dial.context.ts +36 -0
  405. package/src/speed-dial/speed-dial.spec.ts +143 -0
  406. package/src/speed-dial/speed-dial.ts +127 -0
  407. package/src/spinner/config.ts +48 -0
  408. package/src/spinner/index.ts +8 -0
  409. package/src/spinner/spinner.spec.ts +262 -0
  410. package/src/spinner/spinner.ts +129 -0
  411. package/src/stepper/index.ts +15 -0
  412. package/src/stepper/stepper.context.ts +63 -0
  413. package/src/stepper/stepper.spec.ts +403 -0
  414. package/src/stepper/stepper.ts +544 -0
  415. package/src/styles.css +17 -0
  416. package/src/table/column-helpers.spec.ts +74 -0
  417. package/src/table/column-helpers.ts +68 -0
  418. package/src/table/filter-fns.ts +230 -0
  419. package/src/table/filter-models.spec.ts +240 -0
  420. package/src/table/filter-models.ts +124 -0
  421. package/src/table/filter-params.ts +90 -0
  422. package/src/table/grid-api-impl.ts +321 -0
  423. package/src/table/grid-api.spec.ts +178 -0
  424. package/src/table/grid-api.ts +285 -0
  425. package/src/table/index.ts +73 -1
  426. package/src/table/table-cell.spec.ts +43 -0
  427. package/src/table/table-cell.ts +38 -0
  428. package/src/table/table-filter-outlet.ts +120 -0
  429. package/src/table/table-header.spec.ts +54 -0
  430. package/src/table/table-header.ts +63 -0
  431. package/src/table/table-keyboard.spec.ts +68 -0
  432. package/src/table/table-keyboard.ts +107 -0
  433. package/src/table/table-resource.spec.ts +37 -0
  434. package/src/table/table-resource.ts +36 -0
  435. package/src/table/table-row.spec.ts +54 -0
  436. package/src/table/table-row.ts +31 -0
  437. package/src/table/table-storage.spec.ts +67 -0
  438. package/src/table/table-storage.ts +76 -0
  439. package/src/table/table.spec.ts +102 -43
  440. package/src/table/table.ts +228 -73
  441. package/src/table/table.types.ts +83 -0
  442. package/src/tabs/index.ts +7 -2
  443. package/src/tabs/tabs.context.ts +27 -4
  444. package/src/tabs/tabs.spec.ts +336 -24
  445. package/src/tabs/tabs.ts +313 -79
  446. package/src/tag/config.ts +53 -0
  447. package/src/tag/index.ts +7 -0
  448. package/src/tag/tag-list.ts +76 -0
  449. package/src/tag/tag-remove.ts +61 -0
  450. package/src/tag/tag.context.ts +36 -0
  451. package/src/tag/tag.spec.ts +240 -0
  452. package/src/tag/tag.ts +203 -0
  453. package/src/textarea/config.ts +42 -0
  454. package/src/textarea/index.ts +8 -0
  455. package/src/textarea/textarea.spec.ts +176 -0
  456. package/src/textarea/textarea.ts +235 -0
  457. package/src/time-picker/index.ts +15 -0
  458. package/src/time-picker/time-picker-meridiem.ts +123 -0
  459. package/src/time-picker/time-picker-segment.ts +318 -0
  460. package/src/time-picker/time-picker.context.ts +38 -0
  461. package/src/time-picker/time-picker.format.ts +181 -0
  462. package/src/time-picker/time-picker.spec.ts +189 -0
  463. package/src/time-picker/time-picker.ts +274 -0
  464. package/src/toast/_examples/index.ts +5 -0
  465. package/src/toast/_examples/toast.example.ts +282 -0
  466. package/src/toast/_examples/toast.finance.example.ts +180 -0
  467. package/src/toast/_examples/toast.retro.example.ts +161 -0
  468. package/src/toast/index.ts +8 -1
  469. package/src/toast/toast.ref.ts +26 -0
  470. package/src/toast/toast.service.ts +227 -41
  471. package/src/toast/toast.spec.ts +178 -25
  472. package/src/toast/toast.strategy.ts +10 -0
  473. package/src/toast/toast.ts +178 -22
  474. package/src/toggle/toggle.ts +10 -2
  475. package/src/tooltip/_examples/index.ts +6 -0
  476. package/src/tooltip/_examples/tooltip.example.ts +82 -0
  477. package/src/tooltip/_examples/tooltip.finance.example.ts +97 -0
  478. package/src/tooltip/_examples/tooltip.placements.example.ts +83 -0
  479. package/src/tooltip/_examples/tooltip.retro.example.ts +107 -0
  480. package/src/tooltip/index.ts +4 -5
  481. package/src/tooltip/tooltip-arrow.ts +31 -0
  482. package/src/tooltip/tooltip-content.ts +38 -0
  483. package/src/tooltip/tooltip-group.ts +32 -0
  484. package/src/tooltip/tooltip-trigger.ts +47 -0
  485. package/src/tooltip/tooltip.spec.ts +38 -102
  486. package/src/tooltip/tooltip.ts +5 -129
  487. package/src/tree-select/index.ts +12 -0
  488. package/src/tree-select/tree-select-content.ts +199 -0
  489. package/src/tree-select/tree-select-node.ts +132 -0
  490. package/src/tree-select/tree-select-root.ts +272 -0
  491. package/src/tree-select/tree-select-trigger.ts +90 -0
  492. package/src/tree-select/tree-select.context.ts +63 -0
  493. package/src/tree-select/tree-select.spec.ts +433 -0
  494. package/src/tree-select/tree-select.ts +2 -0
  495. package/src/typography/blockquote.ts +57 -0
  496. package/src/typography/code.ts +53 -0
  497. package/src/typography/index.ts +22 -0
  498. package/src/typography/lead.ts +49 -0
  499. package/src/typography/muted.ts +29 -0
  500. package/src/typography/prose.css +312 -0
  501. package/src/typography/truncate.ts +99 -0
  502. package/src/typography/typography.spec.ts +203 -0
  503. package/tsconfig.lib.json +9 -1
  504. package/tsconfig.lib.prod.json +7 -1
  505. package/tsconfig.spec.json +9 -1
  506. package/vite.config.ts +8 -0
  507. package/src/button/button.example.ts +0 -30
  508. package/src/button/button.finance.example.ts +0 -37
  509. package/src/button/button.retro.example.ts +0 -47
  510. package/src/button/button.sizes.example.ts +0 -25
  511. package/src/dialog/dialog.confirm.example.ts +0 -57
  512. package/src/dialog/dialog.context.ts +0 -11
  513. package/src/dialog/dialog.example.ts +0 -40
  514. package/src/dialog/dialog.finance.example.ts +0 -47
  515. package/src/dialog/dialog.retro.example.ts +0 -46
  516. package/src/menu/index.ts +0 -2
  517. package/src/menu/menu.context.ts +0 -10
  518. package/src/menu/menu.example.ts +0 -39
  519. package/src/menu/menu.spec.ts +0 -168
  520. package/src/menu/menu.ts +0 -176
  521. package/src/popover/popover.context.ts +0 -18
  522. package/src/popover/popover.example.ts +0 -50
  523. package/src/popover/popover.finance.example.ts +0 -52
  524. package/src/popover/popover.retro.example.ts +0 -53
  525. package/src/primitives/overlay/overlay.spec.ts +0 -90
  526. package/src/primitives/overlay/overlay.ts +0 -102
  527. package/src/select/select.context.ts +0 -3
  528. package/src/toast/toast.example.ts +0 -134
  529. package/src/toast/toast.finance.example.ts +0 -68
  530. package/src/toast/toast.retro.example.ts +0 -69
  531. package/src/tooltip/tooltip.example.ts +0 -46
  532. package/src/tooltip/tooltip.finance.example.ts +0 -54
  533. package/src/tooltip/tooltip.placements.example.ts +0 -56
  534. package/src/tooltip/tooltip.retro.example.ts +0 -65
@@ -0,0 +1,223 @@
1
+ import { render } from '@testing-library/angular';
2
+ import { axe, toHaveNoViolations } from 'jest-axe';
3
+ import { describe, expect, it } from 'vitest';
4
+ import { KjDivider } from './divider';
5
+
6
+ expect.extend(toHaveNoViolations);
7
+
8
+ /**
9
+ * Flush microtasks + the rAF that backs afterNextRender so the directive has
10
+ * a chance to inspect host child nodes and update its with-content / isHr
11
+ * signals.
12
+ */
13
+ async function flushAfterNextRender(): Promise<void> {
14
+ await new Promise<void>((resolve) => setTimeout(resolve, 0));
15
+ await Promise.resolve();
16
+ await Promise.resolve();
17
+ }
18
+
19
+ describe('KjDivider', () => {
20
+ describe('decorative (default)', () => {
21
+ it('on <hr> host: aria-hidden="true" and no role', async () => {
22
+ const { container } = await render(`<hr kjDivider />`, { imports: [KjDivider] });
23
+ await flushAfterNextRender();
24
+ const host = container.querySelector('hr')!;
25
+ expect(host).toHaveAttribute('aria-hidden', 'true');
26
+ expect(host).not.toHaveAttribute('role');
27
+ });
28
+
29
+ it('on <div> host: aria-hidden="true" and no role', async () => {
30
+ const { container } = await render(`<div kjDivider></div>`, { imports: [KjDivider] });
31
+ await flushAfterNextRender();
32
+ const host = container.querySelector('div')!;
33
+ expect(host).toHaveAttribute('aria-hidden', 'true');
34
+ expect(host).not.toHaveAttribute('role');
35
+ });
36
+
37
+ it('omits aria-orientation in decorative mode (even when vertical)', async () => {
38
+ const { container } = await render(
39
+ `<hr kjDivider [kjOrientation]="'vertical'" />`,
40
+ { imports: [KjDivider] },
41
+ );
42
+ await flushAfterNextRender();
43
+ const host = container.querySelector('hr')!;
44
+ expect(host).not.toHaveAttribute('aria-orientation');
45
+ });
46
+ });
47
+
48
+ describe('structural mode', () => {
49
+ it('on <div> host: role="separator" and no aria-hidden', async () => {
50
+ const { container } = await render(
51
+ `<div kjDivider [kjStructural]="true"></div>`,
52
+ { imports: [KjDivider] },
53
+ );
54
+ await flushAfterNextRender();
55
+ const host = container.querySelector('div')!;
56
+ expect(host).toHaveAttribute('role', 'separator');
57
+ expect(host).not.toHaveAttribute('aria-hidden');
58
+ });
59
+
60
+ it('on <hr> host: relies on implicit role (no explicit role attr) and no aria-hidden', async () => {
61
+ const { container } = await render(
62
+ `<hr kjDivider [kjStructural]="true" />`,
63
+ { imports: [KjDivider] },
64
+ );
65
+ await flushAfterNextRender();
66
+ const host = container.querySelector('hr')!;
67
+ // <hr> has implicit role="separator" per HTML AAM; the directive must
68
+ // NOT set role explicitly (would be redundant noise).
69
+ expect(host).not.toHaveAttribute('role');
70
+ expect(host).not.toHaveAttribute('aria-hidden');
71
+ });
72
+
73
+ it('vertical + structural: aria-orientation="vertical"', async () => {
74
+ const { container } = await render(
75
+ `<div kjDivider [kjStructural]="true" [kjOrientation]="'vertical'"></div>`,
76
+ { imports: [KjDivider] },
77
+ );
78
+ await flushAfterNextRender();
79
+ const host = container.querySelector('div')!;
80
+ expect(host).toHaveAttribute('aria-orientation', 'vertical');
81
+ expect(host).toHaveAttribute('role', 'separator');
82
+ });
83
+
84
+ it('horizontal + structural: omits aria-orientation (spec default)', async () => {
85
+ const { container } = await render(
86
+ `<div kjDivider [kjStructural]="true" [kjOrientation]="'horizontal'"></div>`,
87
+ { imports: [KjDivider] },
88
+ );
89
+ await flushAfterNextRender();
90
+ const host = container.querySelector('div')!;
91
+ expect(host).not.toHaveAttribute('aria-orientation');
92
+ });
93
+ });
94
+
95
+ describe('orientation reflection', () => {
96
+ it('reflects data-orientation="horizontal" by default', async () => {
97
+ const { container } = await render(`<hr kjDivider />`, { imports: [KjDivider] });
98
+ await flushAfterNextRender();
99
+ expect(container.querySelector('hr')).toHaveAttribute('data-orientation', 'horizontal');
100
+ });
101
+
102
+ it('reflects data-orientation="vertical" when kjOrientation="vertical"', async () => {
103
+ const { container } = await render(
104
+ `<hr kjDivider [kjOrientation]="'vertical'" />`,
105
+ { imports: [KjDivider] },
106
+ );
107
+ await flushAfterNextRender();
108
+ expect(container.querySelector('hr')).toHaveAttribute('data-orientation', 'vertical');
109
+ });
110
+ });
111
+
112
+ describe('with-content auto-detection', () => {
113
+ it('rule-only on <hr>: data-with-content="false"', async () => {
114
+ const { container } = await render(`<hr kjDivider />`, { imports: [KjDivider] });
115
+ await flushAfterNextRender();
116
+ expect(container.querySelector('hr')).toHaveAttribute('data-with-content', 'false');
117
+ });
118
+
119
+ it('empty <div>: data-with-content="false"', async () => {
120
+ const { container } = await render(`<div kjDivider></div>`, { imports: [KjDivider] });
121
+ await flushAfterNextRender();
122
+ expect(container.querySelector('div')).toHaveAttribute('data-with-content', 'false');
123
+ });
124
+
125
+ it('<div> with text content: data-with-content="true"', async () => {
126
+ const { container } = await render(
127
+ `<div kjDivider>OR</div>`,
128
+ { imports: [KjDivider] },
129
+ );
130
+ await flushAfterNextRender();
131
+ expect(container.querySelector('div')).toHaveAttribute('data-with-content', 'true');
132
+ });
133
+
134
+ it('<div> with element content: data-with-content="true"', async () => {
135
+ const { container } = await render(
136
+ `<div kjDivider><span>OR</span></div>`,
137
+ { imports: [KjDivider] },
138
+ );
139
+ await flushAfterNextRender();
140
+ expect(container.querySelector('div[kjDivider]')).toHaveAttribute(
141
+ 'data-with-content',
142
+ 'true',
143
+ );
144
+ });
145
+
146
+ it('<div> with whitespace-only content: data-with-content="false"', async () => {
147
+ const { container } = await render(
148
+ `<div kjDivider> </div>`,
149
+ { imports: [KjDivider] },
150
+ );
151
+ await flushAfterNextRender();
152
+ expect(container.querySelector('div')).toHaveAttribute('data-with-content', 'false');
153
+ });
154
+ });
155
+
156
+ describe('align reflection', () => {
157
+ it('defaults to data-align="center"', async () => {
158
+ const { container } = await render(`<hr kjDivider />`, { imports: [KjDivider] });
159
+ await flushAfterNextRender();
160
+ expect(container.querySelector('hr')).toHaveAttribute('data-align', 'center');
161
+ });
162
+
163
+ it('reflects kjAlign to data-align', async () => {
164
+ const { container } = await render(
165
+ `<div kjDivider [kjAlign]="'start'">Today</div>`,
166
+ { imports: [KjDivider] },
167
+ );
168
+ await flushAfterNextRender();
169
+ expect(container.querySelector('div')).toHaveAttribute('data-align', 'start');
170
+ });
171
+ });
172
+
173
+ describe('variant + size composition', () => {
174
+ it('reflects default data-variant from KjVariant preset', async () => {
175
+ const { container } = await render(`<hr kjDivider />`, { imports: [KjDivider] });
176
+ await flushAfterNextRender();
177
+ // KjVariant default factory: { default: 'default' }
178
+ expect(container.querySelector('hr')).toHaveAttribute('data-variant', 'default');
179
+ });
180
+
181
+ it('reflects default data-size from KjSize preset', async () => {
182
+ const { container } = await render(`<hr kjDivider />`, { imports: [KjDivider] });
183
+ await flushAfterNextRender();
184
+ // KjSize default factory: { default: 'md' }
185
+ expect(container.querySelector('hr')).toHaveAttribute('data-size', 'md');
186
+ });
187
+
188
+ it('forwards kjVariant input (composed via hostDirectives)', async () => {
189
+ const { container } = await render(
190
+ `<hr kjDivider [kjVariant]="'dashed'" />`,
191
+ { imports: [KjDivider] },
192
+ );
193
+ await flushAfterNextRender();
194
+ expect(container.querySelector('hr')).toHaveAttribute('data-variant', 'dashed');
195
+ });
196
+
197
+ it('forwards kjSize input (composed via hostDirectives)', async () => {
198
+ const { container } = await render(
199
+ `<hr kjDivider [kjSize]="'lg'" />`,
200
+ { imports: [KjDivider] },
201
+ );
202
+ await flushAfterNextRender();
203
+ expect(container.querySelector('hr')).toHaveAttribute('data-size', 'lg');
204
+ });
205
+ });
206
+
207
+ describe('a11y audits', () => {
208
+ it('decorative <hr> divider passes axe', async () => {
209
+ const { container } = await render(`<hr kjDivider />`, { imports: [KjDivider] });
210
+ await flushAfterNextRender();
211
+ expect(await axe(container)).toHaveNoViolations();
212
+ });
213
+
214
+ it('structural with-content <div> divider passes axe', async () => {
215
+ const { container } = await render(
216
+ `<div kjDivider [kjStructural]="true">OR</div>`,
217
+ { imports: [KjDivider] },
218
+ );
219
+ await flushAfterNextRender();
220
+ expect(await axe(container)).toHaveNoViolations();
221
+ });
222
+ });
223
+ });
@@ -0,0 +1,137 @@
1
+ import {
2
+ Directive,
3
+ ElementRef,
4
+ afterNextRender,
5
+ computed,
6
+ inject,
7
+ input,
8
+ signal,
9
+ } from '@angular/core';
10
+ import { KjSize, KjVariant } from '../presets';
11
+
12
+ /**
13
+ * Orientation of the divider rule.
14
+ */
15
+ export type KjDividerOrientation = 'horizontal' | 'vertical';
16
+
17
+ /**
18
+ * Alignment of the projected content within a with-content divider.
19
+ */
20
+ export type KjDividerAlign = 'start' | 'center' | 'end';
21
+
22
+ /**
23
+ * Marks an element as a kouji divider / separator. Owns the small a11y opinion
24
+ * (decorative-by-default vs. structural) and reflects data attributes that
25
+ * theme CSS reads to render the rule (rule-only vs. with-content layout,
26
+ * orientation, alignment, variant, size).
27
+ *
28
+ * The host element is consumer-supplied — `<hr kjDivider>` for the rule-only
29
+ * case (recommended; native semantic, implicit `role="separator"`) or
30
+ * `<div kjDivider>Title</div>` for the with-content case (label projected in
31
+ * the middle of the rule).
32
+ *
33
+ * **Decorative by default.** Most dividers in real templates are visual
34
+ * punctuation between regions already demarcated by surrounding headings or
35
+ * landmarks, so AT should not announce them. Set `kjStructural=true` when the
36
+ * divider is the only thing demarcating two regions (e.g. an "OR" sign-in
37
+ * divider with no surrounding heading).
38
+ *
39
+ * @example
40
+ * ```html
41
+ * <hr kjDivider />
42
+ * <hr kjDivider [kjStructural]="true" kjOrientation="vertical" />
43
+ * <div kjDivider [kjStructural]="true">OR</div>
44
+ * ```
45
+ * @doc-category Core/Layout/Divider
46
+ * @doc
47
+ * @doc-name divider
48
+ * @doc-description Unstyled divider for separating content with horizontal, vertical, and label-in-rule layouts.
49
+ * @doc-is-main
50
+ */
51
+ @Directive({
52
+ selector: '[kjDivider]',
53
+ standalone: true,
54
+ hostDirectives: [
55
+ { directive: KjVariant, inputs: ['kjVariant'] },
56
+ { directive: KjSize, inputs: ['kjSize'] },
57
+ ],
58
+ host: {
59
+ '[attr.data-orientation]': 'kjOrientation()',
60
+ '[attr.data-align]': 'kjAlign()',
61
+ '[attr.data-with-content]': 'withContent() ? "true" : "false"',
62
+ '[attr.role]': 'roleAttr()',
63
+ '[attr.aria-orientation]': 'ariaOrientationAttr()',
64
+ '[attr.aria-hidden]': 'ariaHiddenAttr()',
65
+ },
66
+ })
67
+ export class KjDivider {
68
+ private readonly el = inject<ElementRef<HTMLElement>>(ElementRef);
69
+
70
+ /**
71
+ * Visual orientation of the rule. Reflects to `data-orientation`. When
72
+ * `kjStructural=true` and orientation is `vertical`, also reflects
73
+ * `aria-orientation="vertical"`. The horizontal case omits `aria-orientation`
74
+ * (the spec default; setting it explicitly is redundant noise).
75
+ */
76
+ readonly kjOrientation = input<KjDividerOrientation>('horizontal');
77
+
78
+ /**
79
+ * Whether the divider carries semantic weight (separates two regions that
80
+ * are not already demarcated by surrounding heading or landmark structure).
81
+ *
82
+ * - `false` (default): host gets `aria-hidden="true"`; AT skips the divider.
83
+ * - `true`: host gets `role="separator"` (suppressed if host is `<hr>`,
84
+ * which has implicit role); when `kjOrientation='vertical'`, also gets
85
+ * `aria-orientation="vertical"`.
86
+ */
87
+ readonly kjStructural = input<boolean>(false);
88
+
89
+ /**
90
+ * Alignment of the projected content within a with-content divider.
91
+ * Affects the with-content layout only; ignored when no content is present.
92
+ * Reflects to `data-align`.
93
+ */
94
+ readonly kjAlign = input<KjDividerAlign>('center');
95
+
96
+ /** Auto-detected from host child nodes (any text or element child). */
97
+ protected readonly withContent = signal<boolean>(false);
98
+
99
+ /** True when the host element is an `<hr>` (case-insensitive). */
100
+ protected readonly isHr = signal<boolean>(false);
101
+
102
+ protected readonly roleAttr = computed(() =>
103
+ this.kjStructural() && !this.isHr() ? 'separator' : null,
104
+ );
105
+
106
+ protected readonly ariaOrientationAttr = computed(() =>
107
+ this.kjStructural() && this.kjOrientation() === 'vertical' ? 'vertical' : null,
108
+ );
109
+
110
+ protected readonly ariaHiddenAttr = computed(() =>
111
+ this.kjStructural() ? null : 'true',
112
+ );
113
+
114
+ constructor() {
115
+ afterNextRender(() => {
116
+ const host = this.el.nativeElement;
117
+ this.isHr.set(host.tagName?.toLowerCase() === 'hr');
118
+ // Detect with-content from any non-empty child nodes (text or elements).
119
+ // Treat whitespace-only text nodes as no content so a multi-line template
120
+ // with a self-closing-style divider doesn't accidentally light up.
121
+ this.withContent.set(hasMeaningfulChildNodes(host));
122
+ });
123
+ }
124
+ }
125
+
126
+ function hasMeaningfulChildNodes(host: HTMLElement): boolean {
127
+ const nodes = host.childNodes;
128
+ for (let i = 0; i < nodes.length; i++) {
129
+ const node = nodes[i];
130
+ if (node.nodeType === Node.ELEMENT_NODE) return true;
131
+ if (node.nodeType === Node.TEXT_NODE) {
132
+ const text = node.nodeValue ?? '';
133
+ if (text.trim().length > 0) return true;
134
+ }
135
+ }
136
+ return false;
137
+ }
@@ -0,0 +1,5 @@
1
+ export {
2
+ KjDivider,
3
+ type KjDividerAlign,
4
+ type KjDividerOrientation,
5
+ } from './divider';
@@ -0,0 +1,63 @@
1
+ import { Subject, type Observable } from 'rxjs';
2
+ import type { Signal } from '@angular/core';
3
+ import type { KjOverlayController } from '../primitives/overlay/controller';
4
+
5
+ /**
6
+ * Reference returned by `KjDrawer.open()`. Mirrors {@link import('../dialog/dialog.ref').KjDialogRef}.
7
+ *
8
+ * Use `close(result?)` to dismiss the drawer programmatically. Subscribe to
9
+ * `afterClosed$` for the close result, or await the `result` promise.
10
+ *
11
+ * @doc-category Core/Overlay
12
+ */
13
+ export class KjDrawerRef<T, R = unknown> {
14
+ private _instance: T | null = null;
15
+ private _result: R | undefined;
16
+ private resolveResult!: (r: R | undefined) => void;
17
+ /** Promise resolving with the close result. */
18
+ readonly result: Promise<R | undefined>;
19
+
20
+ private readonly _afterOpened = new Subject<void>();
21
+ private readonly _afterClosed = new Subject<R | undefined>();
22
+ /** Emits once after the drawer has finished opening. */
23
+ readonly afterOpened$: Observable<void> = this._afterOpened.asObservable();
24
+ /** Emits the close result once the drawer has finished closing. */
25
+ readonly afterClosed$: Observable<R | undefined> = this._afterClosed.asObservable();
26
+
27
+ /** Reactive lifecycle state mirrored from the underlying controller. */
28
+ readonly state: Signal<'closed' | 'opening' | 'open' | 'closing'>;
29
+ /** Convenience for `state() === 'open' || 'opening'`. */
30
+ readonly isOpen: Signal<boolean>;
31
+
32
+ constructor(public readonly controller: KjOverlayController) {
33
+ this.state = controller.state;
34
+ this.isOpen = controller.isOpen;
35
+ this.result = new Promise<R | undefined>((res) => {
36
+ this.resolveResult = res;
37
+ });
38
+ }
39
+
40
+ /** @internal Bind the rendered component instance for `instance`. */
41
+ bindInstance(instance: T): void {
42
+ this._instance = instance;
43
+ }
44
+
45
+ /** The rendered drawer component instance. */
46
+ get instance(): T {
47
+ if (!this._instance) {
48
+ throw new Error('KjDrawerRef: instance not bound');
49
+ }
50
+ return this._instance;
51
+ }
52
+
53
+ /** Close the drawer with an optional result payload. */
54
+ close(result?: R): void {
55
+ this._result = result;
56
+ this.controller.close('programmatic');
57
+ queueMicrotask(() => {
58
+ this._afterClosed.next(this._result);
59
+ this._afterClosed.complete();
60
+ this.resolveResult(this._result);
61
+ });
62
+ }
63
+ }
@@ -0,0 +1,106 @@
1
+ import {
2
+ EnvironmentInjector,
3
+ Injectable,
4
+ InjectionToken,
5
+ Type,
6
+ effect,
7
+ inject,
8
+ runInInjectionContext,
9
+ } from '@angular/core';
10
+ import { KjOverlayBuilder } from '../primitives/overlay/builder';
11
+ import { inPlace } from '../primitives/overlay/strategies/mount/in-place';
12
+ import { edgeSheet } from '../primitives/overlay/strategies/position/edge-sheet';
13
+ import { solidBackdrop } from '../primitives/overlay/strategies/backdrop/solid';
14
+ import { tabCycle } from '../primitives/overlay/strategies/focus-trap/tab-cycle';
15
+ import { htmlOverflow } from '../primitives/overlay/strategies/scroll-lock/html-overflow';
16
+ import { silent } from '../primitives/overlay/strategies/live-announcer/silent';
17
+ import { programmatic } from '../primitives/overlay/strategies/trigger-event/programmatic';
18
+ import { KjDrawerRef } from './drawer.ref';
19
+
20
+ /** Side of the viewport the drawer slides in from. */
21
+ export type KjDrawerSide = 'left' | 'right' | 'top' | 'bottom';
22
+
23
+ /** Configuration options for {@link KjDrawer.open}. */
24
+ export interface KjDrawerOpenOptions<D = unknown> {
25
+ /** Side of the viewport the drawer slides in from. Defaults to `'right'`. */
26
+ side?: KjDrawerSide;
27
+ /** Enables drag-to-dismiss when the body component implements it. */
28
+ drag?: boolean;
29
+ /** Data injected into the rendered component via `DRAWER_DATA` (when wired). */
30
+ data?: D;
31
+ /** Whether clicking the backdrop closes the drawer. Defaults to `true`. */
32
+ closeOnOutside?: boolean;
33
+ }
34
+
35
+ /**
36
+ * Programmatic service for opening drawers. Mirrors `KjDialog`. Builds an
37
+ * overlay handle wired to the overlay primitives and renders a component
38
+ * inside the singleton overlay container, positioned via `edgeSheet({side})`.
39
+ *
40
+ * @doc-category Core/Overlay
41
+ */
42
+ @Injectable({ providedIn: 'root' })
43
+ export class KjDrawerService {
44
+ private readonly builder = inject(KjOverlayBuilder);
45
+ private readonly env = inject(EnvironmentInjector);
46
+
47
+ /**
48
+ * Open a component as a modal drawer.
49
+ *
50
+ * @param component - The component class to render inside the drawer.
51
+ * @param opts - Side, drag, data, and close-on-outside controls.
52
+ * @returns A {@link KjDrawerRef} for closing and observing the drawer.
53
+ */
54
+ open<T, R = unknown, D = unknown>(
55
+ component: Type<T>,
56
+ opts: KjDrawerOpenOptions<D> = {},
57
+ ): KjDrawerRef<T, R> {
58
+ const side: KjDrawerSide = opts.side ?? 'right';
59
+
60
+ const handle = this.builder.create({
61
+ mount: inPlace(),
62
+ position: edgeSheet({ side }),
63
+ backdrop: solidBackdrop({
64
+ inert: true,
65
+ closeOnClick: opts.closeOnOutside ?? true,
66
+ }),
67
+ focusTrap: tabCycle({ returnFocus: true }),
68
+ scrollLock: htmlOverflow(),
69
+ liveAnnouncer: silent(),
70
+ trigger: programmatic(),
71
+ panelRole: 'dialog',
72
+ });
73
+
74
+ const ref = new KjDrawerRef<T, R>(handle.controller);
75
+ const cmpRef = this.builder.attachComponent(handle, component, {
76
+ providers: [
77
+ { provide: KjDrawerRef, useValue: ref },
78
+ { provide: DRAWER_DATA, useValue: opts.data },
79
+ { provide: DRAWER_SIDE, useValue: side },
80
+ { provide: DRAWER_DRAG, useValue: !!opts.drag },
81
+ ],
82
+ });
83
+ ref.bindInstance(cmpRef.instance);
84
+
85
+ runInInjectionContext(this.env, () => {
86
+ let wasOpen = false;
87
+ const eff = effect(() => {
88
+ const s = handle.controller.state();
89
+ if (s === 'open' || s === 'opening') wasOpen = true;
90
+ if (s === 'closed' && wasOpen) {
91
+ eff.destroy();
92
+ queueMicrotask(() => handle.destroy());
93
+ }
94
+ });
95
+ });
96
+ handle.controller.open();
97
+ return ref;
98
+ }
99
+ }
100
+
101
+ /** Token for passing data to a programmatically opened drawer component. */
102
+ export const DRAWER_DATA = new InjectionToken<unknown>('KjDrawerData');
103
+ /** Token exposing the resolved drawer side to the rendered component. */
104
+ export const DRAWER_SIDE = new InjectionToken<KjDrawerSide>('KjDrawerSide');
105
+ /** Token exposing whether drag-to-dismiss is enabled to the rendered component. */
106
+ export const DRAWER_DRAG = new InjectionToken<boolean>('KjDrawerDrag');
@@ -0,0 +1,117 @@
1
+ import { Component, inject, ChangeDetectionStrategy } from '@angular/core';
2
+ import { TestBed } from '@angular/core/testing';
3
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
4
+
5
+ import { KjDrawer } from './drawer';
6
+ import { KjDrawerService, DRAWER_DATA } from './drawer.service';
7
+ import { KjDrawerRef } from './drawer.ref';
8
+
9
+ @Component({
10
+ selector: 'kj-simple-drawer',
11
+ standalone: true,
12
+ imports: [KjDrawer],
13
+ changeDetection: ChangeDetectionStrategy.Eager,
14
+ template: `<kj-drawer><button id="ok" (click)="ref.close('ok')">OK</button></kj-drawer>`,
15
+ })
16
+ class SimpleDrawer {
17
+ readonly ref = inject<KjDrawerRef<SimpleDrawer, string>>(KjDrawerRef);
18
+ }
19
+
20
+ @Component({
21
+ selector: 'kj-data-drawer',
22
+ standalone: true,
23
+ imports: [KjDrawer],
24
+ changeDetection: ChangeDetectionStrategy.Eager,
25
+ template: `<kj-drawer>{{ data }}</kj-drawer>`,
26
+ })
27
+ class DataDrawer {
28
+ readonly data = inject<string>(DRAWER_DATA);
29
+ }
30
+
31
+ function findPanel(): HTMLElement | null {
32
+ return document.body.querySelector<HTMLElement>('kj-drawer');
33
+ }
34
+
35
+ function cleanupOverlays(): void {
36
+ document.body.querySelectorAll('kj-drawer').forEach((el) => el.remove());
37
+ document.documentElement.style.overflow = '';
38
+ document.documentElement.style.paddingRight = '';
39
+ }
40
+
41
+ describe('KjDrawerService', () => {
42
+ beforeEach(() => {
43
+ TestBed.resetTestingModule();
44
+ TestBed.configureTestingModule({});
45
+ });
46
+
47
+ afterEach(() => {
48
+ cleanupOverlays();
49
+ });
50
+
51
+ it('open returns a ref with a controller', () => {
52
+ const svc = TestBed.inject(KjDrawerService);
53
+ const ref = svc.open(SimpleDrawer);
54
+ expect(ref).toBeTruthy();
55
+ expect(typeof ref.close).toBe('function');
56
+ expect(ref.controller).toBeTruthy();
57
+ });
58
+
59
+ it('rendered panel uses role="dialog"', () => {
60
+ const svc = TestBed.inject(KjDrawerService);
61
+ svc.open(SimpleDrawer);
62
+ const panel = findPanel();
63
+ expect(panel).toBeTruthy();
64
+ expect(panel!.getAttribute('role')).toBe('dialog');
65
+ });
66
+
67
+ it('side option is reflected on the host as data-kj-side', () => {
68
+ const svc = TestBed.inject(KjDrawerService);
69
+ svc.open(SimpleDrawer, { side: 'left' });
70
+ expect(findPanel()!.getAttribute('data-kj-side')).toBe('left');
71
+ });
72
+
73
+ it('defaults side to "right"', () => {
74
+ const svc = TestBed.inject(KjDrawerService);
75
+ svc.open(SimpleDrawer);
76
+ expect(findPanel()!.getAttribute('data-kj-side')).toBe('right');
77
+ });
78
+
79
+ it('close resolves the result promise', async () => {
80
+ const svc = TestBed.inject(KjDrawerService);
81
+ const ref = svc.open<SimpleDrawer, string>(SimpleDrawer);
82
+ ref.close('hello');
83
+ await expect(ref.result).resolves.toBe('hello');
84
+ });
85
+
86
+ it('passes data through DRAWER_DATA', () => {
87
+ const svc = TestBed.inject(KjDrawerService);
88
+ svc.open(DataDrawer, { data: 'greetings' });
89
+ expect(findPanel()!.textContent).toContain('greetings');
90
+ });
91
+
92
+ it('drag option is exposed to the body component', () => {
93
+ const svc = TestBed.inject(KjDrawerService);
94
+ const ref = svc.open(SimpleDrawer, { side: 'bottom', drag: true });
95
+ expect(ref.instance).toBeInstanceOf(SimpleDrawer);
96
+ });
97
+
98
+ it('Escape closes the drawer via the overlay-stack coordinator', async () => {
99
+ const svc = TestBed.inject(KjDrawerService);
100
+ const ref = svc.open<SimpleDrawer, unknown>(SimpleDrawer);
101
+ expect(findPanel()).toBeTruthy();
102
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));
103
+ // The stack coordinator runs synchronously on Esc.
104
+ await ref.result;
105
+ // After close, the controller state should have transitioned away from open.
106
+ expect(ref.state()).not.toBe('open');
107
+ });
108
+ });
109
+
110
+ describe('KjDrawer (body component)', () => {
111
+ it('exposes the resolved side through `side`', () => {
112
+ const svc = TestBed.inject(KjDrawerService);
113
+ const ref = svc.open(SimpleDrawer, { side: 'top' });
114
+ expect(ref.instance as unknown as { ref: KjDrawerRef<unknown> }).toBeTruthy();
115
+ cleanupOverlays();
116
+ });
117
+ });