@kouji-ui/core 0.0.4 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (534) hide show
  1. package/.turbo/turbo-build.log +7 -4
  2. package/CHANGELOG.md +169 -0
  3. package/ng-package.json +5 -1
  4. package/package.json +18 -6
  5. package/src/a11y/aria-describedby.ts +7 -3
  6. package/src/a11y/aria-labelledby.spec.ts +55 -0
  7. package/src/a11y/aria-labelledby.ts +38 -0
  8. package/src/a11y/focus-trap.ts +4 -2
  9. package/src/a11y/index.ts +1 -0
  10. package/src/a11y/live-region.ts +4 -2
  11. package/src/a11y/roving-tabindex.spec.ts +139 -0
  12. package/src/a11y/roving-tabindex.ts +56 -7
  13. package/src/a11y/visually-hidden.ts +4 -2
  14. package/src/accordion/accordion.context.ts +22 -0
  15. package/src/accordion/accordion.spec.ts +233 -23
  16. package/src/accordion/accordion.ts +311 -39
  17. package/src/accordion/index.ts +8 -2
  18. package/src/alert/alert.context.ts +28 -0
  19. package/src/alert/alert.spec.ts +217 -0
  20. package/src/alert/alert.ts +417 -0
  21. package/src/alert/config.ts +46 -0
  22. package/src/alert/index.ts +19 -0
  23. package/src/avatar/avatar-group.context.ts +45 -0
  24. package/src/avatar/avatar-group.spec.ts +358 -0
  25. package/src/avatar/avatar-group.ts +259 -0
  26. package/src/avatar/avatar.ts +17 -4
  27. package/src/avatar/index.ts +11 -1
  28. package/src/badge/badge.spec.ts +8 -0
  29. package/src/badge/badge.ts +24 -3
  30. package/src/breadcrumb/breadcrumb-current.ts +38 -0
  31. package/src/breadcrumb/breadcrumb-ellipsis.ts +71 -0
  32. package/src/breadcrumb/breadcrumb-item.ts +41 -0
  33. package/src/breadcrumb/breadcrumb-link.ts +44 -0
  34. package/src/breadcrumb/breadcrumb-list.ts +42 -0
  35. package/src/breadcrumb/breadcrumb-separator.ts +45 -0
  36. package/src/breadcrumb/breadcrumb.context.ts +45 -0
  37. package/src/breadcrumb/breadcrumb.spec.ts +354 -0
  38. package/src/breadcrumb/breadcrumb.ts +239 -0
  39. package/src/breadcrumb/config.ts +84 -0
  40. package/src/breadcrumb/index.ts +18 -0
  41. package/src/button/_examples/button.example.ts +71 -0
  42. package/src/button/_examples/button.finance.example.ts +83 -0
  43. package/src/button/_examples/button.retro.example.ts +91 -0
  44. package/src/button/_examples/button.sizes.example.ts +60 -0
  45. package/src/button/_examples/index.ts +6 -0
  46. package/src/button/button.spec.ts +139 -8
  47. package/src/button/button.ts +114 -16
  48. package/src/button/config.spec.ts +33 -0
  49. package/src/button/config.ts +45 -0
  50. package/src/button/index.ts +2 -1
  51. package/src/button-group/button-group.context.ts +28 -0
  52. package/src/button-group/button-group.spec.ts +99 -0
  53. package/src/button-group/button-group.ts +99 -0
  54. package/src/button-group/index.ts +2 -0
  55. package/src/calendar/calendar-day.ts +102 -0
  56. package/src/calendar/calendar-grid.ts +90 -0
  57. package/src/calendar/calendar-header.ts +65 -0
  58. package/src/calendar/calendar.context.ts +53 -0
  59. package/src/calendar/calendar.spec.ts +225 -0
  60. package/src/calendar/calendar.ts +233 -0
  61. package/src/calendar/date-utils.ts +225 -0
  62. package/src/calendar/index.ts +28 -0
  63. package/src/carousel/carousel.context.ts +81 -0
  64. package/src/carousel/carousel.spec.ts +337 -0
  65. package/src/carousel/carousel.ts +919 -0
  66. package/src/carousel/index.ts +22 -0
  67. package/src/cascade-select/cascade-select-option.ts +205 -0
  68. package/src/cascade-select/cascade-select-panel.ts +106 -0
  69. package/src/cascade-select/cascade-select-root.ts +265 -0
  70. package/src/cascade-select/cascade-select-sub-panel.ts +168 -0
  71. package/src/cascade-select/cascade-select-trigger.ts +44 -0
  72. package/src/cascade-select/cascade-select.context.ts +71 -0
  73. package/src/cascade-select/cascade-select.spec.ts +241 -0
  74. package/src/cascade-select/cascade-select.ts +5 -0
  75. package/src/cascade-select/index.ts +11 -0
  76. package/src/chart/chart.ts +6 -2
  77. package/src/chat/chat-avatar.ts +31 -0
  78. package/src/chat/chat-bubble.ts +49 -0
  79. package/src/chat/chat-footer.ts +67 -0
  80. package/src/chat/chat-header.ts +41 -0
  81. package/src/chat/chat-log.ts +43 -0
  82. package/src/chat/chat.context.ts +64 -0
  83. package/src/chat/chat.spec.ts +341 -0
  84. package/src/chat/chat.ts +191 -0
  85. package/src/chat/config.ts +53 -0
  86. package/src/chat/index.ts +21 -0
  87. package/src/checkbox/checkbox.ts +9 -2
  88. package/src/color-picker/color-picker.context.ts +74 -0
  89. package/src/color-picker/color-picker.spec.ts +241 -0
  90. package/src/color-picker/color-picker.ts +698 -0
  91. package/src/color-picker/color-picker.utils.ts +140 -0
  92. package/src/color-picker/index.ts +28 -0
  93. package/src/combobox/combobox-input.ts +143 -0
  94. package/src/combobox/combobox-listbox.ts +49 -0
  95. package/src/combobox/combobox-option.ts +43 -0
  96. package/src/combobox/combobox-root.ts +281 -0
  97. package/src/combobox/combobox.spec.ts +75 -0
  98. package/src/combobox/combobox.ts +4 -0
  99. package/src/combobox/index.ts +4 -0
  100. package/src/command-palette/command-empty.ts +36 -0
  101. package/src/command-palette/command-group.ts +21 -0
  102. package/src/command-palette/command-input.ts +70 -0
  103. package/src/command-palette/command-item.ts +60 -0
  104. package/src/command-palette/command-list.ts +25 -0
  105. package/src/command-palette/command-palette-dialog.ts +74 -0
  106. package/src/command-palette/command-palette-trigger.ts +64 -0
  107. package/src/command-palette/command-palette.filters.ts +53 -0
  108. package/src/command-palette/command-palette.spec.ts +349 -0
  109. package/src/command-palette/command-palette.ts +204 -0
  110. package/src/command-palette/command-separator.ts +18 -0
  111. package/src/command-palette/index.ts +15 -0
  112. package/src/confirm-popup/confirm-popup-action.ts +39 -0
  113. package/src/confirm-popup/confirm-popup-cancel.ts +38 -0
  114. package/src/confirm-popup/confirm-popup-content.ts +113 -0
  115. package/src/confirm-popup/confirm-popup-message.ts +36 -0
  116. package/src/confirm-popup/confirm-popup-trigger.ts +31 -0
  117. package/src/confirm-popup/confirm-popup.context.ts +41 -0
  118. package/src/confirm-popup/confirm-popup.spec.ts +337 -0
  119. package/src/confirm-popup/confirm-popup.ts +109 -0
  120. package/src/confirm-popup/index.ts +12 -0
  121. package/src/date-picker/date-picker-calendar.ts +65 -0
  122. package/src/date-picker/date-picker-trigger.ts +215 -0
  123. package/src/date-picker/date-picker.context.ts +50 -0
  124. package/src/date-picker/date-picker.spec.ts +117 -0
  125. package/src/date-picker/date-picker.ts +127 -0
  126. package/src/date-picker/index.ts +7 -0
  127. package/src/dialog/_examples/dialog.confirm.example.ts +145 -0
  128. package/src/dialog/_examples/dialog.example.ts +106 -0
  129. package/src/dialog/_examples/dialog.finance.example.ts +121 -0
  130. package/src/dialog/_examples/dialog.retro.example.ts +126 -0
  131. package/src/dialog/_examples/index.ts +6 -0
  132. package/src/dialog/dialog.ref.ts +42 -0
  133. package/src/dialog/dialog.service.ts +57 -167
  134. package/src/dialog/dialog.spec.ts +35 -120
  135. package/src/dialog/dialog.ts +17 -192
  136. package/src/dialog/index.ts +3 -9
  137. package/src/divider/divider.spec.ts +223 -0
  138. package/src/divider/divider.ts +137 -0
  139. package/src/divider/index.ts +5 -0
  140. package/src/drawer/drawer.ref.ts +63 -0
  141. package/src/drawer/drawer.service.ts +106 -0
  142. package/src/drawer/drawer.spec.ts +117 -0
  143. package/src/drawer/drawer.ts +122 -0
  144. package/src/drawer/index.ts +10 -0
  145. package/src/dropdown-menu/dropdown-menu-content.ts +258 -0
  146. package/src/dropdown-menu/dropdown-menu-group.ts +31 -0
  147. package/src/dropdown-menu/dropdown-menu-item.ts +51 -0
  148. package/src/dropdown-menu/dropdown-menu-label.ts +41 -0
  149. package/src/dropdown-menu/dropdown-menu-separator.ts +27 -0
  150. package/src/dropdown-menu/dropdown-menu-trigger.ts +140 -0
  151. package/src/dropdown-menu/dropdown-menu.spec.ts +265 -0
  152. package/src/dropdown-menu/dropdown-menu.ts +60 -0
  153. package/src/dropdown-menu/index.ts +15 -0
  154. package/src/example-components.ts +39 -30
  155. package/src/field/field-error.ts +49 -0
  156. package/src/field/field-group.ts +39 -0
  157. package/src/field/field-help.ts +47 -0
  158. package/src/field/field-label.ts +30 -0
  159. package/src/field/field.context.ts +28 -0
  160. package/src/field/field.spec.ts +179 -0
  161. package/src/field/field.ts +121 -0
  162. package/src/field/index.ts +6 -0
  163. package/src/file-upload/file-upload.context.ts +89 -0
  164. package/src/file-upload/file-upload.spec.ts +290 -0
  165. package/src/file-upload/file-upload.ts +639 -0
  166. package/src/file-upload/file-upload.types.ts +59 -0
  167. package/src/file-upload/index.ts +23 -0
  168. package/src/form/form-error-summary.ts +87 -0
  169. package/src/form/form-field.context.ts +3 -0
  170. package/src/form/form-field.spec.ts +25 -0
  171. package/src/form/form-field.ts +52 -0
  172. package/src/form/form.context.ts +52 -2
  173. package/src/form/form.spec.ts +155 -18
  174. package/src/form/form.ts +315 -35
  175. package/src/form/index.ts +11 -2
  176. package/src/icon/E2E.md +14 -0
  177. package/src/icon/icon.css +40 -0
  178. package/src/icon/icon.directive.spec.ts +198 -0
  179. package/src/icon/icon.directive.ts +113 -0
  180. package/src/icon/icon.mode.spec.ts +29 -0
  181. package/src/icon/icon.mode.ts +23 -0
  182. package/src/icon/icon.providers.spec.ts +63 -0
  183. package/src/icon/icon.providers.ts +47 -0
  184. package/src/icon/icon.resolver.spec.ts +96 -0
  185. package/src/icon/icon.resolver.ts +49 -0
  186. package/src/icon/icon.tokens.ts +88 -0
  187. package/src/icon/icon.types.ts +48 -0
  188. package/src/icon/index.ts +21 -0
  189. package/src/input/input.ts +12 -3
  190. package/src/input-group/index.ts +3 -0
  191. package/src/input-group/input-group-addon.ts +92 -0
  192. package/src/input-group/input-group.context.ts +27 -0
  193. package/src/input-group/input-group.spec.ts +186 -0
  194. package/src/input-group/input-group.ts +136 -0
  195. package/src/input-mask/index.ts +11 -0
  196. package/src/input-mask/input-mask.compile.ts +69 -0
  197. package/src/input-mask/input-mask.engine.ts +376 -0
  198. package/src/input-mask/input-mask.presets.ts +24 -0
  199. package/src/input-mask/input-mask.spec.ts +304 -0
  200. package/src/input-mask/input-mask.tokens.ts +49 -0
  201. package/src/input-mask/input-mask.ts +280 -0
  202. package/src/input-otp/index.ts +2 -0
  203. package/src/input-otp/input-otp.context.ts +41 -0
  204. package/src/input-otp/input-otp.spec.ts +308 -0
  205. package/src/input-otp/input-otp.ts +470 -0
  206. package/src/kbd/index.ts +1 -0
  207. package/src/kbd/kbd.spec.ts +127 -0
  208. package/src/kbd/kbd.ts +132 -0
  209. package/src/link/config.ts +44 -0
  210. package/src/link/index.ts +2 -0
  211. package/src/link/link.spec.ts +246 -0
  212. package/src/link/link.ts +226 -0
  213. package/src/list/index.ts +11 -0
  214. package/src/list/list.spec.ts +237 -0
  215. package/src/list/list.ts +277 -0
  216. package/src/menubar/index.ts +7 -0
  217. package/src/menubar/menubar-item.ts +204 -0
  218. package/src/menubar/menubar.context.ts +43 -0
  219. package/src/menubar/menubar.spec.ts +433 -0
  220. package/src/menubar/menubar.ts +309 -0
  221. package/src/number-input/index.ts +4 -0
  222. package/src/number-input/number-input-group.ts +64 -0
  223. package/src/number-input/number-input.context.ts +31 -0
  224. package/src/number-input/number-input.format.ts +148 -0
  225. package/src/number-input/number-input.spec.ts +170 -0
  226. package/src/number-input/number-input.ts +457 -0
  227. package/src/number-input/number-stepper.ts +193 -0
  228. package/src/overlay-badge/index.ts +7 -0
  229. package/src/overlay-badge/overlay-badge-content.ts +73 -0
  230. package/src/overlay-badge/overlay-badge.context.ts +37 -0
  231. package/src/overlay-badge/overlay-badge.spec.ts +220 -0
  232. package/src/overlay-badge/overlay-badge.ts +128 -0
  233. package/src/pagination/config.ts +82 -0
  234. package/src/pagination/index.ts +19 -0
  235. package/src/pagination/page-tokens.ts +78 -0
  236. package/src/pagination/pagination-ellipsis.ts +69 -0
  237. package/src/pagination/pagination-first.ts +84 -0
  238. package/src/pagination/pagination-info.ts +70 -0
  239. package/src/pagination/pagination-item.ts +81 -0
  240. package/src/pagination/pagination-last.ts +85 -0
  241. package/src/pagination/pagination-next.ts +86 -0
  242. package/src/pagination/pagination-previous.ts +90 -0
  243. package/src/pagination/pagination.context.ts +52 -0
  244. package/src/pagination/pagination.spec.ts +388 -0
  245. package/src/pagination/pagination.ts +288 -0
  246. package/src/password-input/index.ts +15 -0
  247. package/src/password-input/password-input.context.ts +65 -0
  248. package/src/password-input/password-input.scorer.ts +34 -0
  249. package/src/password-input/password-input.spec.ts +274 -0
  250. package/src/password-input/password-input.ts +488 -0
  251. package/src/popover/_examples/index.ts +5 -0
  252. package/src/popover/_examples/popover.example.ts +92 -0
  253. package/src/popover/_examples/popover.finance.example.ts +106 -0
  254. package/src/popover/_examples/popover.retro.example.ts +103 -0
  255. package/src/popover/index.ts +5 -12
  256. package/src/popover/popover-arrow.ts +30 -0
  257. package/src/popover/popover-close.ts +36 -0
  258. package/src/popover/popover-content.ts +41 -0
  259. package/src/popover/popover-title.ts +39 -0
  260. package/src/popover/popover-trigger.ts +40 -0
  261. package/src/popover/popover.spec.ts +51 -44
  262. package/src/popover/popover.ts +5 -124
  263. package/src/presets/bind-presets.spec.ts +50 -0
  264. package/src/presets/bind-presets.ts +43 -0
  265. package/src/presets/index.ts +3 -0
  266. package/src/presets/size.spec.ts +67 -0
  267. package/src/presets/size.ts +59 -0
  268. package/src/presets/variant.spec.ts +72 -0
  269. package/src/presets/variant.ts +62 -0
  270. package/src/primitives/directionality/directionality.spec.ts +112 -0
  271. package/src/primitives/directionality/directionality.ts +89 -0
  272. package/src/primitives/directionality/index.ts +1 -0
  273. package/src/primitives/forms/form-control.spec.ts +11 -3
  274. package/src/primitives/forms/form-control.ts +6 -2
  275. package/src/primitives/index.ts +3 -0
  276. package/src/primitives/interaction/disabled.ts +12 -5
  277. package/src/primitives/interaction/focus-ring.ts +4 -2
  278. package/src/primitives/list/filterable-list.spec.ts +114 -0
  279. package/src/primitives/list/filterable-list.ts +126 -0
  280. package/src/primitives/list/group.spec.ts +99 -0
  281. package/src/primitives/list/group.ts +139 -0
  282. package/src/primitives/list/index.ts +8 -0
  283. package/src/primitives/list/inject-helpers.ts +8 -0
  284. package/src/primitives/list/item.spec.ts +173 -0
  285. package/src/primitives/list/item.ts +222 -0
  286. package/src/primitives/list/navigator.spec.ts +286 -0
  287. package/src/primitives/list/navigator.ts +238 -0
  288. package/src/primitives/list/selection.spec.ts +214 -0
  289. package/src/primitives/list/selection.ts +339 -0
  290. package/src/primitives/list/tokens.ts +106 -0
  291. package/src/primitives/list/type-ahead.spec.ts +58 -0
  292. package/src/primitives/list/type-ahead.ts +46 -0
  293. package/src/primitives/overlay/backdrop.spec.ts +54 -0
  294. package/src/primitives/overlay/backdrop.ts +31 -0
  295. package/src/primitives/overlay/builder.spec.ts +38 -0
  296. package/src/primitives/overlay/builder.ts +151 -0
  297. package/src/primitives/overlay/container.ts +42 -0
  298. package/src/primitives/overlay/context.ts +14 -0
  299. package/src/primitives/overlay/controller.spec.ts +86 -0
  300. package/src/primitives/overlay/controller.ts +190 -0
  301. package/src/primitives/overlay/id.spec.ts +24 -0
  302. package/src/primitives/overlay/id.ts +17 -0
  303. package/src/primitives/overlay/index.ts +18 -1
  304. package/src/primitives/overlay/overlay.css +42 -0
  305. package/src/primitives/overlay/panel.spec.ts +52 -0
  306. package/src/primitives/overlay/panel.ts +102 -0
  307. package/src/primitives/overlay/stack.spec.ts +55 -0
  308. package/src/primitives/overlay/stack.ts +113 -0
  309. package/src/primitives/overlay/strategies/backdrop/blurred.spec.ts +11 -0
  310. package/src/primitives/overlay/strategies/backdrop/blurred.ts +5 -0
  311. package/src/primitives/overlay/strategies/backdrop/none.spec.ts +10 -0
  312. package/src/primitives/overlay/strategies/backdrop/none.ts +9 -0
  313. package/src/primitives/overlay/strategies/backdrop/solid.spec.ts +18 -0
  314. package/src/primitives/overlay/strategies/backdrop/solid.ts +23 -0
  315. package/src/primitives/overlay/strategies/focus-trap/inert-based.spec.ts +28 -0
  316. package/src/primitives/overlay/strategies/focus-trap/inert-based.ts +38 -0
  317. package/src/primitives/overlay/strategies/focus-trap/no-trap.spec.ts +9 -0
  318. package/src/primitives/overlay/strategies/focus-trap/no-trap.ts +8 -0
  319. package/src/primitives/overlay/strategies/focus-trap/tab-cycle.spec.ts +49 -0
  320. package/src/primitives/overlay/strategies/focus-trap/tab-cycle.ts +85 -0
  321. package/src/primitives/overlay/strategies/index.ts +34 -0
  322. package/src/primitives/overlay/strategies/live-announcer/_announce.ts +35 -0
  323. package/src/primitives/overlay/strategies/live-announcer/assertive.spec.ts +21 -0
  324. package/src/primitives/overlay/strategies/live-announcer/assertive.ts +11 -0
  325. package/src/primitives/overlay/strategies/live-announcer/polite.spec.ts +21 -0
  326. package/src/primitives/overlay/strategies/live-announcer/polite.ts +11 -0
  327. package/src/primitives/overlay/strategies/live-announcer/silent.spec.ts +12 -0
  328. package/src/primitives/overlay/strategies/live-announcer/silent.ts +5 -0
  329. package/src/primitives/overlay/strategies/mount/body-portal.spec.ts +40 -0
  330. package/src/primitives/overlay/strategies/mount/body-portal.ts +91 -0
  331. package/src/primitives/overlay/strategies/mount/in-container.spec.ts +43 -0
  332. package/src/primitives/overlay/strategies/mount/in-container.ts +39 -0
  333. package/src/primitives/overlay/strategies/mount/in-place.spec.ts +40 -0
  334. package/src/primitives/overlay/strategies/mount/in-place.ts +20 -0
  335. package/src/primitives/overlay/strategies/position/anchored-to.spec.ts +56 -0
  336. package/src/primitives/overlay/strategies/position/anchored-to.ts +248 -0
  337. package/src/primitives/overlay/strategies/position/corner.spec.ts +36 -0
  338. package/src/primitives/overlay/strategies/position/corner.ts +33 -0
  339. package/src/primitives/overlay/strategies/position/edge-sheet.spec.ts +32 -0
  340. package/src/primitives/overlay/strategies/position/edge-sheet.ts +36 -0
  341. package/src/primitives/overlay/strategies/position/in-place-sibling.spec.ts +25 -0
  342. package/src/primitives/overlay/strategies/position/in-place-sibling.ts +13 -0
  343. package/src/primitives/overlay/strategies/position/point-at.spec.ts +40 -0
  344. package/src/primitives/overlay/strategies/position/point-at.ts +34 -0
  345. package/src/primitives/overlay/strategies/position/viewport-centered.spec.ts +31 -0
  346. package/src/primitives/overlay/strategies/position/viewport-centered.ts +31 -0
  347. package/src/primitives/overlay/strategies/scroll-lock/css-clip.spec.ts +15 -0
  348. package/src/primitives/overlay/strategies/scroll-lock/css-clip.ts +39 -0
  349. package/src/primitives/overlay/strategies/scroll-lock/html-overflow.spec.ts +21 -0
  350. package/src/primitives/overlay/strategies/scroll-lock/html-overflow.ts +44 -0
  351. package/src/primitives/overlay/strategies/scroll-lock/none.spec.ts +9 -0
  352. package/src/primitives/overlay/strategies/scroll-lock/none.ts +5 -0
  353. package/src/primitives/overlay/strategies/trigger-event/on-click.spec.ts +32 -0
  354. package/src/primitives/overlay/strategies/trigger-event/on-click.ts +28 -0
  355. package/src/primitives/overlay/strategies/trigger-event/on-context-menu.spec.ts +45 -0
  356. package/src/primitives/overlay/strategies/trigger-event/on-context-menu.ts +70 -0
  357. package/src/primitives/overlay/strategies/trigger-event/on-focus-or-input.spec.ts +35 -0
  358. package/src/primitives/overlay/strategies/trigger-event/on-focus-or-input.ts +34 -0
  359. package/src/primitives/overlay/strategies/trigger-event/on-focus.spec.ts +64 -0
  360. package/src/primitives/overlay/strategies/trigger-event/on-focus.ts +37 -0
  361. package/src/primitives/overlay/strategies/trigger-event/on-hotkey.spec.ts +37 -0
  362. package/src/primitives/overlay/strategies/trigger-event/on-hotkey.ts +59 -0
  363. package/src/primitives/overlay/strategies/trigger-event/on-hover.spec.ts +34 -0
  364. package/src/primitives/overlay/strategies/trigger-event/on-hover.ts +86 -0
  365. package/src/primitives/overlay/strategies/trigger-event/programmatic.spec.ts +16 -0
  366. package/src/primitives/overlay/strategies/trigger-event/programmatic.ts +12 -0
  367. package/src/primitives/overlay/tokens.ts +54 -0
  368. package/src/primitives/overlay/trigger.spec.ts +103 -0
  369. package/src/primitives/overlay/trigger.ts +75 -0
  370. package/src/primitives/overlay/types.ts +24 -0
  371. package/src/primitives/overlay/wrapper.ts +50 -0
  372. package/src/primitives/signals/deep-signal.ts +109 -0
  373. package/src/primitives/signals/index.ts +1 -0
  374. package/src/progress-bar/config.ts +48 -0
  375. package/src/progress-bar/index.ts +12 -0
  376. package/src/progress-bar/progress-bar-fill.ts +37 -0
  377. package/src/progress-bar/progress-bar.context.ts +34 -0
  378. package/src/progress-bar/progress-bar.spec.ts +423 -0
  379. package/src/progress-bar/progress-bar.ts +217 -0
  380. package/src/public-api.ts +58 -9
  381. package/src/radio/radio.ts +14 -3
  382. package/src/select/index.ts +4 -2
  383. package/src/select/select-content.ts +69 -0
  384. package/src/select/select-option.ts +35 -0
  385. package/src/select/select-root.ts +129 -0
  386. package/src/select/select-trigger.ts +93 -0
  387. package/src/select/select.spec.ts +133 -140
  388. package/src/select/select.ts +4 -250
  389. package/src/skeleton/index.ts +5 -0
  390. package/src/skeleton/skeleton.spec.ts +115 -0
  391. package/src/skeleton/skeleton.ts +56 -0
  392. package/src/slider/index.ts +10 -0
  393. package/src/slider/slider-range.ts +49 -0
  394. package/src/slider/slider-thumb.ts +335 -0
  395. package/src/slider/slider-track.ts +70 -0
  396. package/src/slider/slider.context.ts +79 -0
  397. package/src/slider/slider.geometry.ts +73 -0
  398. package/src/slider/slider.spec.ts +216 -0
  399. package/src/slider/slider.ts +443 -0
  400. package/src/speed-dial/index.ts +10 -0
  401. package/src/speed-dial/speed-dial-action.ts +40 -0
  402. package/src/speed-dial/speed-dial-actions.ts +31 -0
  403. package/src/speed-dial/speed-dial-trigger.ts +55 -0
  404. package/src/speed-dial/speed-dial.context.ts +36 -0
  405. package/src/speed-dial/speed-dial.spec.ts +143 -0
  406. package/src/speed-dial/speed-dial.ts +127 -0
  407. package/src/spinner/config.ts +48 -0
  408. package/src/spinner/index.ts +8 -0
  409. package/src/spinner/spinner.spec.ts +262 -0
  410. package/src/spinner/spinner.ts +129 -0
  411. package/src/stepper/index.ts +15 -0
  412. package/src/stepper/stepper.context.ts +63 -0
  413. package/src/stepper/stepper.spec.ts +403 -0
  414. package/src/stepper/stepper.ts +544 -0
  415. package/src/styles.css +17 -0
  416. package/src/table/column-helpers.spec.ts +74 -0
  417. package/src/table/column-helpers.ts +68 -0
  418. package/src/table/filter-fns.ts +230 -0
  419. package/src/table/filter-models.spec.ts +240 -0
  420. package/src/table/filter-models.ts +124 -0
  421. package/src/table/filter-params.ts +90 -0
  422. package/src/table/grid-api-impl.ts +321 -0
  423. package/src/table/grid-api.spec.ts +178 -0
  424. package/src/table/grid-api.ts +285 -0
  425. package/src/table/index.ts +73 -1
  426. package/src/table/table-cell.spec.ts +43 -0
  427. package/src/table/table-cell.ts +38 -0
  428. package/src/table/table-filter-outlet.ts +120 -0
  429. package/src/table/table-header.spec.ts +54 -0
  430. package/src/table/table-header.ts +63 -0
  431. package/src/table/table-keyboard.spec.ts +68 -0
  432. package/src/table/table-keyboard.ts +107 -0
  433. package/src/table/table-resource.spec.ts +37 -0
  434. package/src/table/table-resource.ts +36 -0
  435. package/src/table/table-row.spec.ts +54 -0
  436. package/src/table/table-row.ts +31 -0
  437. package/src/table/table-storage.spec.ts +67 -0
  438. package/src/table/table-storage.ts +76 -0
  439. package/src/table/table.spec.ts +101 -42
  440. package/src/table/table.ts +228 -73
  441. package/src/table/table.types.ts +83 -0
  442. package/src/tabs/index.ts +7 -2
  443. package/src/tabs/tabs.context.ts +27 -4
  444. package/src/tabs/tabs.spec.ts +336 -24
  445. package/src/tabs/tabs.ts +313 -79
  446. package/src/tag/config.ts +53 -0
  447. package/src/tag/index.ts +7 -0
  448. package/src/tag/tag-list.ts +76 -0
  449. package/src/tag/tag-remove.ts +61 -0
  450. package/src/tag/tag.context.ts +36 -0
  451. package/src/tag/tag.spec.ts +240 -0
  452. package/src/tag/tag.ts +203 -0
  453. package/src/textarea/config.ts +42 -0
  454. package/src/textarea/index.ts +8 -0
  455. package/src/textarea/textarea.spec.ts +176 -0
  456. package/src/textarea/textarea.ts +235 -0
  457. package/src/time-picker/index.ts +15 -0
  458. package/src/time-picker/time-picker-meridiem.ts +123 -0
  459. package/src/time-picker/time-picker-segment.ts +318 -0
  460. package/src/time-picker/time-picker.context.ts +38 -0
  461. package/src/time-picker/time-picker.format.ts +181 -0
  462. package/src/time-picker/time-picker.spec.ts +189 -0
  463. package/src/time-picker/time-picker.ts +274 -0
  464. package/src/toast/_examples/index.ts +5 -0
  465. package/src/toast/_examples/toast.example.ts +282 -0
  466. package/src/toast/_examples/toast.finance.example.ts +180 -0
  467. package/src/toast/_examples/toast.retro.example.ts +161 -0
  468. package/src/toast/index.ts +8 -1
  469. package/src/toast/toast.ref.ts +26 -0
  470. package/src/toast/toast.service.ts +227 -41
  471. package/src/toast/toast.spec.ts +166 -14
  472. package/src/toast/toast.strategy.ts +10 -0
  473. package/src/toast/toast.ts +177 -21
  474. package/src/toggle/toggle.ts +10 -2
  475. package/src/tooltip/_examples/index.ts +6 -0
  476. package/src/tooltip/_examples/tooltip.example.ts +82 -0
  477. package/src/tooltip/_examples/tooltip.finance.example.ts +97 -0
  478. package/src/tooltip/_examples/tooltip.placements.example.ts +83 -0
  479. package/src/tooltip/_examples/tooltip.retro.example.ts +107 -0
  480. package/src/tooltip/index.ts +4 -5
  481. package/src/tooltip/tooltip-arrow.ts +31 -0
  482. package/src/tooltip/tooltip-content.ts +38 -0
  483. package/src/tooltip/tooltip-group.ts +32 -0
  484. package/src/tooltip/tooltip-trigger.ts +47 -0
  485. package/src/tooltip/tooltip.spec.ts +38 -102
  486. package/src/tooltip/tooltip.ts +5 -129
  487. package/src/tree-select/index.ts +12 -0
  488. package/src/tree-select/tree-select-content.ts +199 -0
  489. package/src/tree-select/tree-select-node.ts +132 -0
  490. package/src/tree-select/tree-select-root.ts +272 -0
  491. package/src/tree-select/tree-select-trigger.ts +90 -0
  492. package/src/tree-select/tree-select.context.ts +63 -0
  493. package/src/tree-select/tree-select.spec.ts +433 -0
  494. package/src/tree-select/tree-select.ts +2 -0
  495. package/src/typography/blockquote.ts +57 -0
  496. package/src/typography/code.ts +53 -0
  497. package/src/typography/index.ts +22 -0
  498. package/src/typography/lead.ts +49 -0
  499. package/src/typography/muted.ts +29 -0
  500. package/src/typography/prose.css +312 -0
  501. package/src/typography/truncate.ts +99 -0
  502. package/src/typography/typography.spec.ts +203 -0
  503. package/tsconfig.lib.json +9 -1
  504. package/tsconfig.lib.prod.json +7 -1
  505. package/tsconfig.spec.json +9 -1
  506. package/vite.config.ts +8 -0
  507. package/src/button/button.example.ts +0 -30
  508. package/src/button/button.finance.example.ts +0 -37
  509. package/src/button/button.retro.example.ts +0 -47
  510. package/src/button/button.sizes.example.ts +0 -25
  511. package/src/dialog/dialog.confirm.example.ts +0 -57
  512. package/src/dialog/dialog.context.ts +0 -11
  513. package/src/dialog/dialog.example.ts +0 -40
  514. package/src/dialog/dialog.finance.example.ts +0 -47
  515. package/src/dialog/dialog.retro.example.ts +0 -46
  516. package/src/menu/index.ts +0 -2
  517. package/src/menu/menu.context.ts +0 -10
  518. package/src/menu/menu.example.ts +0 -39
  519. package/src/menu/menu.spec.ts +0 -168
  520. package/src/menu/menu.ts +0 -176
  521. package/src/popover/popover.context.ts +0 -18
  522. package/src/popover/popover.example.ts +0 -50
  523. package/src/popover/popover.finance.example.ts +0 -52
  524. package/src/popover/popover.retro.example.ts +0 -53
  525. package/src/primitives/overlay/overlay.spec.ts +0 -90
  526. package/src/primitives/overlay/overlay.ts +0 -106
  527. package/src/select/select.context.ts +0 -3
  528. package/src/toast/toast.example.ts +0 -134
  529. package/src/toast/toast.finance.example.ts +0 -68
  530. package/src/toast/toast.retro.example.ts +0 -69
  531. package/src/tooltip/tooltip.example.ts +0 -46
  532. package/src/tooltip/tooltip.finance.example.ts +0 -54
  533. package/src/tooltip/tooltip.placements.example.ts +0 -56
  534. package/src/tooltip/tooltip.retro.example.ts +0 -65
@@ -0,0 +1,225 @@
1
+ import { Component, signal, ChangeDetectionStrategy } from '@angular/core';
2
+ import { TestBed } from '@angular/core/testing';
3
+ import { describe, expect, test, beforeEach } from 'vitest';
4
+ import { KjCalendar, KjCalendarDay, KjCalendarGrid, KjCalendarHeader } from './index';
5
+ import {
6
+ addDays,
7
+ buildMonthMatrix,
8
+ formatDateShort,
9
+ formatMonthYear,
10
+ isSameDay,
11
+ parseDate,
12
+ startOfDay,
13
+ } from './date-utils';
14
+
15
+ @Component({
16
+ standalone: true,
17
+ imports: [KjCalendar, KjCalendarHeader, KjCalendarGrid, KjCalendarDay],
18
+ changeDetection: ChangeDetectionStrategy.Eager,
19
+ template: `
20
+ <div
21
+ kjCalendar
22
+ #cal="kjCalendar"
23
+ [(kjValue)]="value"
24
+ [kjMin]="min()"
25
+ [kjMax]="max()"
26
+ [kjDisabledDates]="filter()"
27
+ >
28
+ <div kjCalendarHeader #hdr="kjCalendarHeader">
29
+ <button type="button" (click)="hdr.prev()">prev</button>
30
+ <h2 [id]="hdr.captionId()">{{ hdr.label() }}</h2>
31
+ <button type="button" (click)="hdr.next()">next</button>
32
+ </div>
33
+ <table kjCalendarGrid #g="kjCalendarGrid">
34
+ <thead>
35
+ <tr>
36
+ @for (n of g.weekdayShort(); track $index) {
37
+ <th scope="col">{{ n }}</th>
38
+ }
39
+ </tr>
40
+ </thead>
41
+ <tbody>
42
+ @for (week of g.weeks(); track $index) {
43
+ <tr>
44
+ @for (d of week; track d.getTime()) {
45
+ <td>
46
+ <button kjCalendarDay [kjDate]="d">{{ d.getDate() }}</button>
47
+ </td>
48
+ }
49
+ </tr>
50
+ }
51
+ </tbody>
52
+ </table>
53
+ </div>
54
+ `,
55
+ })
56
+ class HostCalendar {
57
+ value = signal<Date | null>(new Date(2025, 3, 15)); // April 15, 2025
58
+ min = signal<Date | null>(null);
59
+ max = signal<Date | null>(null);
60
+ filter = signal<((d: Date) => boolean) | null>(null);
61
+ }
62
+
63
+ describe('Calendar utils', () => {
64
+ test('buildMonthMatrix returns 6 rows of 7', () => {
65
+ const m = buildMonthMatrix(new Date(2025, 3, 15), 0);
66
+ expect(m.length).toBe(6);
67
+ for (const row of m) expect(row.length).toBe(7);
68
+ });
69
+
70
+ test('parseDate handles ISO yyyy-MM-dd', () => {
71
+ const d = parseDate('2025-04-15', 'en-US')!;
72
+ expect(d.getFullYear()).toBe(2025);
73
+ expect(d.getMonth()).toBe(3);
74
+ expect(d.getDate()).toBe(15);
75
+ });
76
+
77
+ test('parseDate handles US-style m/d/y', () => {
78
+ const d = parseDate('4/15/2025', 'en-US')!;
79
+ expect(d.getFullYear()).toBe(2025);
80
+ expect(d.getMonth()).toBe(3);
81
+ expect(d.getDate()).toBe(15);
82
+ });
83
+
84
+ test('parseDate handles dmy locale', () => {
85
+ const d = parseDate('15/04/2025', 'en-GB')!;
86
+ expect(d.getFullYear()).toBe(2025);
87
+ expect(d.getMonth()).toBe(3);
88
+ expect(d.getDate()).toBe(15);
89
+ });
90
+
91
+ test('parseDate returns null for empty', () => {
92
+ expect(parseDate('', 'en-US')).toBeNull();
93
+ expect(parseDate(' ', 'en-US')).toBeNull();
94
+ });
95
+
96
+ test('formatMonthYear is locale-aware', () => {
97
+ const s = formatMonthYear(new Date(2025, 3, 1), 'en-US');
98
+ expect(s.toLowerCase()).toContain('april');
99
+ expect(s).toContain('2025');
100
+ });
101
+
102
+ test('formatDateShort produces locale numerics', () => {
103
+ const s = formatDateShort(new Date(2025, 3, 15), 'en-US');
104
+ expect(s).toContain('2025');
105
+ });
106
+ });
107
+
108
+ describe('KjCalendar directive', () => {
109
+ beforeEach(() => {
110
+ TestBed.configureTestingModule({ imports: [HostCalendar] });
111
+ });
112
+
113
+ test('renders the day grid for the seeded month', () => {
114
+ const fixture = TestBed.createComponent(HostCalendar);
115
+ fixture.detectChanges();
116
+ const cells = fixture.nativeElement.querySelectorAll('button[kjCalendarDay]');
117
+ expect(cells.length).toBe(42);
118
+ });
119
+
120
+ test('marks today with aria-current="date"', () => {
121
+ const fixture = TestBed.createComponent(HostCalendar);
122
+ // Set value to today so it's in the rendered month
123
+ fixture.componentInstance.value.set(new Date());
124
+ fixture.detectChanges();
125
+ const todayCell = fixture.nativeElement.querySelector('[aria-current="date"]');
126
+ expect(todayCell).not.toBeNull();
127
+ });
128
+
129
+ test('marks selected with aria-selected="true"', () => {
130
+ const fixture = TestBed.createComponent(HostCalendar);
131
+ fixture.detectChanges();
132
+ const selected = fixture.nativeElement.querySelectorAll('[aria-selected="true"]');
133
+ expect(selected.length).toBe(1);
134
+ });
135
+
136
+ test('clicking a day cell updates the value', () => {
137
+ const fixture = TestBed.createComponent(HostCalendar);
138
+ fixture.detectChanges();
139
+ const cells = fixture.nativeElement.querySelectorAll('button[kjCalendarDay]');
140
+ // Click on the first cell of the second week — definitely a real day in April.
141
+ const target = cells[7] as HTMLButtonElement;
142
+ target.click();
143
+ fixture.detectChanges();
144
+ const newVal = fixture.componentInstance.value();
145
+ expect(newVal).not.toBeNull();
146
+ });
147
+
148
+ test('respects kjMin — out-of-bounds cells get aria-disabled', () => {
149
+ const fixture = TestBed.createComponent(HostCalendar);
150
+ fixture.componentInstance.min.set(new Date(2025, 3, 10)); // Apr 10
151
+ fixture.detectChanges();
152
+ // Apr 5 should be disabled.
153
+ const disabled = fixture.nativeElement.querySelectorAll('[aria-disabled="true"]');
154
+ expect(disabled.length).toBeGreaterThan(0);
155
+ });
156
+
157
+ test('respects kjDisabledDates predicate', () => {
158
+ const fixture = TestBed.createComponent(HostCalendar);
159
+ // Disable all weekends.
160
+ fixture.componentInstance.filter.set((d: Date) => d.getDay() !== 0 && d.getDay() !== 6);
161
+ fixture.detectChanges();
162
+ const disabled = fixture.nativeElement.querySelectorAll('[aria-disabled="true"]');
163
+ // Across 6 weeks * 2 weekend days = up to 12 disabled cells.
164
+ expect(disabled.length).toBeGreaterThan(8);
165
+ });
166
+
167
+ test('grid hosts role="grid" with aria-labelledby pointing to the caption', () => {
168
+ const fixture = TestBed.createComponent(HostCalendar);
169
+ fixture.detectChanges();
170
+ const grid = fixture.nativeElement.querySelector('table[kjCalendarGrid]');
171
+ expect(grid.getAttribute('role')).toBe('grid');
172
+ const labelledBy = grid.getAttribute('aria-labelledby');
173
+ expect(labelledBy).toMatch(/^kj-calendar-caption-/);
174
+ const heading = fixture.nativeElement.querySelector(`#${labelledBy}`);
175
+ expect(heading).not.toBeNull();
176
+ });
177
+
178
+ test('host carries role="application" + aria-roledescription="calendar"', () => {
179
+ const fixture = TestBed.createComponent(HostCalendar);
180
+ fixture.detectChanges();
181
+ const root = fixture.nativeElement.querySelector('[kjCalendar]');
182
+ expect(root.getAttribute('role')).toBe('application');
183
+ expect(root.getAttribute('aria-roledescription')).toBe('calendar');
184
+ expect(root.getAttribute('aria-label')).toBe('Calendar');
185
+ });
186
+
187
+ test('header next() advances the focused month', () => {
188
+ const fixture = TestBed.createComponent(HostCalendar);
189
+ fixture.detectChanges();
190
+ const before = fixture.componentInstance.value()!;
191
+ const nextBtn = fixture.nativeElement.querySelectorAll('button')[1] as HTMLButtonElement;
192
+ nextBtn.click();
193
+ fixture.detectChanges();
194
+ // Value unchanged but the rendered month should have advanced — check the caption.
195
+ const cap = fixture.nativeElement.querySelector('h2');
196
+ expect(cap.textContent).not.toBe(formatMonthYear(before, 'en-US'));
197
+ });
198
+
199
+ test('keyboard ArrowRight advances focusedDate by one day', () => {
200
+ const fixture = TestBed.createComponent(HostCalendar);
201
+ fixture.detectChanges();
202
+ const grid = fixture.nativeElement.querySelector('table[kjCalendarGrid]');
203
+ // The focused-date cell should have tabindex=0.
204
+ const focused = grid.querySelector('[tabindex="0"]') as HTMLElement;
205
+ expect(focused).not.toBeNull();
206
+ const before = fixture.componentInstance.value()!;
207
+ grid.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true }));
208
+ fixture.detectChanges();
209
+ // Selection unchanged, focusedDate advanced — selected cell is still the original.
210
+ expect(isSameDay(fixture.componentInstance.value()!, before)).toBe(true);
211
+ });
212
+
213
+ test('keyboard Enter selects the focused date', () => {
214
+ const fixture = TestBed.createComponent(HostCalendar);
215
+ fixture.detectChanges();
216
+ const grid = fixture.nativeElement.querySelector('table[kjCalendarGrid]');
217
+ grid.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true }));
218
+ fixture.detectChanges();
219
+ grid.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
220
+ fixture.detectChanges();
221
+ const newVal = startOfDay(fixture.componentInstance.value()!);
222
+ const expected = startOfDay(addDays(new Date(2025, 3, 15), 1));
223
+ expect(isSameDay(newVal, expected)).toBe(true);
224
+ });
225
+ });
@@ -0,0 +1,233 @@
1
+ import {
2
+ Directive,
3
+ LOCALE_ID,
4
+ computed,
5
+ effect,
6
+ inject,
7
+ input,
8
+ model,
9
+ signal,
10
+ } from '@angular/core';
11
+ import { KjDisabled } from '../primitives/interaction/disabled';
12
+ import {
13
+ addDays,
14
+ addMonths,
15
+ addYears,
16
+ compareDay,
17
+ endOfMonth,
18
+ firstDayOfWeek as localeFirstDayOfWeek,
19
+ isInRange,
20
+ isSameDay,
21
+ startOfDay,
22
+ startOfMonth,
23
+ } from './date-utils';
24
+ import { KJ_CALENDAR, type KjCalendarContext } from './calendar.context';
25
+
26
+ let _captionIdCounter = 0;
27
+ function nextCaptionId(): string {
28
+ _captionIdCounter += 1;
29
+ return `kj-calendar-caption-${_captionIdCounter}`;
30
+ }
31
+
32
+ /**
33
+ * Headless calendar root. Owns date selection state, the focused-date roving
34
+ * signal, locale resolution, and the keyboard-navigation contract. Renders
35
+ * nothing on its own — pair with `KjCalendarHeader`, `KjCalendarGrid`, and
36
+ * `KjCalendarDay`.
37
+ *
38
+ * Composes `KjDisabled` for the global-disabled stance. Native `Date` only —
39
+ * locale-aware via `Intl.DateTimeFormat`.
40
+ *
41
+ * **Keyboard contract** (when focus is on a day cell, per APG):
42
+ * - `ArrowLeft` / `ArrowRight` — ±1 day
43
+ * - `ArrowUp` / `ArrowDown` — ±1 week
44
+ * - `Home` / `End` — first / last day of the focused week
45
+ * - `PageUp` / `PageDown` — ±1 month
46
+ * - `Shift+PageUp` / `Shift+PageDown` — ±1 year
47
+ * - `Enter` / `Space` — select the focused date
48
+ *
49
+ * Disabled dates are skipped by keyboard navigation — pressing `ArrowRight`
50
+ * lands on the next *selectable* day. Cells outside `kjMin` / `kjMax` carry
51
+ * `aria-disabled="true"` and can't receive focus.
52
+ *
53
+ * @doc-category Core/Data input
54
+ * @doc
55
+ * @doc-name calendar
56
+ * @doc-description Unstyled calendar root for date selection with locale, bounds, and full keyboard navigation.
57
+ * @doc-is-main
58
+ */
59
+ @Directive({
60
+ selector: '[kjCalendar]',
61
+ standalone: true,
62
+ exportAs: 'kjCalendar',
63
+ hostDirectives: [
64
+ { directive: KjDisabled, inputs: ['kjDisabled'] },
65
+ ],
66
+ providers: [
67
+ { provide: KJ_CALENDAR, useExisting: KjCalendar },
68
+ ],
69
+ host: {
70
+ 'role': 'application',
71
+ 'aria-roledescription': 'calendar',
72
+ '[attr.aria-label]': 'computedAriaLabel()',
73
+ '[attr.aria-disabled]': 'disabled() ? "true" : null',
74
+ '[attr.data-disabled]': 'disabled() ? "" : null',
75
+ },
76
+ })
77
+ export class KjCalendar implements KjCalendarContext {
78
+ private readonly disabledHost = inject(KjDisabled);
79
+ private readonly defaultLocale = inject(LOCALE_ID);
80
+
81
+ /** Current value. Two-way bindable — `[(kjValue)]`. `null` clears. */
82
+ readonly kjValue = model<Date | null>(null);
83
+
84
+ /** Earliest selectable date (inclusive). `null` = open. */
85
+ readonly kjMin = input<Date | null>(null);
86
+
87
+ /** Latest selectable date (inclusive). `null` = open. */
88
+ readonly kjMax = input<Date | null>(null);
89
+
90
+ /** Predicate returning `false` to disable specific dates (e.g. weekends). */
91
+ readonly kjDisabledDates = input<((date: Date) => boolean) | null>(null);
92
+
93
+ /** BCP-47 locale tag. Defaults to Angular's `LOCALE_ID`. */
94
+ readonly kjLocale = input<string>('');
95
+
96
+ /** First day of the week (0=Sun … 6=Sat). Defaults to locale-derived. */
97
+ readonly kjFirstDayOfWeek = input<number | null>(null);
98
+
99
+ /** Optional accessible label override. Defaults to `"Calendar"`. */
100
+ readonly kjAriaLabel = input<string>('Calendar');
101
+
102
+ /** Initially-focused date when no value is selected. Defaults to today. */
103
+ readonly kjStartAt = input<Date | null>(null);
104
+
105
+ // ── Context shared with children ───────────────────────────────────
106
+
107
+ readonly locale = computed(() => this.kjLocale() || this.defaultLocale);
108
+ readonly value = this.kjValue.asReadonly();
109
+ readonly minDate = computed(() => this.kjMin());
110
+ readonly maxDate = computed(() => this.kjMax());
111
+ readonly disabledDates = computed(() => this.kjDisabledDates());
112
+ readonly firstDayOfWeek = computed(() => {
113
+ const explicit = this.kjFirstDayOfWeek();
114
+ if (explicit !== null && explicit >= 0 && explicit <= 6) return explicit;
115
+ return localeFirstDayOfWeek(this.locale());
116
+ });
117
+ readonly disabled = this.disabledHost.disabled;
118
+
119
+ /** Internal focused-date signal. Two-way exposed via `kjFocusedDate` model. */
120
+ readonly kjFocusedDate = model<Date>(startOfDay(new Date()));
121
+ readonly focusedDate = this.kjFocusedDate;
122
+
123
+ /** Auto-minted caption id — used by the grid's `aria-labelledby`. */
124
+ readonly captionId = signal<string>(nextCaptionId());
125
+
126
+ /** Computed accessible name combining label + active selection (for SR clarity). */
127
+ readonly computedAriaLabel = computed(() => this.kjAriaLabel());
128
+
129
+ constructor() {
130
+ // Seed focusedDate from kjStartAt or kjValue or today.
131
+ const seed = this.kjStartAt() ?? this.kjValue() ?? new Date();
132
+ this.kjFocusedDate.set(startOfDay(seed));
133
+
134
+ // Whenever the value changes externally, follow with focus.
135
+ effect(() => {
136
+ const v = this.kjValue();
137
+ if (v) this.kjFocusedDate.set(startOfDay(v));
138
+ });
139
+ }
140
+
141
+ // ── KjCalendarContext methods ──────────────────────────────────────
142
+
143
+ isDisabled(date: Date): boolean {
144
+ if (this.disabled()) return true;
145
+ if (!isInRange(date, this.minDate(), this.maxDate())) return true;
146
+ const filter = this.disabledDates();
147
+ if (filter && !filter(date)) return true;
148
+ return false;
149
+ }
150
+
151
+ selectDate(date: Date): void {
152
+ if (this.isDisabled(date)) return;
153
+ const sod = startOfDay(date);
154
+ this.kjValue.set(sod);
155
+ this.kjFocusedDate.set(sod);
156
+ }
157
+
158
+ moveFocus(unit: 'day' | 'week' | 'month' | 'year', delta: number): void {
159
+ const start = this.focusedDate();
160
+ let candidate = this.advance(start, unit, delta);
161
+ // Skip disabled by stepping in the same direction one day at a time, max 366.
162
+ if (this.isDisabled(candidate)) {
163
+ const step = delta >= 0 ? 1 : -1;
164
+ for (let i = 0; i < 366; i += 1) {
165
+ candidate = addDays(candidate, step);
166
+ if (this.minDate() && compareDay(candidate, this.minDate() as Date) < 0) break;
167
+ if (this.maxDate() && compareDay(candidate, this.maxDate() as Date) > 0) break;
168
+ if (!this.isDisabled(candidate)) {
169
+ this.kjFocusedDate.set(startOfDay(candidate));
170
+ return;
171
+ }
172
+ }
173
+ // Couldn't find one — leave focus alone.
174
+ return;
175
+ }
176
+ this.kjFocusedDate.set(startOfDay(candidate));
177
+ }
178
+
179
+ moveFocusToWeekBoundary(boundary: 'start' | 'end'): void {
180
+ const f = this.focusedDate();
181
+ const ws = this.firstDayOfWeek();
182
+ const offset = (f.getDay() - ws + 7) % 7;
183
+ const target = boundary === 'start' ? addDays(f, -offset) : addDays(f, 6 - offset);
184
+ if (!this.isDisabled(target)) {
185
+ this.kjFocusedDate.set(startOfDay(target));
186
+ }
187
+ }
188
+
189
+ /** Snap focus to a specific date (used by the day-cell click handler). */
190
+ setFocusedDate(date: Date): void {
191
+ this.kjFocusedDate.set(startOfDay(date));
192
+ }
193
+
194
+ /** Helper: month-anchor for the currently-rendered grid. */
195
+ readonly viewMonth = computed(() => startOfMonth(this.focusedDate()));
196
+
197
+ private advance(date: Date, unit: 'day' | 'week' | 'month' | 'year', delta: number): Date {
198
+ switch (unit) {
199
+ case 'day':
200
+ return addDays(date, delta);
201
+ case 'week':
202
+ return addDays(date, delta * 7);
203
+ case 'month': {
204
+ const target = addMonths(date, delta);
205
+ // Clamp day-of-month if necessary.
206
+ const last = endOfMonth(target).getDate();
207
+ if (target.getDate() !== Math.min(date.getDate(), last)) {
208
+ target.setDate(Math.min(date.getDate(), last));
209
+ }
210
+ return target;
211
+ }
212
+ case 'year': {
213
+ const target = addYears(date, delta);
214
+ const last = endOfMonth(target).getDate();
215
+ if (target.getDate() !== Math.min(date.getDate(), last)) {
216
+ target.setDate(Math.min(date.getDate(), last));
217
+ }
218
+ return target;
219
+ }
220
+ }
221
+ }
222
+
223
+ /** True when `date` is the currently-selected value. */
224
+ isSelected(date: Date): boolean {
225
+ const v = this.value();
226
+ return !!v && isSameDay(v, date);
227
+ }
228
+
229
+ /** True when `date` matches today's local day. */
230
+ isToday(date: Date): boolean {
231
+ return isSameDay(date, new Date());
232
+ }
233
+ }
@@ -0,0 +1,225 @@
1
+ /**
2
+ * Tiny date helpers used by the Calendar and Date Picker families.
3
+ *
4
+ * Native `Date` only — no third-party date library. Operations work in local
5
+ * time zone (matching the user's runtime). Day-precision throughout —
6
+ * comparisons strip the time portion via {@link startOfDay}.
7
+ *
8
+ * @doc-category Core/Data input
9
+ */
10
+
11
+ /** Returns a new `Date` set to `00:00:00.000` in local time. */
12
+ export function startOfDay(date: Date): Date {
13
+ const out = new Date(date.getFullYear(), date.getMonth(), date.getDate());
14
+ return out;
15
+ }
16
+
17
+ /** Returns the first day of the month for `date`. */
18
+ export function startOfMonth(date: Date): Date {
19
+ return new Date(date.getFullYear(), date.getMonth(), 1);
20
+ }
21
+
22
+ /** Returns the last day of the month for `date`. */
23
+ export function endOfMonth(date: Date): Date {
24
+ return new Date(date.getFullYear(), date.getMonth() + 1, 0);
25
+ }
26
+
27
+ /** Adds `days` calendar days to `date`. Negative values subtract. */
28
+ export function addDays(date: Date, days: number): Date {
29
+ const out = new Date(date);
30
+ out.setDate(out.getDate() + days);
31
+ return out;
32
+ }
33
+
34
+ /** Adds `months` calendar months. Day-of-month clamps when target month is shorter. */
35
+ export function addMonths(date: Date, months: number): Date {
36
+ const day = date.getDate();
37
+ const out = new Date(date.getFullYear(), date.getMonth() + months, 1);
38
+ const dim = endOfMonth(out).getDate();
39
+ out.setDate(Math.min(day, dim));
40
+ return out;
41
+ }
42
+
43
+ /** Adds `years` calendar years. Feb 29 → Feb 28 on a non-leap target. */
44
+ export function addYears(date: Date, years: number): Date {
45
+ return addMonths(date, years * 12);
46
+ }
47
+
48
+ /** Returns true if `a` and `b` represent the same day in local time. */
49
+ export function isSameDay(a: Date, b: Date): boolean {
50
+ return (
51
+ a.getFullYear() === b.getFullYear()
52
+ && a.getMonth() === b.getMonth()
53
+ && a.getDate() === b.getDate()
54
+ );
55
+ }
56
+
57
+ /** Returns true if `a` and `b` are in the same month and year. */
58
+ export function isSameMonth(a: Date, b: Date): boolean {
59
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth();
60
+ }
61
+
62
+ /** Day-precision compare. Negative if `a < b`, positive if `a > b`, 0 if equal. */
63
+ export function compareDay(a: Date, b: Date): number {
64
+ return startOfDay(a).getTime() - startOfDay(b).getTime();
65
+ }
66
+
67
+ /** True when `date` is within `[min, max]` (inclusive). `null` bounds are open. */
68
+ export function isInRange(date: Date, min: Date | null, max: Date | null): boolean {
69
+ if (min && compareDay(date, min) < 0) return false;
70
+ if (max && compareDay(date, max) > 0) return false;
71
+ return true;
72
+ }
73
+
74
+ /**
75
+ * Locale-aware first day of the week (0 = Sunday … 6 = Saturday).
76
+ *
77
+ * Reads `Intl.Locale.getWeekInfo()` when available (Chromium 99+, Safari 17+,
78
+ * Firefox 130+); falls back to Sunday for `en` locales and Monday otherwise.
79
+ */
80
+ export function firstDayOfWeek(locale: string): number {
81
+ try {
82
+ const loc = new Intl.Locale(locale);
83
+ type LocWithWeek = Intl.Locale & { getWeekInfo?: () => { firstDay: number } };
84
+ const info = (loc as LocWithWeek).getWeekInfo?.();
85
+ if (info && typeof info.firstDay === 'number') {
86
+ // Intl reports Mon=1 … Sun=7. Convert to 0..6 with Sun=0.
87
+ return info.firstDay === 7 ? 0 : info.firstDay;
88
+ }
89
+ } catch {
90
+ /* ignore */
91
+ }
92
+ return locale.startsWith('en') ? 0 : 1;
93
+ }
94
+
95
+ /**
96
+ * Builds the 6×7 day-grid for `monthAnchor`. Always returns 42 cells —
97
+ * leading/trailing days from neighbouring months fill the grid so callers
98
+ * have a stable shape.
99
+ *
100
+ * @param monthAnchor — any `Date` whose month/year identifies the target month.
101
+ * @param weekStart — `0..6` — first column of the grid.
102
+ */
103
+ export function buildMonthMatrix(monthAnchor: Date, weekStart: number): Date[][] {
104
+ const first = startOfMonth(monthAnchor);
105
+ const firstDow = first.getDay();
106
+ const offset = (firstDow - weekStart + 7) % 7;
107
+ const gridStart = addDays(first, -offset);
108
+
109
+ const weeks: Date[][] = [];
110
+ for (let w = 0; w < 6; w += 1) {
111
+ const row: Date[] = [];
112
+ for (let d = 0; d < 7; d += 1) {
113
+ row.push(addDays(gridStart, w * 7 + d));
114
+ }
115
+ weeks.push(row);
116
+ }
117
+ return weeks;
118
+ }
119
+
120
+ /** Locale-aware short weekday names starting from `weekStart`. */
121
+ export function weekdayShortNames(locale: string, weekStart: number): string[] {
122
+ const fmt = new Intl.DateTimeFormat(locale, { weekday: 'short' });
123
+ // 1970-01-04 was a Sunday in UTC — anchor for stable iteration.
124
+ const base = new Date(1970, 0, 4);
125
+ const out: string[] = [];
126
+ for (let i = 0; i < 7; i += 1) {
127
+ out.push(fmt.format(addDays(base, (i + weekStart) % 7)));
128
+ }
129
+ return out;
130
+ }
131
+
132
+ /** Locale-aware long weekday names starting from `weekStart`. Used for `<th abbr>`. */
133
+ export function weekdayLongNames(locale: string, weekStart: number): string[] {
134
+ const fmt = new Intl.DateTimeFormat(locale, { weekday: 'long' });
135
+ const base = new Date(1970, 0, 4);
136
+ const out: string[] = [];
137
+ for (let i = 0; i < 7; i += 1) {
138
+ out.push(fmt.format(addDays(base, (i + weekStart) % 7)));
139
+ }
140
+ return out;
141
+ }
142
+
143
+ /** Locale-aware "April 2025" for the calendar caption. */
144
+ export function formatMonthYear(date: Date, locale: string): string {
145
+ return new Intl.DateTimeFormat(locale, { year: 'numeric', month: 'long' }).format(date);
146
+ }
147
+
148
+ /** Locale-aware long form for cell `aria-label` — "Tuesday, April 15, 2025". */
149
+ export function formatDateLong(date: Date, locale: string): string {
150
+ return new Intl.DateTimeFormat(locale, {
151
+ weekday: 'long',
152
+ year: 'numeric',
153
+ month: 'long',
154
+ day: 'numeric',
155
+ }).format(date);
156
+ }
157
+
158
+ /** Locale-aware short form for the Date Picker input — "4/15/2025" / "15/04/2025". */
159
+ export function formatDateShort(date: Date, locale: string): string {
160
+ return new Intl.DateTimeFormat(locale, {
161
+ year: 'numeric',
162
+ month: 'numeric',
163
+ day: 'numeric',
164
+ }).format(date);
165
+ }
166
+
167
+ /**
168
+ * Best-effort parse of a typed date string. Tries:
169
+ * 1. `Date.parse()` on ISO-ish input (`yyyy-MM-dd`).
170
+ * 2. Splitting on `/` `-` `.` ` ` and inferring locale order from the
171
+ * resolved options of `Intl.DateTimeFormat(locale, …)`.
172
+ *
173
+ * Returns `null` for empty / invalid input. Day-precision only.
174
+ */
175
+ export function parseDate(value: string, locale: string): Date | null {
176
+ const trimmed = value.trim();
177
+ if (!trimmed) return null;
178
+
179
+ // ISO 8601 (yyyy-MM-dd or yyyy-MM-ddTHH:mm:ss)
180
+ const iso = /^(\d{4})-(\d{2})-(\d{2})/.exec(trimmed);
181
+ if (iso) {
182
+ const y = Number(iso[1]);
183
+ const m = Number(iso[2]) - 1;
184
+ const d = Number(iso[3]);
185
+ const out = new Date(y, m, d);
186
+ if (out.getFullYear() === y && out.getMonth() === m && out.getDate() === d) return out;
187
+ return null;
188
+ }
189
+
190
+ const parts = trimmed.split(/[/\-.\s]+/).filter(Boolean);
191
+ if (parts.length < 3) return null;
192
+ const nums = parts.map((p) => Number(p));
193
+ if (nums.some((n) => !Number.isFinite(n))) return null;
194
+
195
+ // Determine locale order via resolved-options of DateTimeFormat parts.
196
+ const order = inferDateOrder(locale);
197
+ let y: number, m: number, d: number;
198
+ if (order === 'dmy') {
199
+ [d, m, y] = nums as [number, number, number];
200
+ } else if (order === 'ymd') {
201
+ [y, m, d] = nums as [number, number, number];
202
+ } else {
203
+ [m, d, y] = nums as [number, number, number];
204
+ }
205
+ if (y < 100) y += y < 50 ? 2000 : 1900;
206
+ m -= 1;
207
+ const out = new Date(y, m, d);
208
+ if (out.getFullYear() !== y || out.getMonth() !== m || out.getDate() !== d) return null;
209
+ return out;
210
+ }
211
+
212
+ function inferDateOrder(locale: string): 'mdy' | 'dmy' | 'ymd' {
213
+ try {
214
+ const parts = new Intl.DateTimeFormat(locale).formatToParts(new Date(2000, 0, 2));
215
+ const order: string[] = [];
216
+ for (const p of parts) {
217
+ if (p.type === 'day' || p.type === 'month' || p.type === 'year') order.push(p.type);
218
+ }
219
+ if (order[0] === 'year') return 'ymd';
220
+ if (order[0] === 'day') return 'dmy';
221
+ return 'mdy';
222
+ } catch {
223
+ return 'mdy';
224
+ }
225
+ }