@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,376 @@
1
+ import type { CompiledMask, Slot } from './input-mask.compile';
2
+
3
+ /** Callbacks injected into MaskEngine to decouple it from the DOM and Angular. */
4
+ export interface MaskEngineCallbacks {
5
+ /** Returns the current display value in the input element. */
6
+ getValue(): string;
7
+ /** Sets the display value in the input element. */
8
+ setValue(value: string): void;
9
+ /** Returns the current caret position (selectionStart). */
10
+ getCaret(): number;
11
+ /** Moves the caret to the given position. */
12
+ setCaret(pos: number): void;
13
+ /** Called whenever the logical (raw or masked) value changes. */
14
+ notifyChange(value: string): void;
15
+ }
16
+
17
+ /** Options for constructing a MaskEngine. */
18
+ export interface MaskEngineOptions {
19
+ getCompiled(): CompiledMask;
20
+ getSlotChar(): string;
21
+ getMaskMode(): 'unmasked' | 'masked';
22
+ callbacks: MaskEngineCallbacks;
23
+ }
24
+
25
+ /**
26
+ * Pure state machine for the input-mask keyboard/paste/caret behaviour.
27
+ * No Angular DI — constructed by the directive with callback closures.
28
+ *
29
+ * Responsibilities:
30
+ * - Intercept keydown (printable chars, Backspace, Delete, arrow keys).
31
+ * - Intercept paste events and sanitise clipboard content.
32
+ * - Reconstruct a display string from raw characters.
33
+ * - Emit the model value (raw or masked) via `notifyChange`.
34
+ */
35
+ export class MaskEngine {
36
+ private _composing = false;
37
+
38
+ constructor(private readonly opts: MaskEngineOptions) {}
39
+
40
+ // ── Public event handlers ──────────────────────────────────────────────
41
+
42
+ /** Handle keyboard events. Printable chars, Backspace, Delete, navigation. */
43
+ onKeydown(e: KeyboardEvent): void {
44
+ if (this._composing) return;
45
+
46
+ const { key, ctrlKey, metaKey, altKey } = e;
47
+
48
+ // Pass through: ctrl/meta/alt combos (copy, paste, undo, …)
49
+ if (ctrlKey || metaKey || altKey) return;
50
+ // Pass through: Tab, Enter, Escape, function keys
51
+ if (['Tab', 'Enter', 'Escape'].includes(key) || key.startsWith('F')) return;
52
+
53
+ if (key === 'Backspace') {
54
+ e.preventDefault();
55
+ this._handleBackspace();
56
+ return;
57
+ }
58
+
59
+ if (key === 'Delete') {
60
+ e.preventDefault();
61
+ this._handleDelete();
62
+ return;
63
+ }
64
+
65
+ if (key === 'ArrowLeft') {
66
+ e.preventDefault();
67
+ this._snapCaret('prev');
68
+ return;
69
+ }
70
+
71
+ if (key === 'ArrowRight') {
72
+ e.preventDefault();
73
+ this._snapCaret('next');
74
+ return;
75
+ }
76
+
77
+ if (key === 'Home') {
78
+ e.preventDefault();
79
+ this._snapCaret('first');
80
+ return;
81
+ }
82
+
83
+ if (key === 'End') {
84
+ e.preventDefault();
85
+ this._snapCaret('last');
86
+ return;
87
+ }
88
+
89
+ // Printable character (single char, not a special key)
90
+ if (key.length === 1) {
91
+ e.preventDefault();
92
+ this._insertChar(key);
93
+ }
94
+ }
95
+
96
+ /** Handle the beforeinput event (IME, mobile keyboards). */
97
+ onBeforeInput(e: InputEvent): void {
98
+ if (this._composing) return;
99
+ if (!e.data) return;
100
+ if (e.inputType === 'insertText' || e.inputType === 'insertCompositionText') {
101
+ e.preventDefault();
102
+ for (const ch of e.data) {
103
+ this._insertChar(ch);
104
+ }
105
+ }
106
+ }
107
+
108
+ /** Handle paste events — sanitise clipboard text into the mask. */
109
+ onPaste(e: ClipboardEvent): void {
110
+ e.preventDefault();
111
+ const text = e.clipboardData?.getData('text') ?? '';
112
+ this._pasteText(text);
113
+ }
114
+
115
+ /** Composition start — hold off on keydown processing. */
116
+ onCompositionStart(): void {
117
+ this._composing = true;
118
+ }
119
+
120
+ /** Composition end — process the composed string. */
121
+ onCompositionEnd(e: CompositionEvent): void {
122
+ this._composing = false;
123
+ if (e.data) {
124
+ for (const ch of e.data) {
125
+ this._insertChar(ch);
126
+ }
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Reconstruct a display string from a raw (unmasked) string.
132
+ * Fills variable slots in order with characters from `raw`.
133
+ * Returns the display string and the slot index after the last filled slot.
134
+ */
135
+ reconstructFromRaw(raw: string): { display: string; caretSlot: number } {
136
+ const { slots, variableIndices } = this.opts.getCompiled();
137
+ const slotChar = this.opts.getSlotChar();
138
+ const filled = new Array<string | null>(slots.length).fill(null);
139
+ let rawIdx = 0;
140
+
141
+ for (const varIdx of variableIndices) {
142
+ if (rawIdx >= raw.length) break;
143
+ const slot = slots[varIdx] as Extract<Slot, { kind: 'variable' }>;
144
+ const ch = raw[rawIdx];
145
+ if (slot.re.test(ch)) {
146
+ filled[varIdx] = ch;
147
+ rawIdx++;
148
+ } else {
149
+ rawIdx++;
150
+ // Try to keep going — skip non-matching raw chars
151
+ // (handles masked input passed via setValue)
152
+ }
153
+ }
154
+
155
+ const display = slots
156
+ .map((slot, i) =>
157
+ slot.kind === 'literal' ? slot.char : (filled[i] ?? slotChar),
158
+ )
159
+ .join('');
160
+
161
+ // caretSlot: position just after the last filled variable, or first empty
162
+ let caretSlot = variableIndices[0] ?? 0;
163
+ for (let i = variableIndices.length - 1; i >= 0; i--) {
164
+ if (filled[variableIndices[i]] !== null) {
165
+ caretSlot = (variableIndices[i + 1] ?? variableIndices[i]) + (i + 1 < variableIndices.length ? 0 : 1);
166
+ break;
167
+ }
168
+ }
169
+
170
+ return { display, caretSlot };
171
+ }
172
+
173
+ /**
174
+ * Reconstruct a display from a raw string, normalising the raw by stripping
175
+ * chars that don't fit the variable slot sequence. Used when writing from a
176
+ * form control setValue() call.
177
+ */
178
+ applyRawValue(raw: string): void {
179
+ const { slots, variableIndices } = this.opts.getCompiled();
180
+ const slotChar = this.opts.getSlotChar();
181
+ const filled = new Array<string | null>(slots.length).fill(null);
182
+
183
+ let rawIdx = 0;
184
+ for (const varIdx of variableIndices) {
185
+ if (rawIdx >= raw.length) break;
186
+ const slot = slots[varIdx] as Extract<Slot, { kind: 'variable' }>;
187
+ // skip chars that don't match (forgiving intake)
188
+ while (rawIdx < raw.length && !slot.re.test(raw[rawIdx])) {
189
+ rawIdx++;
190
+ }
191
+ if (rawIdx < raw.length) {
192
+ filled[varIdx] = raw[rawIdx];
193
+ rawIdx++;
194
+ }
195
+ }
196
+
197
+ const display = slots
198
+ .map((slot, i) =>
199
+ slot.kind === 'literal' ? slot.char : (filled[i] ?? slotChar),
200
+ )
201
+ .join('');
202
+
203
+ this.opts.callbacks.setValue(display);
204
+ this._emitModelValue(filled);
205
+ }
206
+
207
+ // ── Private helpers ────────────────────────────────────────────────────
208
+
209
+ private _getFilledSlots(): Array<string | null> {
210
+ const { slots } = this.opts.getCompiled();
211
+ const slotChar = this.opts.getSlotChar();
212
+ const display = this.opts.callbacks.getValue();
213
+ return slots.map((slot, i) => {
214
+ if (slot.kind === 'literal') return null;
215
+ const ch = display[i];
216
+ return ch && ch !== slotChar ? ch : null;
217
+ });
218
+ }
219
+
220
+ private _buildDisplay(filled: Array<string | null>): string {
221
+ const { slots } = this.opts.getCompiled();
222
+ const slotChar = this.opts.getSlotChar();
223
+ return slots
224
+ .map((slot, i) =>
225
+ slot.kind === 'literal' ? slot.char : (filled[i] ?? slotChar),
226
+ )
227
+ .join('');
228
+ }
229
+
230
+ private _emitModelValue(filled: Array<string | null>): void {
231
+ const { slots, variableIndices } = this.opts.getCompiled();
232
+ const mode = this.opts.getMaskMode();
233
+ let value: string;
234
+ if (mode === 'unmasked') {
235
+ value = variableIndices
236
+ .map(i => filled[i] ?? '')
237
+ .join('');
238
+ } else {
239
+ const slotChar = this.opts.getSlotChar();
240
+ value = slots
241
+ .map((slot, i) =>
242
+ slot.kind === 'literal' ? slot.char : (filled[i] ?? slotChar),
243
+ )
244
+ .join('');
245
+ }
246
+ this.opts.callbacks.notifyChange(value);
247
+ }
248
+
249
+ private _insertChar(ch: string): void {
250
+ const { variableIndices } = this.opts.getCompiled();
251
+ const { slots } = this.opts.getCompiled();
252
+ const caret = this.opts.callbacks.getCaret();
253
+ const filled = this._getFilledSlots();
254
+
255
+ // Find the next variable slot at or after caret
256
+ const targetIdx = variableIndices.find(i => i >= caret);
257
+ if (targetIdx === undefined) return; // mask is full or caret past end
258
+
259
+ const slot = slots[targetIdx] as Extract<Slot, { kind: 'variable' }>;
260
+ if (!slot.re.test(ch)) return; // char doesn't match slot regex
261
+
262
+ filled[targetIdx] = ch;
263
+ const display = this._buildDisplay(filled);
264
+ this.opts.callbacks.setValue(display);
265
+ this._emitModelValue(filled);
266
+
267
+ // Advance caret to next variable slot
268
+ const nextVar = variableIndices.find(i => i > targetIdx);
269
+ const newCaret = nextVar !== undefined ? nextVar : targetIdx + 1;
270
+ this.opts.callbacks.setCaret(newCaret);
271
+
272
+ // Check completion
273
+ const allFilled = variableIndices.every(i => filled[i] !== null);
274
+ if (allFilled) {
275
+ this.opts.callbacks.setCaret(variableIndices[variableIndices.length - 1] + 1);
276
+ }
277
+ }
278
+
279
+ private _handleBackspace(): void {
280
+ const { variableIndices } = this.opts.getCompiled();
281
+ const caret = this.opts.callbacks.getCaret();
282
+ const filled = this._getFilledSlots();
283
+
284
+ // Find the last variable slot before caret
285
+ const targets = variableIndices.filter(i => i < caret);
286
+ const targetIdx = targets[targets.length - 1];
287
+ if (targetIdx === undefined) return;
288
+
289
+ filled[targetIdx] = null;
290
+ const display = this._buildDisplay(filled);
291
+ this.opts.callbacks.setValue(display);
292
+ this._emitModelValue(filled);
293
+ this.opts.callbacks.setCaret(targetIdx);
294
+ }
295
+
296
+ private _handleDelete(): void {
297
+ const { variableIndices } = this.opts.getCompiled();
298
+ const caret = this.opts.callbacks.getCaret();
299
+ const filled = this._getFilledSlots();
300
+
301
+ // Find the variable slot at or after caret
302
+ const targetIdx = variableIndices.find(i => i >= caret);
303
+ if (targetIdx === undefined) return;
304
+
305
+ filled[targetIdx] = null;
306
+ const display = this._buildDisplay(filled);
307
+ this.opts.callbacks.setValue(display);
308
+ this._emitModelValue(filled);
309
+ this.opts.callbacks.setCaret(targetIdx);
310
+ }
311
+
312
+ private _snapCaret(direction: 'prev' | 'next' | 'first' | 'last'): void {
313
+ const { variableIndices } = this.opts.getCompiled();
314
+ if (variableIndices.length === 0) return;
315
+
316
+ const caret = this.opts.callbacks.getCaret();
317
+
318
+ if (direction === 'first') {
319
+ this.opts.callbacks.setCaret(variableIndices[0]);
320
+ return;
321
+ }
322
+ if (direction === 'last') {
323
+ this.opts.callbacks.setCaret(
324
+ variableIndices[variableIndices.length - 1] + 1,
325
+ );
326
+ return;
327
+ }
328
+ if (direction === 'prev') {
329
+ const prev = [...variableIndices].reverse().find(i => i < caret);
330
+ if (prev !== undefined) this.opts.callbacks.setCaret(prev);
331
+ return;
332
+ }
333
+ if (direction === 'next') {
334
+ const next = variableIndices.find(i => i > caret);
335
+ if (next !== undefined) this.opts.callbacks.setCaret(next);
336
+ return;
337
+ }
338
+ }
339
+
340
+ private _pasteText(text: string): void {
341
+ const { variableIndices } = this.opts.getCompiled();
342
+ const { slots } = this.opts.getCompiled();
343
+ const caret = this.opts.callbacks.getCaret();
344
+ const filled = this._getFilledSlots();
345
+
346
+ // Determine which variable slots we start filling from
347
+ const startVarPos = variableIndices.findIndex(i => i >= caret);
348
+ if (startVarPos === -1) return;
349
+
350
+ let textIdx = 0;
351
+ for (let vi = startVarPos; vi < variableIndices.length && textIdx < text.length; vi++) {
352
+ const slotIdx = variableIndices[vi];
353
+ const slot = slots[slotIdx] as Extract<Slot, { kind: 'variable' }>;
354
+ // Skip paste chars that don't match
355
+ while (textIdx < text.length && !slot.re.test(text[textIdx])) {
356
+ textIdx++;
357
+ }
358
+ if (textIdx < text.length) {
359
+ filled[slotIdx] = text[textIdx];
360
+ textIdx++;
361
+ }
362
+ }
363
+
364
+ const display = this._buildDisplay(filled);
365
+ this.opts.callbacks.setValue(display);
366
+ this._emitModelValue(filled);
367
+
368
+ // Set caret after the last filled slot
369
+ let lastFilledVar = variableIndices[startVarPos];
370
+ for (const vi of variableIndices) {
371
+ if (filled[vi] !== null) lastFilledVar = vi;
372
+ }
373
+ const nextVar = variableIndices.find(i => i > lastFilledVar);
374
+ this.opts.callbacks.setCaret(nextVar !== undefined ? nextVar : lastFilledVar + 1);
375
+ }
376
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Predefined mask templates for common use cases.
3
+ *
4
+ * These are convenience string constants — not DI tokens, not hardcoded
5
+ * into the directive. Use them as `[kjMask]="presets.phoneUS"` or spread
6
+ * and extend per your app's locale needs.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { KJ_INPUT_MASK_PRESETS } from '@kouji-ui/core';
11
+ *
12
+ * @Component({ template: `<input kjInputMask [kjMask]="presets.phoneUS" />` })
13
+ * export class MyForm {
14
+ * readonly presets = KJ_INPUT_MASK_PRESETS;
15
+ * }
16
+ * ```
17
+ */
18
+ export const KJ_INPUT_MASK_PRESETS = {
19
+ phoneUS: '(999) 999-9999',
20
+ cardNumber: '9999 9999 9999 9999',
21
+ cardExpiry: '99/99',
22
+ isoDate: '9999-99-99',
23
+ usDate: '99/99/9999',
24
+ } as const;
@@ -0,0 +1,304 @@
1
+ import { Component } from '@angular/core';
2
+ import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
3
+ import { render } from '@testing-library/angular';
4
+ import { describe, expect, it } from 'vitest';
5
+
6
+ import { compileMask } from './input-mask.compile';
7
+ import { MaskEngine } from './input-mask.engine';
8
+ import { KjInputMask } from './input-mask';
9
+
10
+ // ── compileMask ────────────────────────────────────────────────────────────────
11
+
12
+ describe('compileMask', () => {
13
+ const tokens = { '9': /[0-9]/, 'a': /[A-Za-z]/, '*': /[A-Za-z0-9]/ };
14
+
15
+ it('splits a phone mask into literal and variable slots', () => {
16
+ const result = compileMask('(999) 999-9999', tokens);
17
+ expect(result.variableCount).toBe(10);
18
+ expect(result.literalCount).toBe(4);
19
+ expect(result.slots[0]).toEqual({ kind: 'literal', char: '(' });
20
+ expect(result.slots[1]).toMatchObject({ kind: 'variable', tokenId: '9' });
21
+ });
22
+
23
+ it('treats unknown token chars as literals', () => {
24
+ const result = compileMask('X99', tokens);
25
+ // X not in tokens → literal
26
+ expect(result.slots[0]).toEqual({ kind: 'literal', char: 'X' });
27
+ expect(result.slots[1]).toMatchObject({ kind: 'variable' });
28
+ expect(result.slots[2]).toMatchObject({ kind: 'variable' });
29
+ expect(result.variableCount).toBe(2);
30
+ });
31
+
32
+ it('handles escape syntax: \\9 → literal 9', () => {
33
+ const result = compileMask('\\99', tokens);
34
+ expect(result.slots[0]).toEqual({ kind: 'literal', char: '9' });
35
+ expect(result.slots[1]).toMatchObject({ kind: 'variable', tokenId: '9' });
36
+ expect(result.variableCount).toBe(1);
37
+ });
38
+
39
+ it('handles all-literal mask', () => {
40
+ const result = compileMask('---', tokens);
41
+ expect(result.variableCount).toBe(0);
42
+ expect(result.literalCount).toBe(3);
43
+ });
44
+
45
+ it('builds variableIndices correctly', () => {
46
+ // '9-9' → slot 0: variable, slot 1: literal, slot 2: variable
47
+ const result = compileMask('9-9', tokens);
48
+ expect(result.variableIndices).toEqual([0, 2]);
49
+ });
50
+
51
+ it('handles repeated escape sequences', () => {
52
+ const result = compileMask('\\9\\9', tokens);
53
+ expect(result.variableCount).toBe(0);
54
+ expect(result.slots).toEqual([
55
+ { kind: 'literal', char: '9' },
56
+ { kind: 'literal', char: '9' },
57
+ ]);
58
+ });
59
+
60
+ it('handles custom tokens', () => {
61
+ const customTokens = { H: /[0-9A-Fa-f]/ };
62
+ const result = compileMask('#HHHHHH', customTokens);
63
+ expect(result.slots[0]).toEqual({ kind: 'literal', char: '#' });
64
+ expect(result.variableCount).toBe(6);
65
+ const slot = result.slots[1] as Extract<(typeof result.slots)[0], { kind: 'variable' }>;
66
+ expect(slot.re.test('A')).toBe(true);
67
+ expect(slot.re.test('G')).toBe(false);
68
+ });
69
+ });
70
+
71
+ // ── MaskEngine ─────────────────────────────────────────────────────────────────
72
+
73
+ describe('MaskEngine.reconstructFromRaw', () => {
74
+ const tokens = { '9': /[0-9]/, 'a': /[A-Za-z]/, '*': /[A-Za-z0-9]/ };
75
+
76
+ function makeEngine(mask: string, options?: { mode?: 'unmasked' | 'masked'; slotChar?: string }) {
77
+ let _value = '';
78
+ let _caret = 0;
79
+ let _emitted = '';
80
+
81
+ const compiled = compileMask(mask, tokens);
82
+ const engine = new MaskEngine({
83
+ getCompiled: () => compiled,
84
+ getSlotChar: () => options?.slotChar ?? '_',
85
+ getMaskMode: () => options?.mode ?? 'unmasked',
86
+ callbacks: {
87
+ getValue: () => _value,
88
+ setValue: (v) => { _value = v; },
89
+ getCaret: () => _caret,
90
+ setCaret: (p) => { _caret = p; },
91
+ notifyChange: (v) => { _emitted = v; },
92
+ },
93
+ });
94
+
95
+ return { engine, getValue: () => _value, getCaret: () => _caret, getEmitted: () => _emitted };
96
+ }
97
+
98
+ it('reconstructs phone from raw digits', () => {
99
+ const { engine, getValue } = makeEngine('(999) 999-9999');
100
+ engine.applyRawValue('4155551234');
101
+ expect(getValue()).toBe('(415) 555-1234');
102
+ });
103
+
104
+ it('reconstructs partial raw value', () => {
105
+ const { engine, getValue } = makeEngine('(999) 999-9999');
106
+ engine.applyRawValue('415');
107
+ expect(getValue()).toBe('(415) ___-____');
108
+ });
109
+
110
+ it('emits unmasked value by default', () => {
111
+ const { engine, getEmitted } = makeEngine('(999) 999-9999');
112
+ engine.applyRawValue('4155551234');
113
+ expect(getEmitted()).toBe('4155551234');
114
+ });
115
+
116
+ it('emits masked value when mode is masked', () => {
117
+ const { engine, getEmitted } = makeEngine('(999) 999-9999', { mode: 'masked' });
118
+ engine.applyRawValue('4155551234');
119
+ expect(getEmitted()).toBe('(415) 555-1234');
120
+ });
121
+
122
+ it('strips non-matching chars from raw (forgiving intake)', () => {
123
+ const { engine, getValue } = makeEngine('(999) 999-9999');
124
+ engine.applyRawValue('(415) 555-1234');
125
+ expect(getValue()).toBe('(415) 555-1234');
126
+ });
127
+ });
128
+
129
+ describe('MaskEngine keyboard interactions', () => {
130
+ const tokens = { '9': /[0-9]/, 'a': /[A-Za-z]/, '*': /[A-Za-z0-9]/ };
131
+
132
+ function makeEngine(mask: string) {
133
+ let _value = '(___) ___-____';
134
+ let _caret = 1; // starts after '('
135
+ let _emitted = '';
136
+
137
+ const compiled = compileMask(mask, tokens);
138
+ // Initialise display
139
+ const slotChar = '_';
140
+ _value = compiled.slots
141
+ .map(s => s.kind === 'literal' ? s.char : slotChar)
142
+ .join('');
143
+
144
+ const engine = new MaskEngine({
145
+ getCompiled: () => compiled,
146
+ getSlotChar: () => slotChar,
147
+ getMaskMode: () => 'unmasked',
148
+ callbacks: {
149
+ getValue: () => _value,
150
+ setValue: (v) => { _value = v; },
151
+ getCaret: () => _caret,
152
+ setCaret: (p) => { _caret = p; },
153
+ notifyChange: (v) => { _emitted = v; },
154
+ },
155
+ });
156
+
157
+ const key = (k: string) => {
158
+ engine.onKeydown(new KeyboardEvent('keydown', { key: k }));
159
+ };
160
+
161
+ return { engine, getValue: () => _value, getCaret: () => _caret, getEmitted: () => _emitted, key, setCaret: (p: number) => { _caret = p; } };
162
+ }
163
+
164
+ it('types a digit into the first variable slot', () => {
165
+ const { key, getValue, getEmitted } = makeEngine('(999) 999-9999');
166
+ // caret = 1 (first variable slot after '(')
167
+ key('4');
168
+ expect(getValue()[1]).toBe('4');
169
+ expect(getEmitted()).toBe('4');
170
+ });
171
+
172
+ it('rejects a non-matching character', () => {
173
+ const { key, getValue } = makeEngine('(999) 999-9999');
174
+ key('A'); // not a digit
175
+ expect(getValue()[1]).toBe('_');
176
+ });
177
+
178
+ it('backspace clears the previous variable slot', () => {
179
+ const s = makeEngine('(999) 999-9999');
180
+ s.key('4');
181
+ s.setCaret(2); // move to after slot 1
182
+ s.key('Backspace');
183
+ expect(s.getValue()[1]).toBe('_');
184
+ });
185
+ });
186
+
187
+ describe('MaskEngine paste', () => {
188
+ const tokens = { '9': /[0-9]/ };
189
+
190
+ function makeEngine(mask: string) {
191
+ let _value = '';
192
+ let _caret = 0;
193
+
194
+ const compiled = compileMask(mask, tokens);
195
+ _value = compiled.slots
196
+ .map(s => s.kind === 'literal' ? s.char : '_')
197
+ .join('');
198
+
199
+ const engine = new MaskEngine({
200
+ getCompiled: () => compiled,
201
+ getSlotChar: () => '_',
202
+ getMaskMode: () => 'unmasked',
203
+ callbacks: {
204
+ getValue: () => _value,
205
+ setValue: (v) => { _value = v; },
206
+ getCaret: () => _caret,
207
+ setCaret: (p) => { _caret = p; },
208
+ notifyChange: () => {},
209
+ },
210
+ });
211
+
212
+ return { engine, getValue: () => _value };
213
+ }
214
+
215
+ it('pastes digits filling the mask', () => {
216
+ const { engine, getValue } = makeEngine('9999');
217
+ const event = {
218
+ preventDefault: () => {},
219
+ clipboardData: { getData: () => '1234' },
220
+ } as unknown as ClipboardEvent;
221
+ engine.onPaste(event);
222
+ expect(getValue()).toBe('1234');
223
+ });
224
+
225
+ it('strips non-digit characters from paste', () => {
226
+ const { engine, getValue } = makeEngine('9999');
227
+ const event = {
228
+ preventDefault: () => {},
229
+ clipboardData: { getData: () => '12-34' },
230
+ } as unknown as ClipboardEvent;
231
+ engine.onPaste(event);
232
+ expect(getValue()).toBe('1234');
233
+ });
234
+ });
235
+
236
+ // ── KjInputMask directive ─────────────────────────────────────────────────────
237
+
238
+ describe('KjInputMask directive', () => {
239
+ it('renders with type="text"', async () => {
240
+ const { container } = await render(
241
+ `<input kjInputMask kjMask="(999) 999-9999" />`,
242
+ { imports: [KjInputMask] },
243
+ );
244
+ expect(container.querySelector('input')!.getAttribute('type')).toBe('text');
245
+ });
246
+
247
+ it('sets inputmode=numeric for all-digit mask', async () => {
248
+ const { container } = await render(
249
+ `<input kjInputMask kjMask="9999" />`,
250
+ { imports: [KjInputMask] },
251
+ );
252
+ expect(container.querySelector('input')!.getAttribute('inputmode')).toBe('numeric');
253
+ });
254
+
255
+ it('sets inputmode=text for mixed mask', async () => {
256
+ const { container } = await render(
257
+ `<input kjInputMask kjMask="aaa-999" />`,
258
+ { imports: [KjInputMask] },
259
+ );
260
+ expect(container.querySelector('input')!.getAttribute('inputmode')).toBe('text');
261
+ });
262
+
263
+ it('sets placeholder from mask with slot chars', async () => {
264
+ const { container } = await render(
265
+ `<input kjInputMask kjMask="(999) 999-9999" />`,
266
+ { imports: [KjInputMask] },
267
+ );
268
+ expect(container.querySelector('input')!.getAttribute('placeholder')).toBe(
269
+ '(___) ___-____',
270
+ );
271
+ });
272
+
273
+ it('emits mask validation error when partially filled', async () => {
274
+ const ctrl = new FormControl('415');
275
+ @Component({
276
+ standalone: true,
277
+ imports: [KjInputMask, ReactiveFormsModule],
278
+ template: `<input kjInputMask kjMask="(999) 999-9999" [formControl]="ctrl" />`,
279
+ })
280
+ class Host {
281
+ ctrl = ctrl;
282
+ }
283
+ await render(Host);
284
+ // The mask validator should detect partial fill
285
+ // raw='415' = 3 chars out of 10 required
286
+ expect(ctrl.errors?.['mask']).toBeDefined();
287
+ expect(ctrl.errors?.['mask'].required).toBe(10);
288
+ });
289
+
290
+ it('returns null validator error when empty (required handles it)', async () => {
291
+ const ctrl = new FormControl('', [Validators.required]);
292
+ @Component({
293
+ standalone: true,
294
+ imports: [KjInputMask, ReactiveFormsModule],
295
+ template: `<input kjInputMask kjMask="(999) 999-9999" [formControl]="ctrl" />`,
296
+ })
297
+ class Host {
298
+ ctrl = ctrl;
299
+ }
300
+ await render(Host);
301
+ expect(ctrl.errors?.['mask']).toBeUndefined();
302
+ expect(ctrl.errors?.['required']).toBeDefined();
303
+ });
304
+ });