@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,919 @@
1
+ import {
2
+ Directive,
3
+ ElementRef,
4
+ OnDestroy,
5
+ OnInit,
6
+ PLATFORM_ID,
7
+ Signal,
8
+ afterNextRender,
9
+ booleanAttribute,
10
+ computed,
11
+ effect,
12
+ inject,
13
+ input,
14
+ model,
15
+ signal,
16
+ untracked,
17
+ } from '@angular/core';
18
+ import { isPlatformBrowser } from '@angular/common';
19
+ import { KjFocusRing } from '../primitives/interaction/focus-ring';
20
+ import { KjDisabled } from '../primitives/interaction/disabled';
21
+ import { KjRovingTabindex, KjRovingTabindexItemDirective } from '../a11y/roving-tabindex';
22
+ import {
23
+ KJ_CAROUSEL,
24
+ KJ_CAROUSEL_INDICATORS,
25
+ KJ_CAROUSEL_SLIDE,
26
+ KjCarouselAlign,
27
+ KjCarouselContext,
28
+ KjCarouselControlPattern,
29
+ KjCarouselIndicatorsContext,
30
+ KjCarouselOrientation,
31
+ KjCarouselSlideContext,
32
+ } from './carousel.context';
33
+
34
+ let kjCarouselSeedCounter = 0;
35
+ function nextSeed(): string {
36
+ const cryptoLike = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;
37
+ if (cryptoLike?.randomUUID) return cryptoLike.randomUUID().slice(0, 8);
38
+ return `kj${(++kjCarouselSeedCounter).toString(36)}`;
39
+ }
40
+
41
+ function prefersReducedMotion(): boolean {
42
+ if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return false;
43
+ try {
44
+ return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
45
+ } catch {
46
+ return false;
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Root carousel directive — region landmark, source of truth for the active
52
+ * slide, and orchestrator for orientation, loop semantics, autoplay gating,
53
+ * and the live-region announcement cadence.
54
+ *
55
+ * The directive itself owns no DOM beyond the host element; it sits at the
56
+ * outermost element of the carousel composition (the one wearing
57
+ * `role="region"` + `aria-roledescription="carousel"`) and exposes
58
+ * `KJ_CAROUSEL` to descendants for the per-slide ARIA wiring, the
59
+ * previous / next / indicator controls, and the autoplay / pause directives.
60
+ *
61
+ * @example
62
+ * ```html
63
+ * <div kjCarousel kjLabel="Featured products" [(kjValue)]="active">
64
+ * <button kjCarouselPrevious aria-label="Previous slide">‹</button>
65
+ * <div kjCarouselViewport>
66
+ * <div kjCarouselSlide kjSlideValue="a">…</div>
67
+ * <div kjCarouselSlide kjSlideValue="b">…</div>
68
+ * </div>
69
+ * <button kjCarouselNext aria-label="Next slide">›</button>
70
+ * <div kjCarouselIndicators></div>
71
+ * </div>
72
+ * ```
73
+ * @doc-category Core/Data display
74
+ * @doc
75
+ * @doc-name carousel
76
+ * @doc-description Unstyled carousel root that owns slide state, autoplay, loop semantics, and ARIA announcements.
77
+ * @doc-is-main
78
+ */
79
+ @Directive({
80
+ selector: '[kjCarousel]',
81
+ standalone: true,
82
+ exportAs: 'kjCarousel',
83
+ providers: [{ provide: KJ_CAROUSEL, useExisting: KjCarousel }],
84
+ host: {
85
+ '[attr.role]': '"region"',
86
+ '[attr.aria-roledescription]': '"carousel"',
87
+ '[attr.aria-label]': 'kjLabel() ?? null',
88
+ '[attr.aria-labelledby]': 'kjLabelledby() ?? null',
89
+ '[attr.aria-orientation]': 'kjOrientation()',
90
+ '[attr.data-paused]': 'paused() ? "" : null',
91
+ '[attr.data-autoplay]': 'autoplayActive() ? "" : null',
92
+ '[attr.data-orientation]': 'kjOrientation()',
93
+ },
94
+ })
95
+ export class KjCarousel implements KjCarouselContext {
96
+ private readonly platformId = inject(PLATFORM_ID);
97
+ private readonly idSeed = nextSeed();
98
+
99
+ /** Two-way bound active slide value. `null` defers to the first registered slide on mount. */
100
+ readonly kjValue = model<string | null>(null);
101
+
102
+ /** When true, prev / next wrap around. Boundary buttons go disabled when false. */
103
+ readonly kjLoop = input(false, { transform: booleanAttribute });
104
+
105
+ /** Carousel orientation. Drives the scroll-snap axis and the keyboard mapping. */
106
+ readonly kjOrientation = input<KjCarouselOrientation>('horizontal');
107
+
108
+ /** Number of slides visible in the viewport at once. `'auto'` defers to slide content widths. */
109
+ readonly kjSlidesPerView = input<number | 'auto'>(1);
110
+
111
+ /** Number of slides moved per `next()` / `prev()` call. Independent of slides-per-view. */
112
+ readonly kjSlidesPerAdvance = input<number>(1);
113
+
114
+ /** Default slide alignment within the viewport. Per-slide override via `KjCarouselSlide.kjSlideAlign`. */
115
+ readonly kjAlign = input<KjCarouselAlign>('start');
116
+
117
+ /** Accessible name for the region. Either this or `kjLabelledby` is required for WCAG 4.1.2. */
118
+ readonly kjLabel = input<string | undefined>(undefined);
119
+
120
+ /** Id of an external label element. Either this or `kjLabel` is required. */
121
+ readonly kjLabelledby = input<string | undefined>(undefined);
122
+
123
+ /** When false, native scroll drag is blocked on the viewport (programmatic advance still works). */
124
+ readonly kjDraggable = input(true, { transform: booleanAttribute });
125
+
126
+ /** Read-only mirror of the orientation for context consumers. */
127
+ readonly orientation: Signal<KjCarouselOrientation> = this.kjOrientation;
128
+
129
+ /** Read-only mirror of the loop flag for context consumers. */
130
+ readonly loop: Signal<boolean> = this.kjLoop;
131
+
132
+ // ── Slide registration ────────────────────────────────────────────────
133
+ private readonly _slides = signal<readonly KjCarouselSlide[]>([]);
134
+ /** Public read-only registration list, in DOM order. */
135
+ readonly slides = this._slides.asReadonly();
136
+
137
+ /** Total count of registered slides (excluding loop clones, which are DOM-only). */
138
+ readonly slideCount = computed(() => this._slides().length);
139
+
140
+ /** Current value, normalised to `null` when the registered set does not contain it. */
141
+ readonly currentValue = computed<string | null>(() => {
142
+ const v = this.kjValue();
143
+ if (v == null) return null;
144
+ return this._slides().some((s) => s.kjSlideValue() === v) ? v : null;
145
+ });
146
+
147
+ /** Index of the active slide in registration order; -1 when nothing is active. */
148
+ readonly currentIndex = computed(() => {
149
+ const v = this.currentValue();
150
+ if (v == null) return -1;
151
+ return this._slides().findIndex((s) => s.kjSlideValue() === v);
152
+ });
153
+
154
+ // ── Autoplay gating ───────────────────────────────────────────────────
155
+ private readonly _userPaused = signal(false);
156
+ private readonly _hovering = signal(false);
157
+ private readonly _focusWithin = signal(false);
158
+ private readonly _autoplay = signal<KjCarouselAutoplay | null>(null);
159
+ private readonly _liveRegion = signal<{ announce: (msg: string) => void } | null>(null);
160
+
161
+ /** True only when the user has explicitly paused (gate 3). */
162
+ readonly paused: Signal<boolean> = this._userPaused.asReadonly();
163
+
164
+ /** Whether an autoplay directive is registered. */
165
+ readonly autoplayRegistered = computed(() => this._autoplay() != null);
166
+
167
+ /** Whether autoplay should currently be ticking (all five gates open). */
168
+ readonly autoplayActive = computed(() => {
169
+ if (!this._autoplay()) return false;
170
+ if (this._reducedMotion()) return false;
171
+ if (this._userPaused()) return false;
172
+ const ap = this._autoplay()!;
173
+ if (ap.kjPauseOnHover() && this._hovering()) return false;
174
+ if (ap.kjPauseOnFocus() && this._focusWithin()) return false;
175
+ return true;
176
+ });
177
+
178
+ /** Whether `prefers-reduced-motion: reduce` matches in the current environment. */
179
+ private readonly _reducedMotion = signal(false);
180
+
181
+ // ── Viewport reference (set by KjCarouselViewport on mount) ───────────
182
+ private _viewport: KjCarouselViewport | null = null;
183
+
184
+ // ── Indicator-group registration (used by slides to flip role to tabpanel) ─
185
+ private readonly _indicators = signal<KjCarouselIndicators | null>(null);
186
+ /** Read-only mirror of the active control pattern (or `'buttons'` when no indicators are mounted). */
187
+ readonly controlPattern = computed<KjCarouselControlPattern>(
188
+ () => this._indicators()?.controlPattern() ?? 'buttons',
189
+ );
190
+
191
+ constructor() {
192
+ if (isPlatformBrowser(this.platformId)) {
193
+ afterNextRender(() => {
194
+ // Refresh reduced-motion any time after first render; effects gate on it.
195
+ this._reducedMotion.set(prefersReducedMotion());
196
+ const mql = typeof window !== 'undefined' && window.matchMedia
197
+ ? window.matchMedia('(prefers-reduced-motion: reduce)')
198
+ : null;
199
+ if (mql) {
200
+ const onChange = () => this._reducedMotion.set(mql.matches);
201
+ if (typeof mql.addEventListener === 'function') {
202
+ mql.addEventListener('change', onChange);
203
+ }
204
+ }
205
+ });
206
+ }
207
+
208
+ // Default-value reconciliation: when kjValue is null and slides have registered,
209
+ // adopt the first slide.
210
+ effect(() => {
211
+ const list = this._slides();
212
+ const current = this.kjValue();
213
+ if (current == null && list.length > 0) {
214
+ const first = untracked(() => list[0]);
215
+ if (first) {
216
+ queueMicrotask(() => {
217
+ if (this.kjValue() == null) this.kjValue.set(first.kjSlideValue());
218
+ });
219
+ }
220
+ }
221
+ });
222
+
223
+ // Programmatic-advance bridge: when kjValue changes (via next/prev/goTo or
224
+ // an external two-way bind), scroll the viewport to the new slide.
225
+ effect(() => {
226
+ const value = this.currentValue();
227
+ if (value == null) return;
228
+ // Run outside the effect's tracking by deferring to a microtask.
229
+ queueMicrotask(() => this.scrollToValue(value));
230
+ });
231
+ }
232
+
233
+ /** @internal Slide registration. */
234
+ registerSlide(slide: KjCarouselSlide): void {
235
+ this._slides.update((list) => (list.includes(slide) ? list : [...list, slide]));
236
+ }
237
+
238
+ /** @internal Slide un-registration. */
239
+ unregisterSlide(slide: KjCarouselSlide): void {
240
+ this._slides.update((list) => list.filter((s) => s !== slide));
241
+ }
242
+
243
+ /** @internal Viewport association. */
244
+ registerViewport(vp: KjCarouselViewport): void {
245
+ this._viewport = vp;
246
+ }
247
+
248
+ /** @internal Viewport teardown. */
249
+ unregisterViewport(vp: KjCarouselViewport): void {
250
+ if (this._viewport === vp) this._viewport = null;
251
+ }
252
+
253
+ /** @internal Autoplay registration. */
254
+ registerAutoplay(ap: KjCarouselAutoplay): void {
255
+ this._autoplay.set(ap);
256
+ }
257
+
258
+ /** @internal Autoplay teardown. */
259
+ unregisterAutoplay(ap: KjCarouselAutoplay): void {
260
+ if (this._autoplay() === ap) this._autoplay.set(null);
261
+ }
262
+
263
+ /** @internal Indicator-group registration (used by slide role flip). */
264
+ registerIndicators(ind: KjCarouselIndicators): void {
265
+ this._indicators.set(ind);
266
+ }
267
+
268
+ /** @internal Indicator-group teardown. */
269
+ unregisterIndicators(ind: KjCarouselIndicators): void {
270
+ if (this._indicators() === ind) this._indicators.set(null);
271
+ }
272
+
273
+ /** @internal Live-region announcer registration (set by the wrapper). */
274
+ registerLiveRegion(lr: { announce: (msg: string) => void } | null): void {
275
+ this._liveRegion.set(lr);
276
+ }
277
+
278
+ /** @internal Hover/focus state setters used by the host listeners. */
279
+ setHovering(v: boolean): void { this._hovering.set(v); }
280
+ setFocusWithin(v: boolean): void { this._focusWithin.set(v); }
281
+
282
+ /** Whether the slide for `value` is the active one. */
283
+ isActive(value: string): boolean {
284
+ return this.currentValue() === value;
285
+ }
286
+
287
+ /** Stable id for the viewport element (consumed by Previous/Next as `aria-controls`). */
288
+ viewportId(): string {
289
+ return `kj-carousel-viewport-${this.idSeed}`;
290
+ }
291
+
292
+ /** Stable id for the slide identified by `value`. */
293
+ slideId(value: string): string {
294
+ return `kj-carousel-slide-${value}-${this.idSeed}`;
295
+ }
296
+
297
+ /** Imperative: advance to the next slide. No-op at the last slide when `kjLoop` is false. */
298
+ next(): void {
299
+ const list = this._slides();
300
+ if (!list.length) return;
301
+ const advance = Math.max(1, this.kjSlidesPerAdvance());
302
+ const idx = this.currentIndex();
303
+ let nextIdx = idx + advance;
304
+ if (nextIdx >= list.length) {
305
+ if (this.kjLoop()) nextIdx = nextIdx % list.length;
306
+ else nextIdx = list.length - 1;
307
+ }
308
+ if (nextIdx === idx) return;
309
+ this.kjValue.set(list[nextIdx].kjSlideValue());
310
+ }
311
+
312
+ /** Imperative: advance to the previous slide. No-op at the first slide when `kjLoop` is false. */
313
+ prev(): void {
314
+ const list = this._slides();
315
+ if (!list.length) return;
316
+ const advance = Math.max(1, this.kjSlidesPerAdvance());
317
+ const idx = this.currentIndex();
318
+ let nextIdx = idx - advance;
319
+ if (nextIdx < 0) {
320
+ if (this.kjLoop()) nextIdx = ((nextIdx % list.length) + list.length) % list.length;
321
+ else nextIdx = 0;
322
+ }
323
+ if (nextIdx === idx) return;
324
+ this.kjValue.set(list[nextIdx].kjSlideValue());
325
+ }
326
+
327
+ /** Imperative: jump to a slide by value. Silently ignores unknown values. */
328
+ goTo(value: string): void {
329
+ const list = this._slides();
330
+ if (!list.some((s) => s.kjSlideValue() === value)) return;
331
+ if (this.kjValue() !== value) this.kjValue.set(value);
332
+ }
333
+
334
+ /** Imperative: explicitly pause autoplay. Persists across slide changes (gate 3). */
335
+ pause(): void {
336
+ if (!this._userPaused()) {
337
+ this._userPaused.set(true);
338
+ this.announce('Autoplay paused');
339
+ }
340
+ }
341
+
342
+ /** Imperative: explicitly resume autoplay (clears gate 3). */
343
+ play(): void {
344
+ if (this._userPaused()) {
345
+ this._userPaused.set(false);
346
+ this.announce('Autoplay resumed');
347
+ }
348
+ }
349
+
350
+ /** @internal Whether the boundary is reached (used by Previous/Next disabled binding). */
351
+ atStart(): boolean {
352
+ return !this.kjLoop() && this.currentIndex() <= 0;
353
+ }
354
+
355
+ atEnd(): boolean {
356
+ return !this.kjLoop() && this.currentIndex() >= this._slides().length - 1;
357
+ }
358
+
359
+ /** Push a polite announcement (user-initiated changes only — autoplay path skips this). */
360
+ announce(message: string): void {
361
+ const lr = this._liveRegion();
362
+ if (lr) lr.announce(message);
363
+ }
364
+
365
+ /**
366
+ * Build the announcement string for a settled user-initiated advance:
367
+ * `"Slide 3 of 7: <label>"`. Returns `null` if the value is not registered.
368
+ */
369
+ buildSlideAnnouncement(value: string): string | null {
370
+ const list = this._slides();
371
+ const idx = list.findIndex((s) => s.kjSlideValue() === value);
372
+ if (idx === -1) return null;
373
+ const slide = list[idx];
374
+ const label = slide.kjSlideLabel();
375
+ const base = `Slide ${idx + 1} of ${list.length}`;
376
+ return label ? `${base}: ${label}` : base;
377
+ }
378
+
379
+ /** @internal Whether reduced motion is currently in force. */
380
+ prefersReducedMotion(): boolean {
381
+ return this._reducedMotion();
382
+ }
383
+
384
+ /** @internal Programmatic scrolling — invoked from the kjValue effect and from goTo/next/prev callers. */
385
+ private scrollToValue(value: string): void {
386
+ if (!isPlatformBrowser(this.platformId)) return;
387
+ const vp = this._viewport;
388
+ if (!vp) return;
389
+ const slide = this._slides().find((s) => s.kjSlideValue() === value);
390
+ if (!slide) return;
391
+ const slideEl = slide.el.nativeElement;
392
+ const vpEl = vp.el.nativeElement;
393
+ const orientation = this.kjOrientation();
394
+ const reduced = this._reducedMotion();
395
+ const opts: ScrollToOptions = {
396
+ behavior: reduced ? 'auto' : 'smooth',
397
+ };
398
+ if (orientation === 'horizontal') {
399
+ opts.left = slideEl.offsetLeft - vpEl.offsetLeft;
400
+ } else {
401
+ opts.top = slideEl.offsetTop - vpEl.offsetTop;
402
+ }
403
+ if (typeof vpEl.scrollTo === 'function') {
404
+ vpEl.scrollTo(opts);
405
+ }
406
+ }
407
+ }
408
+
409
+ /**
410
+ * Carousel viewport — the scroll container that hosts the slides. Owns
411
+ * `overflow: hidden` (or `auto` when draggable), the scroll-snap axis, and
412
+ * the `IntersectionObserver` that detects which slide has settled into view.
413
+ *
414
+ * Place inside `KjCarousel`. The viewport is *not* a tab stop — the previous
415
+ * / next buttons and indicator dots cover the keyboard advance paths, and a
416
+ * focusable scroll container conflicts with screen-reader virtual-cursor
417
+ * navigation through the projected slide content.
418
+ *
419
+ * @doc-category Core/Data display
420
+ * @doc
421
+ * @doc-name carousel
422
+ */
423
+ @Directive({
424
+ selector: '[kjCarouselViewport]',
425
+ standalone: true,
426
+ exportAs: 'kjCarouselViewport',
427
+ host: {
428
+ '[attr.id]': 'carousel.viewportId()',
429
+ '[attr.data-orientation]': 'carousel.orientation()',
430
+ '[style.--kj-carousel-slides-per-view]': 'slidesPerViewVar()',
431
+ '(mouseenter)': 'carousel.setHovering(true)',
432
+ '(mouseleave)': 'carousel.setHovering(false)',
433
+ '(focusin)': 'carousel.setFocusWithin(true)',
434
+ '(focusout)': 'onFocusOut($event)',
435
+ },
436
+ })
437
+ export class KjCarouselViewport implements OnInit, OnDestroy {
438
+ private readonly platformId = inject(PLATFORM_ID);
439
+ /** @internal */
440
+ readonly carousel = inject(KJ_CAROUSEL) as KjCarousel;
441
+ /** @internal Native host element. */
442
+ readonly el = inject<ElementRef<HTMLElement>>(ElementRef);
443
+
444
+ private observer: IntersectionObserver | null = null;
445
+
446
+ /** @internal Computed style variable feed for the slide flex-basis. */
447
+ protected slidesPerViewVar(): string {
448
+ const v = this.carousel.kjSlidesPerView();
449
+ return v === 'auto' ? 'auto' : String(v);
450
+ }
451
+
452
+ ngOnInit(): void {
453
+ this.carousel.registerViewport(this);
454
+ if (isPlatformBrowser(this.platformId) && typeof IntersectionObserver !== 'undefined') {
455
+ this.observer = new IntersectionObserver(
456
+ (entries) => this.onIntersect(entries),
457
+ { root: this.el.nativeElement, threshold: [0.6] },
458
+ );
459
+ // Defer slide observation to the next microtask so registered slides have host elements.
460
+ queueMicrotask(() => this.observeSlides());
461
+ }
462
+ }
463
+
464
+ ngOnDestroy(): void {
465
+ this.observer?.disconnect();
466
+ this.observer = null;
467
+ this.carousel.unregisterViewport(this);
468
+ }
469
+
470
+ /** @internal */
471
+ observeSlides(): void {
472
+ if (!this.observer) return;
473
+ for (const slide of this.carousel.slides()) {
474
+ this.observer.observe(slide.el.nativeElement);
475
+ }
476
+ }
477
+
478
+ /** @internal */
479
+ onFocusOut(event: FocusEvent): void {
480
+ const next = event.relatedTarget as Node | null;
481
+ if (!next || !this.el.nativeElement.contains(next)) {
482
+ // Inspect the carousel root via the viewport's parentage — the host is
483
+ // somewhere up the tree. If focus left the viewport but stayed within
484
+ // the broader carousel root, leave focusWithin true; otherwise clear.
485
+ // Conservative behaviour: clear on viewport blur — gate 5 only matters
486
+ // for autoplay, and any subsequent focusin will re-set it.
487
+ this.carousel.setFocusWithin(false);
488
+ }
489
+ }
490
+
491
+ private lastBest: { ratio: number; value: string } | null = null;
492
+ private settleTimer: ReturnType<typeof setTimeout> | null = null;
493
+
494
+ private onIntersect(entries: IntersectionObserverEntry[]): void {
495
+ // Pick the entry with the highest ratio that crossed the threshold.
496
+ let best: { ratio: number; el: Element } | null = null;
497
+ for (const entry of entries) {
498
+ if (!entry.isIntersecting) continue;
499
+ if (!best || entry.intersectionRatio > best.ratio) {
500
+ best = { ratio: entry.intersectionRatio, el: entry.target };
501
+ }
502
+ }
503
+ if (!best) return;
504
+ const slide = this.carousel.slides().find((s) => s.el.nativeElement === best!.el);
505
+ if (!slide) return;
506
+ const value = slide.kjSlideValue();
507
+ if (this.carousel.currentValue() === value) return;
508
+
509
+ // Debounce settle to avoid flicker when two slides cross 0.6 in the same frame.
510
+ if (this.settleTimer) clearTimeout(this.settleTimer);
511
+ this.settleTimer = setTimeout(() => {
512
+ // Only commit if this is still the best; in vertical-scroll cases the
513
+ // browser may emit several entries before settling.
514
+ const list = this.carousel.slides();
515
+ if (!list.some((s) => s.kjSlideValue() === value)) return;
516
+ const previous = this.carousel.currentValue();
517
+ this.carousel.kjValue.set(value);
518
+ // Announce only when the carousel is not currently auto-playing;
519
+ // autoplay-driven advances are silent per APG to avoid AT flooding.
520
+ if (previous !== null && previous !== value && !this.carousel.autoplayActive()) {
521
+ const msg = this.carousel.buildSlideAnnouncement(value);
522
+ if (msg) this.carousel.announce(msg);
523
+ }
524
+ }, 50);
525
+ }
526
+ }
527
+
528
+ /**
529
+ * Single slide. Hosts `role="group"` + `aria-roledescription="slide"` and
530
+ * `aria-label="N of M[: <label>]"` (or `aria-labelledby` when a projected
531
+ * heading id is supplied via the slide context). Switches to
532
+ * `role="tabpanel"` when the parent indicator group is in
533
+ * `controlPattern="tabs"` mode.
534
+ *
535
+ * @doc-category Core/Data display
536
+ * @doc
537
+ * @doc-name carousel
538
+ */
539
+ @Directive({
540
+ selector: '[kjCarouselSlide]',
541
+ standalone: true,
542
+ exportAs: 'kjCarouselSlide',
543
+ providers: [{ provide: KJ_CAROUSEL_SLIDE, useExisting: KjCarouselSlide }],
544
+ host: {
545
+ '[attr.id]': 'slideLabelId()',
546
+ '[attr.role]': 'role()',
547
+ '[attr.aria-roledescription]': 'role() === "group" ? "slide" : null',
548
+ '[attr.aria-label]': 'ariaLabel()',
549
+ '[attr.data-active]': 'isActive() ? "" : null',
550
+ '[attr.data-index]': 'index()',
551
+ },
552
+ })
553
+ export class KjCarouselSlide implements KjCarouselSlideContext, OnInit, OnDestroy {
554
+ /** @internal */
555
+ readonly carousel = inject(KJ_CAROUSEL) as KjCarousel;
556
+ /** @internal Native host element. */
557
+ readonly el = inject<ElementRef<HTMLElement>>(ElementRef);
558
+
559
+ /** Required string identifier for this slide. */
560
+ readonly kjSlideValue = input.required<string>();
561
+
562
+ /** Optional accessible name appended to `"N of M"` to form the slide's `aria-label`. */
563
+ readonly kjSlideLabel = input<string | undefined>(undefined);
564
+
565
+ /** Per-slide alignment override; defaults to the carousel's `kjAlign`. */
566
+ readonly kjSlideAlign = input<KjCarouselAlign | undefined>(undefined);
567
+
568
+ /** Read-only mirror of the slide value. */
569
+ readonly value: Signal<string> = this.kjSlideValue;
570
+
571
+ /** Index of this slide in registration order. */
572
+ readonly index = computed(() =>
573
+ this.carousel.slides().findIndex((s) => s === this),
574
+ );
575
+
576
+ /** Whether this slide is the currently active one. */
577
+ readonly isActive = computed(() => this.carousel.isActive(this.kjSlideValue()));
578
+
579
+ /** Stable id for the slide host element — sourced from the parent carousel so indicator `aria-controls` stays in sync. */
580
+ readonly slideLabelId = computed(() => this.carousel.slideId(this.kjSlideValue()));
581
+
582
+ /** Effective alignment — per-slide override beats carousel-level default. */
583
+ readonly effectiveAlign = computed<KjCarouselAlign>(
584
+ () => this.kjSlideAlign() ?? this.carousel.kjAlign(),
585
+ );
586
+
587
+ /** @internal Computed role — `'tabpanel'` when an indicators group in `controlPattern="tabs"` is registered, `'group'` otherwise. */
588
+ protected role(): string {
589
+ return this.carousel.controlPattern() === 'tabs' ? 'tabpanel' : 'group';
590
+ }
591
+
592
+ /** @internal Computed aria-label — `"N of M[: <label>]"` when in group mode. */
593
+ protected ariaLabel(): string | null {
594
+ if (this.role() !== 'group') return null;
595
+ const total = this.carousel.slideCount();
596
+ const idx = this.index();
597
+ if (idx < 0) return null;
598
+ const base = `${idx + 1} of ${total}`;
599
+ const label = this.kjSlideLabel();
600
+ return label ? `${base}: ${label}` : base;
601
+ }
602
+
603
+ ngOnInit(): void {
604
+ this.carousel.registerSlide(this);
605
+ }
606
+
607
+ ngOnDestroy(): void {
608
+ this.carousel.unregisterSlide(this);
609
+ }
610
+ }
611
+
612
+ /**
613
+ * Previous-slide control. Composes `KjFocusRing` and `KjDisabled`; when the
614
+ * carousel is not looping and the current slide is the first, the host
615
+ * receives `aria-disabled="true"` and click is a no-op.
616
+ *
617
+ * Apply to a real `<button>` so Enter / Space, focus, and disabled semantics
618
+ * work without extra wiring.
619
+ *
620
+ * @doc-category Core/Data display
621
+ * @doc
622
+ * @doc-name carousel
623
+ */
624
+ @Directive({
625
+ selector: '[kjCarouselPrevious]',
626
+ standalone: true,
627
+ hostDirectives: [KjFocusRing],
628
+ host: {
629
+ '[attr.aria-controls]': 'carousel.viewportId()',
630
+ '[attr.aria-disabled]': 'disabled() ? "true" : null',
631
+ '[attr.data-disabled]': 'disabled() ? "" : null',
632
+ '(click)': 'onClick()',
633
+ },
634
+ })
635
+ export class KjCarouselPrevious {
636
+ /** @internal */
637
+ readonly carousel = inject(KJ_CAROUSEL) as KjCarousel;
638
+
639
+ /** Whether the button is currently at the boundary (and not looping). */
640
+ readonly disabled = computed(() => this.carousel.atStart());
641
+
642
+ /** @internal */
643
+ onClick(): void {
644
+ if (this.disabled()) return;
645
+ const previousValue = this.carousel.currentValue();
646
+ this.carousel.prev();
647
+ const next = this.carousel.currentValue();
648
+ if (next != null && next !== previousValue) {
649
+ const msg = this.carousel.buildSlideAnnouncement(next);
650
+ if (msg) this.carousel.announce(msg);
651
+ }
652
+ }
653
+ }
654
+
655
+ /**
656
+ * Next-slide control. Symmetric counterpart of `KjCarouselPrevious`.
657
+ *
658
+ * @doc-category Core/Data display
659
+ * @doc
660
+ * @doc-name carousel
661
+ */
662
+ @Directive({
663
+ selector: '[kjCarouselNext]',
664
+ standalone: true,
665
+ hostDirectives: [KjFocusRing],
666
+ host: {
667
+ '[attr.aria-controls]': 'carousel.viewportId()',
668
+ '[attr.aria-disabled]': 'disabled() ? "true" : null',
669
+ '[attr.data-disabled]': 'disabled() ? "" : null',
670
+ '(click)': 'onClick()',
671
+ },
672
+ })
673
+ export class KjCarouselNext {
674
+ /** @internal */
675
+ readonly carousel = inject(KJ_CAROUSEL) as KjCarousel;
676
+
677
+ /** Whether the button is currently at the boundary (and not looping). */
678
+ readonly disabled = computed(() => this.carousel.atEnd());
679
+
680
+ /** @internal */
681
+ onClick(): void {
682
+ if (this.disabled()) return;
683
+ const previousValue = this.carousel.currentValue();
684
+ this.carousel.next();
685
+ const next = this.carousel.currentValue();
686
+ if (next != null && next !== previousValue) {
687
+ const msg = this.carousel.buildSlideAnnouncement(next);
688
+ if (msg) this.carousel.announce(msg);
689
+ }
690
+ }
691
+ }
692
+
693
+ /**
694
+ * Indicator-group container. Hosts `role="group"` (default) or
695
+ * `role="tablist"` (`controlPattern="tabs"`), composes `KjRovingTabindex`
696
+ * for arrow-key navigation across the dots, and exposes
697
+ * `KJ_CAROUSEL_INDICATORS` to descendants so individual indicator items can
698
+ * read the active control pattern.
699
+ *
700
+ * @doc-category Core/Data display
701
+ * @doc
702
+ * @doc-name carousel
703
+ */
704
+ @Directive({
705
+ selector: '[kjCarouselIndicators]',
706
+ standalone: true,
707
+ hostDirectives: [KjRovingTabindex],
708
+ providers: [{ provide: KJ_CAROUSEL_INDICATORS, useExisting: KjCarouselIndicators }],
709
+ host: {
710
+ '[attr.role]': 'kjControlPattern() === "tabs" ? "tablist" : "group"',
711
+ '[attr.aria-label]': 'kjAriaLabel()',
712
+ '[attr.aria-orientation]': 'carousel.orientation()',
713
+ '[attr.data-control-pattern]': 'kjControlPattern()',
714
+ },
715
+ })
716
+ export class KjCarouselIndicators implements KjCarouselIndicatorsContext, OnInit, OnDestroy {
717
+ /** @internal */
718
+ readonly carousel = inject(KJ_CAROUSEL) as KjCarousel;
719
+
720
+ /** Active control pattern. `'buttons'` is the APG-recommended default; `'tabs'` opts into the tablist variant. */
721
+ readonly kjControlPattern = input<KjCarouselControlPattern>('buttons');
722
+
723
+ /** Accessible name for the indicator group. */
724
+ readonly kjAriaLabel = input<string>('Slide controls');
725
+
726
+ /** Read-only mirror of the control pattern for the per-indicator host bindings. */
727
+ readonly controlPattern: Signal<KjCarouselControlPattern> = this.kjControlPattern;
728
+
729
+ ngOnInit(): void {
730
+ this.carousel.registerIndicators(this);
731
+ }
732
+
733
+ ngOnDestroy(): void {
734
+ this.carousel.unregisterIndicators(this);
735
+ }
736
+
737
+ /** Activate the slide for `value`. */
738
+ activate(value: string): void {
739
+ this.carousel.goTo(value);
740
+ }
741
+ }
742
+
743
+ /**
744
+ * Single indicator dot. Composes `KjRovingTabindexItem` and `KjFocusRing`,
745
+ * targets the slide whose value matches `kjForValue` on click, and reflects
746
+ * the active state via `aria-current` (buttons mode) or `aria-selected`
747
+ * (tabs mode).
748
+ *
749
+ * @doc-category Core/Data display
750
+ * @doc
751
+ * @doc-name carousel
752
+ */
753
+ @Directive({
754
+ selector: '[kjCarouselIndicator]',
755
+ standalone: true,
756
+ hostDirectives: [KjRovingTabindexItemDirective, KjFocusRing],
757
+ host: {
758
+ '[attr.role]': 'indicators.controlPattern() === "tabs" ? "tab" : null',
759
+ '[attr.aria-current]': 'indicators.controlPattern() === "buttons" && isActive() ? "true" : null',
760
+ '[attr.aria-selected]': 'indicators.controlPattern() === "tabs" ? (isActive() ? "true" : "false") : null',
761
+ '[attr.aria-controls]': 'indicators.controlPattern() === "tabs" ? carousel.slideId(kjForValue()) : null',
762
+ '[attr.data-active]': 'isActive() ? "" : null',
763
+ '(click)': 'onClick()',
764
+ '(keydown)': 'onKeydown($event)',
765
+ },
766
+ })
767
+ export class KjCarouselIndicator {
768
+ /** @internal */
769
+ readonly carousel = inject(KJ_CAROUSEL) as KjCarousel;
770
+ /** @internal */
771
+ readonly indicators = inject(KJ_CAROUSEL_INDICATORS) as KjCarouselIndicators;
772
+
773
+ /** Slide value this indicator targets. */
774
+ readonly kjForValue = input.required<string>();
775
+
776
+ /** Whether this indicator's slide is the active one. */
777
+ readonly isActive = computed(() => this.carousel.isActive(this.kjForValue()));
778
+
779
+ /** @internal */
780
+ onClick(): void {
781
+ const previousValue = this.carousel.currentValue();
782
+ this.indicators.activate(this.kjForValue());
783
+ const next = this.carousel.currentValue();
784
+ if (next != null && next !== previousValue) {
785
+ const msg = this.carousel.buildSlideAnnouncement(next);
786
+ if (msg) this.carousel.announce(msg);
787
+ }
788
+ }
789
+
790
+ /**
791
+ * @internal Tabs-mode keyboard contract: ArrowLeft/Right (or Up/Down in
792
+ * vertical orientation) advance focus *and* activate, per APG tabs. In
793
+ * buttons mode, the composed `KjRovingTabindex` already handles arrow
794
+ * focus; activation requires Enter / Space, which the native `<button>`
795
+ * resolves on its own.
796
+ */
797
+ onKeydown(event: KeyboardEvent): void {
798
+ if (this.indicators.controlPattern() !== 'tabs') return;
799
+ // Roving tabindex moves focus on next tick; piggy-back on the
800
+ // focus event by activating the matching slide via the new active item.
801
+ // We don't preventDefault — KjRovingTabindex needs to consume the arrow.
802
+ if (
803
+ event.key === 'ArrowLeft' || event.key === 'ArrowRight' ||
804
+ event.key === 'ArrowUp' || event.key === 'ArrowDown'
805
+ ) {
806
+ // Defer so the new active item has been focused.
807
+ queueMicrotask(() => {
808
+ const focused = (typeof document !== 'undefined' ? document.activeElement : null) as
809
+ | HTMLElement
810
+ | null;
811
+ if (!focused) return;
812
+ const value = focused.getAttribute('data-kj-indicator-value');
813
+ if (value) this.carousel.goTo(value);
814
+ });
815
+ }
816
+ }
817
+ }
818
+
819
+ /**
820
+ * Autoplay directive — lifecycle-only, no DOM. Manages a `setInterval` that
821
+ * advances the carousel when all five gates are open:
822
+ *
823
+ * 1. The directive is mounted (composition is the on switch).
824
+ * 2. `prefers-reduced-motion: reduce` does not match.
825
+ * 3. The user has not pressed pause (`carousel.paused()` is false).
826
+ * 4. Pointer is not hovering the carousel root, or `kjPauseOnHover` is false.
827
+ * 5. Focus is not within the carousel root, or `kjPauseOnFocus` is false.
828
+ *
829
+ * Per WCAG 2.2.2 a delay greater than 5000 ms requires a pause control to be
830
+ * present in the same composition.
831
+ *
832
+ * @doc-category Core/Data display
833
+ * @doc
834
+ * @doc-name carousel
835
+ */
836
+ @Directive({
837
+ selector: '[kjCarouselAutoplay]',
838
+ standalone: true,
839
+ exportAs: 'kjCarouselAutoplay',
840
+ })
841
+ export class KjCarouselAutoplay implements OnInit, OnDestroy {
842
+ private readonly carousel = inject(KJ_CAROUSEL) as KjCarousel;
843
+
844
+ /** Interval between automatic advances. WCAG 2.2.2 requires a pause control when > 5000 ms. */
845
+ readonly kjAutoplayDelay = input<number>(5000);
846
+
847
+ /** Auto-pause while pointer is over the carousel root. */
848
+ readonly kjPauseOnHover = input(true, { transform: booleanAttribute });
849
+
850
+ /** Auto-pause while focus is within the carousel root. APG-required affordance for keyboard users. */
851
+ readonly kjPauseOnFocus = input(true, { transform: booleanAttribute });
852
+
853
+ private timer: ReturnType<typeof setInterval> | null = null;
854
+
855
+ constructor() {
856
+ effect(() => {
857
+ const active = this.carousel.autoplayActive();
858
+ const delay = this.kjAutoplayDelay();
859
+ this.clearTimer();
860
+ if (active && delay > 0) {
861
+ this.timer = setInterval(() => {
862
+ // Re-check gates inside the timer (may have flipped between fires).
863
+ if (!this.carousel.autoplayActive()) return;
864
+ // Auto-advance is silent — buildSlideAnnouncement is *not* called.
865
+ this.carousel.next();
866
+ }, delay);
867
+ }
868
+ });
869
+ }
870
+
871
+ ngOnInit(): void {
872
+ this.carousel.registerAutoplay(this);
873
+ }
874
+
875
+ ngOnDestroy(): void {
876
+ this.clearTimer();
877
+ this.carousel.unregisterAutoplay(this);
878
+ }
879
+
880
+ private clearTimer(): void {
881
+ if (this.timer != null) {
882
+ clearInterval(this.timer);
883
+ this.timer = null;
884
+ }
885
+ }
886
+ }
887
+
888
+ /**
889
+ * Pause / play toggle. Wraps a real `<button>` and reflects the carousel's
890
+ * `paused` signal as `aria-pressed`. When `prefers-reduced-motion: reduce`
891
+ * matches the host is auto-disabled (autoplay never starts, so the toggle is
892
+ * meaningless).
893
+ *
894
+ * @doc-category Core/Data display
895
+ * @doc
896
+ * @doc-name carousel
897
+ */
898
+ @Directive({
899
+ selector: '[kjCarouselPauseToggle]',
900
+ standalone: true,
901
+ hostDirectives: [KjFocusRing, KjDisabled],
902
+ host: {
903
+ '[attr.aria-pressed]': 'carousel.paused() ? "true" : "false"',
904
+ '[attr.data-paused]': 'carousel.paused() ? "" : null',
905
+ '[attr.disabled]': 'carousel.prefersReducedMotion() ? "" : null',
906
+ '(click)': 'onClick()',
907
+ },
908
+ })
909
+ export class KjCarouselPauseToggle {
910
+ /** @internal */
911
+ readonly carousel = inject(KJ_CAROUSEL) as KjCarousel;
912
+
913
+ /** @internal */
914
+ onClick(): void {
915
+ if (this.carousel.prefersReducedMotion()) return;
916
+ if (this.carousel.paused()) this.carousel.play();
917
+ else this.carousel.pause();
918
+ }
919
+ }