@konce-pt/react 0.11.2 → 0.14.0

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 (382) hide show
  1. package/README.md +128 -64
  2. package/dist/accordion/accordion.d.ts +14 -6
  3. package/dist/accordion/accordion.js +25 -17
  4. package/dist/accordion/llms.txt +22 -10
  5. package/dist/alert/alert.d.ts +2 -2
  6. package/dist/alert/alert.js +3 -2
  7. package/dist/alert/llms.txt +10 -9
  8. package/dist/app-shell/app-shell.d.ts +14 -14
  9. package/dist/app-shell/app-shell.js +22 -15
  10. package/dist/app-shell/llms.txt +16 -15
  11. package/dist/auth/auth-types.d.ts +6 -6
  12. package/dist/auth/auth.d.ts +2 -2
  13. package/dist/auth/auth.js +24 -15
  14. package/dist/auth/llms.txt +12 -11
  15. package/dist/autocomplete/autocomplete.d.ts +19 -6
  16. package/dist/autocomplete/autocomplete.js +32 -11
  17. package/dist/autocomplete/llms.txt +19 -5
  18. package/dist/avatar/avatar.d.ts +6 -6
  19. package/dist/avatar/avatar.js +6 -5
  20. package/dist/avatar/llms.txt +10 -10
  21. package/dist/avatar-group/avatar-group.d.ts +4 -4
  22. package/dist/avatar-group/avatar-group.js +4 -3
  23. package/dist/avatar-group/llms.txt +8 -8
  24. package/dist/backdrop/backdrop.d.ts +7 -26
  25. package/dist/backdrop/backdrop.js +32 -27
  26. package/dist/backdrop/llms.txt +27 -27
  27. package/dist/badge/badge.d.ts +3 -3
  28. package/dist/badge/badge.js +1 -1
  29. package/dist/badge/llms.txt +11 -11
  30. package/dist/bottom-sheet/bottom-sheet.d.ts +8 -3
  31. package/dist/bottom-sheet/bottom-sheet.js +5 -4
  32. package/dist/bottom-sheet/llms.txt +11 -8
  33. package/dist/breadcrumb/breadcrumb.d.ts +8 -2
  34. package/dist/breadcrumb/breadcrumb.js +11 -3
  35. package/dist/breadcrumb/llms.txt +10 -2
  36. package/dist/button/button.d.ts +3 -3
  37. package/dist/button/button.js +10 -9
  38. package/dist/button/llms.txt +27 -19
  39. package/dist/button-group/button-group.d.ts +18 -5
  40. package/dist/button-group/button-group.js +54 -6
  41. package/dist/button-group/llms.txt +27 -18
  42. package/dist/card/card.d.ts +8 -8
  43. package/dist/card/card.js +10 -9
  44. package/dist/card/llms.txt +20 -20
  45. package/dist/carousel/carousel.d.ts +6 -6
  46. package/dist/carousel/carousel.js +8 -5
  47. package/dist/carousel/llms.txt +10 -10
  48. package/dist/charts/chart-legend.d.ts +3 -3
  49. package/dist/charts/chart-legend.js +2 -1
  50. package/dist/charts/chart-tooltip.d.ts +3 -3
  51. package/dist/charts/chart-tooltip.js +1 -1
  52. package/dist/charts/chart.d.ts +4 -3
  53. package/dist/charts/chart.js +18 -10
  54. package/dist/charts/llms.txt +10 -7
  55. package/dist/charts-entry/public-api.js +2 -2
  56. package/dist/chat/chat.d.ts +17 -10
  57. package/dist/chat/chat.js +77 -23
  58. package/dist/chat/llms.txt +39 -15
  59. package/dist/chat-composer/chat-composer.d.ts +20 -7
  60. package/dist/chat-composer/chat-composer.js +29 -7
  61. package/dist/chat-composer/llms.txt +20 -11
  62. package/dist/chat-dock/chat-dock.d.ts +11 -4
  63. package/dist/chat-dock/chat-dock.js +10 -6
  64. package/dist/chat-dock/llms.txt +15 -14
  65. package/dist/chat-entry/public-api.js +1 -1
  66. package/dist/chat-message/chat-message.d.ts +12 -5
  67. package/dist/chat-message/chat-message.js +19 -8
  68. package/dist/chat-message/llms.txt +16 -15
  69. package/dist/chat-typing/chat-typing.d.ts +2 -2
  70. package/dist/chat-typing/chat-typing.js +4 -3
  71. package/dist/chat-typing/llms.txt +6 -6
  72. package/dist/checkbox/checkbox.d.ts +13 -6
  73. package/dist/checkbox/checkbox.js +31 -5
  74. package/dist/checkbox/llms.txt +26 -11
  75. package/dist/chip/chip.d.ts +4 -4
  76. package/dist/chip/chip.js +3 -2
  77. package/dist/chip/llms.txt +11 -11
  78. package/dist/chips-input/chips-input.d.ts +6 -4
  79. package/dist/chips-input/chips-input.js +47 -12
  80. package/dist/chips-input/llms.txt +18 -8
  81. package/dist/clock/clock.d.ts +11 -6
  82. package/dist/clock/clock.js +38 -8
  83. package/dist/clock/llms.txt +12 -8
  84. package/dist/color-picker/color-picker.d.ts +9 -5
  85. package/dist/color-picker/color-picker.js +37 -18
  86. package/dist/color-picker/llms.txt +8 -5
  87. package/dist/confirm/confirm-container.d.ts +3 -3
  88. package/dist/confirm/confirm-container.js +6 -5
  89. package/dist/confirm/confirm-store.d.ts +4 -4
  90. package/dist/confirm/confirm-store.js +11 -5
  91. package/dist/confirm/llms.txt +8 -7
  92. package/dist/context-menu/context-menu.d.ts +1 -1
  93. package/dist/context-menu/context-menu.js +37 -14
  94. package/dist/context-menu/llms.txt +10 -7
  95. package/dist/data-table/data-table-types.d.ts +5 -5
  96. package/dist/data-table/data-table.d.ts +37 -5
  97. package/dist/data-table/data-table.js +426 -119
  98. package/dist/data-table/export-csv.d.ts +3 -3
  99. package/dist/data-table/export-csv.js +8 -10
  100. package/dist/data-table/llms.txt +127 -79
  101. package/dist/data-view/data-view.d.ts +13 -5
  102. package/dist/data-view/data-view.js +68 -6
  103. package/dist/data-view/llms.txt +29 -10
  104. package/dist/date-range/date-range.d.ts +20 -8
  105. package/dist/date-range/date-range.js +86 -63
  106. package/dist/date-range/llms.txt +32 -18
  107. package/dist/datepicker/datepicker.d.ts +22 -8
  108. package/dist/datepicker/datepicker.js +103 -71
  109. package/dist/datepicker/llms.txt +36 -22
  110. package/dist/dialog/dialog.d.ts +4 -4
  111. package/dist/dialog/dialog.js +32 -11
  112. package/dist/dialog/llms.txt +13 -10
  113. package/dist/divider/divider.d.ts +7 -1
  114. package/dist/divider/divider.js +3 -2
  115. package/dist/divider/llms.txt +4 -3
  116. package/dist/drawer/drawer.d.ts +9 -4
  117. package/dist/drawer/drawer.js +5 -4
  118. package/dist/drawer/llms.txt +13 -10
  119. package/dist/empty/empty.d.ts +2 -2
  120. package/dist/empty/empty.js +1 -1
  121. package/dist/empty/llms.txt +9 -9
  122. package/dist/fab/fab.d.ts +1 -1
  123. package/dist/fab/fab.js +1 -1
  124. package/dist/fab/llms.txt +10 -6
  125. package/dist/fieldset/fieldset.d.ts +18 -4
  126. package/dist/fieldset/fieldset.js +12 -6
  127. package/dist/fieldset/llms.txt +26 -9
  128. package/dist/file-upload/file-upload.d.ts +8 -2
  129. package/dist/file-upload/file-upload.js +22 -11
  130. package/dist/file-upload/llms.txt +12 -9
  131. package/dist/form-field/form-field-context.d.ts +4 -4
  132. package/dist/form-field/form-field.d.ts +12 -4
  133. package/dist/form-field/form-field.js +27 -6
  134. package/dist/form-field/llms.txt +25 -11
  135. package/dist/galleria/galleria.d.ts +3 -3
  136. package/dist/galleria/galleria.js +9 -6
  137. package/dist/galleria/llms.txt +7 -7
  138. package/dist/grid/col.d.ts +4 -4
  139. package/dist/grid/col.js +2 -2
  140. package/dist/grid/flex.d.ts +3 -3
  141. package/dist/grid/flex.js +1 -1
  142. package/dist/grid/grid.d.ts +7 -7
  143. package/dist/grid/grid.js +3 -3
  144. package/dist/grid/layout.d.ts +2 -2
  145. package/dist/grid/layout.js +2 -2
  146. package/dist/grid/llms.txt +15 -15
  147. package/dist/grid-entry/public-api.js +1 -1
  148. package/dist/i18n/i18n-context.d.ts +6 -6
  149. package/dist/i18n/i18n-context.js +27 -5
  150. package/dist/icon/icon-registry.d.ts +3 -3
  151. package/dist/icon/icon-registry.js +3 -3
  152. package/dist/icon/icon.d.ts +4 -4
  153. package/dist/icon/icon.js +10 -7
  154. package/dist/icon/llms.txt +13 -13
  155. package/dist/icon-button/icon-button.d.ts +2 -2
  156. package/dist/icon-button/icon-button.js +1 -1
  157. package/dist/icon-button/llms.txt +9 -5
  158. package/dist/icons-entry/public-api.d.ts +2 -2
  159. package/dist/icons-entry/public-api.js +3 -3
  160. package/dist/image/image.js +5 -5
  161. package/dist/image/llms.txt +13 -13
  162. package/dist/input/input.d.ts +18 -5
  163. package/dist/input/input.js +22 -6
  164. package/dist/input/llms.txt +25 -11
  165. package/dist/input-mask/input-mask.d.ts +7 -2
  166. package/dist/input-mask/input-mask.js +17 -43
  167. package/dist/input-mask/llms.txt +8 -5
  168. package/dist/input-number/input-number.d.ts +11 -9
  169. package/dist/input-number/input-number.js +25 -21
  170. package/dist/input-number/llms.txt +17 -14
  171. package/dist/input-otp/input-otp.d.ts +18 -5
  172. package/dist/input-otp/input-otp.js +48 -24
  173. package/dist/input-otp/llms.txt +18 -7
  174. package/dist/internal/calendar-focus.d.ts +1 -1
  175. package/dist/internal/calendar-focus.js +3 -3
  176. package/dist/internal/cn.d.ts +1 -1
  177. package/dist/internal/mask.d.ts +14 -0
  178. package/dist/internal/mask.js +62 -0
  179. package/dist/internal/number-group.d.ts +22 -0
  180. package/dist/internal/number-group.js +53 -0
  181. package/dist/internal/option-key.d.ts +6 -0
  182. package/dist/internal/option-key.js +8 -0
  183. package/dist/internal/option-nav.d.ts +4 -4
  184. package/dist/internal/option-nav.js +5 -5
  185. package/dist/internal/overlay-container.js +1 -1
  186. package/dist/internal/overlay.d.ts +3 -3
  187. package/dist/internal/overlay.js +40 -10
  188. package/dist/internal/password-strength.d.ts +6 -0
  189. package/dist/internal/password-strength.js +74 -0
  190. package/dist/internal/roving.d.ts +2 -2
  191. package/dist/internal/roving.js +3 -3
  192. package/dist/internal/scroll-lock.d.ts +6 -0
  193. package/dist/internal/scroll-lock.js +35 -0
  194. package/dist/internal/slot-children.d.ts +13 -0
  195. package/dist/internal/slot-children.js +31 -0
  196. package/dist/internal/slot.d.ts +3 -3
  197. package/dist/internal/slot.js +2 -2
  198. package/dist/internal/step.d.ts +12 -0
  199. package/dist/internal/step.js +31 -0
  200. package/dist/internal/tween.d.ts +5 -5
  201. package/dist/internal/tween.js +7 -8
  202. package/dist/internal/types.d.ts +2 -2
  203. package/dist/internal/use-breakpoint.d.ts +2 -2
  204. package/dist/internal/use-breakpoint.js +4 -4
  205. package/dist/internal/use-controllable-state.d.ts +5 -0
  206. package/dist/internal/use-controllable-state.js +25 -4
  207. package/dist/internal/use-id.d.ts +2 -2
  208. package/dist/internal/use-id.js +2 -2
  209. package/dist/internal/use-modal-focus.d.ts +2 -1
  210. package/dist/internal/use-modal-focus.js +14 -3
  211. package/dist/knob/knob.d.ts +7 -4
  212. package/dist/knob/knob.js +27 -22
  213. package/dist/knob/llms.txt +18 -11
  214. package/dist/listbox/listbox.d.ts +8 -6
  215. package/dist/listbox/listbox.js +37 -17
  216. package/dist/listbox/llms.txt +12 -9
  217. package/dist/loader/llms.txt +56 -0
  218. package/dist/loader/loader.d.ts +49 -0
  219. package/dist/loader/loader.js +68 -0
  220. package/dist/map/llms.txt +30 -23
  221. package/dist/map/map-create-dialog.d.ts +4 -4
  222. package/dist/map/map-create-dialog.js +36 -28
  223. package/dist/map/map-hours-field.d.ts +4 -4
  224. package/dist/map/map-hours-field.js +8 -7
  225. package/dist/map/map-image-field.d.ts +2 -2
  226. package/dist/map/map-image-field.js +6 -5
  227. package/dist/map/map-legend.d.ts +4 -4
  228. package/dist/map/map-legend.js +3 -2
  229. package/dist/map/map-poi-card.d.ts +3 -3
  230. package/dist/map/map-poi-card.js +10 -9
  231. package/dist/map/map-search.d.ts +17 -6
  232. package/dist/map/map-search.js +70 -43
  233. package/dist/map/map.d.ts +25 -13
  234. package/dist/map/map.js +86 -57
  235. package/dist/map/ports.d.ts +9 -9
  236. package/dist/map/ports.js +6 -6
  237. package/dist/map-entry/public-api.js +2 -2
  238. package/dist/megamenu/llms.txt +18 -7
  239. package/dist/megamenu/megamenu.d.ts +12 -6
  240. package/dist/megamenu/megamenu.js +23 -14
  241. package/dist/mention/llms.txt +15 -15
  242. package/dist/mention/mention.d.ts +5 -5
  243. package/dist/mention/mention.js +2 -2
  244. package/dist/mention/mentions.d.ts +1 -1
  245. package/dist/mention/mentions.js +1 -1
  246. package/dist/menu/llms.txt +12 -12
  247. package/dist/menu/menu-item.d.ts +4 -4
  248. package/dist/menu/menu-keys.d.ts +2 -2
  249. package/dist/menu/menu-keys.js +5 -5
  250. package/dist/menu/menu-rows.js +1 -0
  251. package/dist/menu/menu.d.ts +2 -2
  252. package/dist/menu/menu.js +15 -8
  253. package/dist/menubar/llms.txt +19 -8
  254. package/dist/menubar/menubar.d.ts +11 -5
  255. package/dist/menubar/menubar.js +23 -11
  256. package/dist/meter-group/llms.txt +12 -12
  257. package/dist/meter-group/meter-group.d.ts +2 -2
  258. package/dist/meter-group/meter-group.js +2 -1
  259. package/dist/motion/motion-context.d.ts +22 -0
  260. package/dist/motion/motion-context.js +28 -0
  261. package/dist/order-list/llms.txt +24 -9
  262. package/dist/order-list/order-list.d.ts +12 -4
  263. package/dist/order-list/order-list.js +123 -25
  264. package/dist/paginator/llms.txt +8 -8
  265. package/dist/paginator/paginator.js +7 -4
  266. package/dist/panel/llms.txt +25 -13
  267. package/dist/panel/panel.d.ts +11 -3
  268. package/dist/panel/panel.js +12 -7
  269. package/dist/password/llms.txt +19 -7
  270. package/dist/password/password.d.ts +2 -2
  271. package/dist/password/password.js +27 -16
  272. package/dist/peek/llms.txt +11 -11
  273. package/dist/peek/peek.d.ts +6 -6
  274. package/dist/peek/peek.js +41 -19
  275. package/dist/pick-list/llms.txt +35 -12
  276. package/dist/pick-list/pick-list.d.ts +12 -4
  277. package/dist/pick-list/pick-list.js +128 -24
  278. package/dist/popover/llms.txt +13 -10
  279. package/dist/popover/popover.d.ts +6 -4
  280. package/dist/popover/popover.js +14 -7
  281. package/dist/progress/llms.txt +17 -17
  282. package/dist/progress/progress.d.ts +7 -7
  283. package/dist/progress/progress.js +7 -6
  284. package/dist/public-api.d.ts +16 -4
  285. package/dist/public-api.js +13 -7
  286. package/dist/radio-group/llms.txt +16 -6
  287. package/dist/radio-group/radio-group.d.ts +12 -3
  288. package/dist/radio-group/radio-group.js +23 -4
  289. package/dist/rating/llms.txt +20 -13
  290. package/dist/rating/rating-pointer.d.ts +16 -0
  291. package/dist/rating/rating-pointer.js +26 -0
  292. package/dist/rating/rating.d.ts +6 -6
  293. package/dist/rating/rating.js +77 -12
  294. package/dist/rich-text/lists.d.ts +2 -2
  295. package/dist/rich-text/lists.js +7 -7
  296. package/dist/rich-text/llms.txt +35 -12
  297. package/dist/rich-text/rich-text.d.ts +13 -14
  298. package/dist/rich-text/rich-text.js +311 -142
  299. package/dist/rich-text/sanitize.d.ts +11 -2
  300. package/dist/rich-text/sanitize.js +80 -32
  301. package/dist/rich-text/tables.d.ts +3 -3
  302. package/dist/rich-text/tables.js +3 -3
  303. package/dist/roadmap/llms.txt +23 -17
  304. package/dist/roadmap/roadmap.d.ts +6 -6
  305. package/dist/roadmap/roadmap.js +102 -25
  306. package/dist/roadmap-entry/public-api.js +2 -2
  307. package/dist/scroll-top/llms.txt +6 -6
  308. package/dist/scroll-top/scroll-top.d.ts +1 -1
  309. package/dist/scroll-top/scroll-top.js +2 -1
  310. package/dist/select/llms.txt +22 -12
  311. package/dist/select/select.d.ts +10 -8
  312. package/dist/select/select.js +87 -47
  313. package/dist/shared/types.d.ts +22 -1
  314. package/dist/shared/types.js +9 -1
  315. package/dist/sidenav/llms.txt +9 -9
  316. package/dist/sidenav/sidenav.d.ts +2 -3
  317. package/dist/sidenav/sidenav.js +5 -4
  318. package/dist/skeleton/llms.txt +3 -3
  319. package/dist/skeleton/skeleton.d.ts +1 -1
  320. package/dist/skeleton/skeleton.js +1 -1
  321. package/dist/slider/llms.txt +34 -12
  322. package/dist/slider/slider-drag.d.ts +26 -0
  323. package/dist/slider/slider-drag.js +79 -0
  324. package/dist/slider/slider.d.ts +8 -5
  325. package/dist/slider/slider.js +38 -8
  326. package/dist/speech/llms.txt +97 -0
  327. package/dist/speech/speech-button.d.ts +44 -0
  328. package/dist/speech/speech-button.js +86 -0
  329. package/dist/speech/speech-wave.d.ts +19 -0
  330. package/dist/speech/speech-wave.js +16 -0
  331. package/dist/speech/use-speech-recognition.d.ts +81 -0
  332. package/dist/speech/use-speech-recognition.js +179 -0
  333. package/dist/speed-dial/llms.txt +14 -8
  334. package/dist/speed-dial/speed-dial.d.ts +1 -1
  335. package/dist/speed-dial/speed-dial.js +3 -2
  336. package/dist/spinner/llms.txt +7 -7
  337. package/dist/spinner/spinner.d.ts +1 -1
  338. package/dist/spinner/spinner.js +2 -1
  339. package/dist/split-button/llms.txt +21 -15
  340. package/dist/split-button/split-button.d.ts +2 -2
  341. package/dist/split-button/split-button.js +13 -6
  342. package/dist/splitter/llms.txt +11 -11
  343. package/dist/splitter/splitter.d.ts +3 -3
  344. package/dist/splitter/splitter.js +25 -10
  345. package/dist/stepper/llms.txt +35 -13
  346. package/dist/stepper/stepper.d.ts +6 -6
  347. package/dist/stepper/stepper.js +60 -13
  348. package/dist/switch/llms.txt +15 -5
  349. package/dist/switch/switch.d.ts +10 -3
  350. package/dist/switch/switch.js +18 -4
  351. package/dist/switch-group/llms.txt +11 -8
  352. package/dist/switch-group/switch-group.d.ts +6 -4
  353. package/dist/switch-group/switch-group.js +9 -7
  354. package/dist/tabs/llms.txt +12 -9
  355. package/dist/tabs/tabs.d.ts +5 -5
  356. package/dist/tabs/tabs.js +36 -20
  357. package/dist/textarea/llms.txt +23 -12
  358. package/dist/textarea/textarea.d.ts +19 -6
  359. package/dist/textarea/textarea.js +22 -7
  360. package/dist/timeline/llms.txt +4 -4
  361. package/dist/timeline/timeline.d.ts +1 -1
  362. package/dist/timeline/timeline.js +1 -1
  363. package/dist/toast/llms.txt +50 -17
  364. package/dist/toast/toast-container.d.ts +9 -3
  365. package/dist/toast/toast-container.js +18 -9
  366. package/dist/toast/toast-store.d.ts +42 -2
  367. package/dist/toast/toast-store.js +36 -1
  368. package/dist/toolbar/llms.txt +7 -7
  369. package/dist/toolbar/toolbar.d.ts +3 -3
  370. package/dist/toolbar/toolbar.js +2 -2
  371. package/dist/tooltip/llms.txt +14 -12
  372. package/dist/tooltip/tooltip.d.ts +5 -4
  373. package/dist/tooltip/tooltip.js +100 -41
  374. package/dist/tree/llms.txt +25 -11
  375. package/dist/tree/tree-keys.d.ts +5 -5
  376. package/dist/tree/tree-keys.js +7 -7
  377. package/dist/tree/tree.d.ts +17 -5
  378. package/dist/tree/tree.js +62 -20
  379. package/dist/update/llms.txt +54 -0
  380. package/dist/update/update.d.ts +49 -0
  381. package/dist/update/update.js +82 -0
  382. package/package.json +18 -15
@@ -1,11 +1,15 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useCallback } from 'react';
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useCallback, useRef } from 'react';
3
4
  import { cn } from "../internal/cn.js";
5
+ import { composeRefs } from "../internal/compose-refs.js";
6
+ import { KptSpeechButton } from "../speech/speech-button.js";
7
+ import { useKptSpeechRecognition } from "../speech/use-speech-recognition.js";
4
8
  import { joinIds, useKptFieldA11y } from "../form-field/form-field-context.js";
5
9
  /**
6
10
  * Pole tekstowe Koncept UI. Renderuje natywny `<input>` w hoście `.kpt-input-host`.
7
11
  *
8
- * Działa kontrolowane (`value` + `onValueChange`) i niekontrolowane (`defaultValue`) —
12
+ * Działa kontrolowane (`value` + `onValueChange`) i niekontrolowane (`defaultValue`) -
9
13
  * w drugim trybie wartość należy do DOM, więc natywny `<form>`, `<form action>`, reset
10
14
  * formularza i React Hook Form (`register`) działają bez adaptera.
11
15
  *
@@ -16,10 +20,22 @@ import { joinIds, useKptFieldA11y } from "../form-field/form-field-context.js";
16
20
  * <KptInput name="email" type="email" defaultValue="" />
17
21
  * <KptInput value={email} onValueChange={setEmail} invalid={!ok} touched={blurred} />
18
22
  */
19
- export function KptInput({ type = 'text', required = false, invalid = false, touched, onValueChange, onTouch, hostProps, className, id, onChange, onBlur, 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedBy, ...rest }) {
23
+ export function KptInput({ type = 'text', required = false, invalid = false, touched, onValueChange, onTouch, hostProps, className, id, onChange, onBlur, ref, speech = false, speechLang, speechColor, speechMode = 'insert', 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedBy, ...rest }) {
20
24
  const { className: hostClassName, ...hostRest } = hostProps ?? {};
21
25
  const field = useKptFieldA11y(id, 'kpt-input');
22
- // Stan nieprawidłowy pokazujemy dopiero po dotknięciu pola — jak natywne `:user-invalid`.
26
+ const innerRef = useRef(null);
27
+ // Tekst zatwierdzony wchodzi przez zdarzenie `input` pola, więc `onChange` dostaje go jak wpisany;
28
+ // tymczasowy jest tylko na ekranie. Bez `speech` kontroler nie tworzy sesji.
29
+ const mic = useKptSpeechRecognition({
30
+ enabled: speech,
31
+ target: innerRef,
32
+ lang: speechLang,
33
+ mode: speechMode,
34
+ continuous: speechMode !== 'replace',
35
+ disabled: !!rest.disabled || !!rest.readOnly,
36
+ });
37
+ const speechState = speech && mic.available ? (mic.listening ? 'listening' : 'idle') : undefined;
38
+ // Stan nieprawidłowy pokazujemy dopiero po dotknięciu pola - jak natywne `:user-invalid`.
23
39
  // Bez `touched` nie ma czego bramkować, więc decyduje samo `invalid`.
24
40
  const showInvalid = invalid && (touched ?? true);
25
41
  const handleChange = useCallback((event) => {
@@ -30,5 +46,5 @@ export function KptInput({ type = 'text', required = false, invalid = false, tou
30
46
  onBlur?.(event);
31
47
  onTouch?.();
32
48
  }, [onBlur, onTouch]);
33
- return (_jsx("span", { className: cn('kpt-input-host', hostClassName), ...hostRest, children: _jsx("input", { className: cn('kpt-input__field', className), id: field.id, "aria-describedby": joinIds(ariaDescribedBy, field.describedBy), type: type, "aria-required": required || undefined, "aria-invalid": ariaInvalid ?? (showInvalid || undefined), onChange: handleChange, onBlur: handleBlur, ...rest }) }));
49
+ return (_jsxs("span", { className: cn('kpt-input-host', hostClassName), "data-speech": speechState, ...hostRest, children: [_jsx("input", { className: cn('kpt-input__field', className), id: field.id, "aria-describedby": joinIds(ariaDescribedBy, field.describedBy), type: type, "aria-required": required || undefined, "aria-invalid": ariaInvalid ?? (showInvalid || undefined), onChange: handleChange, onBlur: handleBlur, ref: composeRefs(innerRef, ref), ...rest }), speech ? (_jsx(KptSpeechButton, { listeningColor: speechColor, speech: mic, size: "sm", unsupported: "hide", disabled: !!rest.disabled || !!rest.readOnly, hostProps: { className: 'kpt-input__speech' } })) : null] }));
34
50
  }
@@ -5,28 +5,28 @@ Import: `import { KptInput } from '@konce-pt/react';`
5
5
 
6
6
  ## DOM
7
7
  <span class="kpt-input-host">
8
- <input class="kpt-input__field" id="…" type="text">
8
+ <input class="kpt-input__field" id="..." type="text">
9
9
  </span>
10
10
 
11
11
  ## Props
12
12
  - `type`: 'text' | 'email' | 'password' | 'number' | 'search' | 'tel' | 'url' | 'time' (default 'text')
13
- - `value` / `defaultValue` — controlled or uncontrolled, exactly like a native input
14
- - `onValueChange(value)` — the value alone; `onChange` still fires with the full event
15
- - `onTouch()` — fires on blur, the moment the field becomes touched
16
- - `required`: boolean — see the note below
17
- - `invalid`: boolean, `touched`: boolean — the error state
18
- - `hostProps` — props for the `.kpt-input-host` wrapper
13
+ - `value` / `defaultValue` - controlled or uncontrolled, exactly like a native input
14
+ - `onValueChange(value)` - the value alone; `onChange` still fires with the full event
15
+ - `onTouch()` - fires on blur, the moment the field becomes touched
16
+ - `required`: boolean - see the note below
17
+ - `invalid`: boolean, `touched`: boolean - the error state
18
+ - `hostProps` - props for the `.kpt-input-host` wrapper
19
19
  - `className`, `ref`, `name`, `placeholder`, `autoComplete` and every other `<input>` prop go to
20
20
  the inner `<input>`
21
21
 
22
22
  ## Controlled and uncontrolled
23
23
  Pass `value` and the field is controlled. Pass `defaultValue` (or nothing) and the value belongs
24
- to the DOM, so a native `<form>`, `<form action={…}>`, a form reset and React Hook Form's
25
- `register()` all work without an adapter — there is no internal mirror of the value to fight with.
24
+ to the DOM, so a native `<form>`, `<form action={...}>`, a form reset and React Hook Form's
25
+ `register()` all work without an adapter - there is no internal mirror of the value to fight with.
26
26
 
27
27
  ## The error state
28
28
  `aria-invalid="true"` is set when `invalid` is true. Pass `touched` as well to reproduce the
29
- Angular port's gate — the error then shows only after the field has been touched, like the native
29
+ Angular port's gate - the error then shows only after the field has been touched, like the native
30
30
  `:user-invalid`. With `touched` omitted there is nothing to gate on, so `invalid` decides alone;
31
31
  that is the common case in React, where the form library already knows when an error is ready.
32
32
  The style hangs off `aria-invalid`, never off a class.
@@ -55,5 +55,19 @@ added to `aria-describedby` (merged with your own). Without a wrapper, name it w
55
55
  `--kpt-form-field-{bg,text,placeholder,border,border-hover,border-focus,border-error,radius}`.
56
56
 
57
57
  ## Accessibility
58
- A native `<input>` — full keyboard and screen reader support. `aria-required` marks the field as
58
+ A native `<input>` - full keyboard and screen reader support. `aria-required` marks the field as
59
59
  required, `aria-invalid` marks it as erroneous, and the focus ring comes from `@konce-pt/styles`.
60
+
61
+ ## Dictation
62
+ - `speech`: boolean = false - a microphone at the end of the field (`KptSpeechButton`, Web Speech
63
+ API). Without browser support it is not shown and the field keeps its normal padding.
64
+ - `speechLang`: string - BCP 47 tag; without it from `KptI18nProvider`.
65
+ - `speechColor`: `'primary' | 'danger' | 'success' | 'warning' | 'info'` = `'primary'` - colour of "stop" and the wave while listening.
66
+ - `speechMode`: `'insert' | 'append' | 'replace'` = `'insert'`. `replace` clears the field the moment
67
+ the microphone is clicked (undoable with Ctrl+Z) and stops after one utterance - the search pattern.
68
+
69
+ Interim words are visible in the field but never reach your state; final text comes through
70
+ `onChange` / `onValueChange` like typed text.
71
+
72
+ <KptInput value={title} onValueChange={setTitle} speech />
73
+ <KptInput type="search" speech speechMode="replace" />
@@ -1,10 +1,15 @@
1
1
  import type { ComponentPropsWithRef } from 'react';
2
- export interface KptInputMaskProps extends Omit<ComponentPropsWithRef<'input'>, 'value' | 'defaultValue' | 'onChange'> {
2
+ export interface KptInputMaskProps extends Omit<ComponentPropsWithRef<'input'>, 'value' | 'defaultValue' | 'onChange' | 'required'> {
3
3
  value?: string;
4
4
  defaultValue?: string;
5
5
  onValueChange?: (value: string) => void;
6
6
  /** Wzorzec maski; pozostałe znaki to literały (spacje, nawiasy, myślniki). */
7
7
  mask?: string;
8
+ /**
9
+ * Pole wymagane - `aria-required`, a nie natywne `required` (jak w `KptInput`): natywne
10
+ * blokowałoby wysyłkę i uruchamiało walidację przeglądarki obok Twojej.
11
+ */
12
+ required?: boolean;
8
13
  invalid?: boolean;
9
14
  touched?: boolean;
10
15
  onTouch?: () => void;
@@ -16,4 +21,4 @@ export interface KptInputMaskProps extends Omit<ComponentPropsWithRef<'input'>,
16
21
  * @example
17
22
  * <KptInputMask mask="(999) 999-99-99" value={phone} onValueChange={setPhone} />
18
23
  */
19
- export declare function KptInputMask({ value, defaultValue, onValueChange, mask, placeholder, invalid, touched, onTouch, disabled, className, id, onBlur, 'aria-describedby': ariaDescribedBy, inputMode, ...rest }: Readonly<KptInputMaskProps>): import("react").JSX.Element;
24
+ export declare function KptInputMask({ value, defaultValue, onValueChange, mask, placeholder, required, invalid, touched, onTouch, disabled, className, id, onBlur, 'aria-describedby': ariaDescribedBy, inputMode, ...rest }: Readonly<KptInputMaskProps>): import("react").JSX.Element;
@@ -1,13 +1,9 @@
1
+ 'use client';
1
2
  import { jsx as _jsx } from "react/jsx-runtime";
2
3
  import { cn } from "../internal/cn.js";
3
4
  import { useControllableState } from "../internal/use-controllable-state.js";
4
5
  import { joinIds, useKptFieldA11y } from "../form-field/form-field-context.js";
5
- /** Tokeny maski: `9` = cyfra, `a` = litera, `*` = znak alfanumeryczny. */
6
- const TOKENS = {
7
- '9': /\d/,
8
- a: /[a-zA-Z]/,
9
- '*': /[a-zA-Z0-9]/,
10
- };
6
+ import { KPT_MASK_TOKENS, kptApplyMask, kptMaskCaret } from "../internal/mask.js";
11
7
  /**
12
8
  * Pole z maską znakową. Tokeny: `9` = cyfra, `a` = litera, `*` = alfanumeryczny; reszta wzorca
13
9
  * to literały wstawiane automatycznie.
@@ -15,52 +11,30 @@ const TOKENS = {
15
11
  * @example
16
12
  * <KptInputMask mask="(999) 999-99-99" value={phone} onValueChange={setPhone} />
17
13
  */
18
- export function KptInputMask({ value, defaultValue = '', onValueChange, mask = '', placeholder, invalid = false, touched, onTouch, disabled = false, className, id, onBlur, 'aria-describedby': ariaDescribedBy, inputMode, ...rest }) {
14
+ export function KptInputMask({ value, defaultValue = '', onValueChange, mask = '', placeholder, required = false, invalid = false, touched, onTouch, disabled = false, className, id, onBlur, 'aria-describedby': ariaDescribedBy, inputMode, ...rest }) {
19
15
  const field = useKptFieldA11y(id, 'kpt-input-mask');
20
- // Klawiatura ekranowa z maski: same tokeny `9` (telefon, kod pocztowy) → numeryczna.
21
- const tokens = [...mask].filter((char) => char in TOKENS);
16
+ // Klawiatura ekranowa z maski: same tokeny `9` (telefon, kod pocztowy) -> numeryczna.
17
+ const tokens = [...mask].filter((char) => char in KPT_MASK_TOKENS);
22
18
  const autoInputMode = tokens.length && tokens.every((char) => char === '9') ? 'numeric' : 'text';
23
19
  const inputId = field.id;
24
20
  const [current, setCurrent] = useControllableState({ value, defaultValue, onChange: onValueChange });
25
21
  const showInvalid = invalid && (touched ?? true);
26
- /*
27
- * Maska przepisuje wejście od zera: z tekstu bierzemy wyłącznie znaki pasujące do kolejnych
28
- * tokenów, a literały wstawiamy same. Dzięki temu wklejenie „48123456789" i wpisanie
29
- * „(48) 123…" dają ten sam wynik, a usunięcie literału nie zostawia dziury we wzorcu.
30
- */
31
- const applyMask = (raw) => {
32
- if (!mask)
33
- return raw;
34
- const chars = raw.split('');
35
- let charIndex = 0;
36
- let out = '';
37
- for (let maskIndex = 0; maskIndex < mask.length && charIndex < chars.length;) {
38
- const token = mask[maskIndex];
39
- const rule = TOKENS[token];
40
- if (rule) {
41
- while (charIndex < chars.length && !rule.test(chars[charIndex]))
42
- charIndex++;
43
- if (charIndex >= chars.length)
44
- break;
45
- out += chars[charIndex];
46
- charIndex++;
47
- maskIndex++;
48
- }
49
- else {
50
- out += token;
51
- maskIndex++;
52
- }
53
- }
54
- return out;
55
- };
56
22
  const onInput = (event) => {
57
- const formatted = applyMask(event.target.value);
23
+ const el = event.target;
24
+ const raw = el.value;
25
+ const caret = el.selectionStart ?? raw.length;
26
+ const formatted = kptApplyMask(raw, mask);
58
27
  setCurrent(formatted);
59
- // Gdy maska odrzuci znak, wartość się nie zmienia i React nie przerysuje pola —
28
+ // Gdy maska odrzuci znak, wartość się nie zmienia i React nie przerysuje pola -
60
29
  // wpisany znak zostałby na ekranie, więc DOM zrównujemy ręcznie.
61
- event.target.value = formatted;
30
+ el.value = formatted;
31
+ // Przepisanie `value` przenosi kursor na koniec; wraca za tyle samo znaczących znaków co przed zmianą.
32
+ if (document.activeElement === el) {
33
+ const at = kptMaskCaret(raw, caret, mask);
34
+ el.setSelectionRange(at, at);
35
+ }
62
36
  };
63
- return (_jsx("span", { className: "kpt-input-mask-host", children: _jsx("input", { className: cn('kpt-input-mask__input', showInvalid && 'kpt-input-mask--invalid', className), id: inputId, "aria-invalid": showInvalid || undefined, "aria-describedby": joinIds(ariaDescribedBy, field.describedBy), inputMode: inputMode ?? autoInputMode, type: "text", value: current, disabled: disabled, placeholder: placeholder || mask, onChange: onInput, onBlur: (event) => {
37
+ return (_jsx("span", { className: "kpt-input-mask-host", children: _jsx("input", { className: cn('kpt-input-mask__input', showInvalid && 'kpt-input-mask--invalid', className), id: inputId, "aria-invalid": showInvalid || undefined, "aria-required": required || undefined, "aria-describedby": joinIds(ariaDescribedBy, field.describedBy), inputMode: inputMode ?? autoInputMode, type: "text", value: current, disabled: disabled, placeholder: placeholder || mask, onChange: onInput, onBlur: (event) => {
64
38
  onBlur?.(event);
65
39
  onTouch?.();
66
40
  }, ...rest }) }));
@@ -9,9 +9,9 @@ Import: `import { KptInputMask } from '@konce-pt/react';`
9
9
  </span>
10
10
 
11
11
  ## Props
12
- - `value` / `defaultValue` / `onValueChange` — the formatted text
13
- - `mask`: string — `9` a digit, `a` a letter, `*` alphanumeric; everything else is a literal
14
- - `placeholder`: string — defaults to the mask itself, which doubles as a hint
12
+ - `value` / `defaultValue` / `onValueChange` - the formatted text
13
+ - `mask`: string - `9` a digit, `a` a letter, `*` alphanumeric; everything else is a literal
14
+ - `placeholder`: string - defaults to the mask itself, which doubles as a hint
15
15
  - `invalid`, `touched`, `onTouch()`
16
16
  - `className`, `ref` and every other `<input>` prop go to the input
17
17
 
@@ -22,7 +22,7 @@ digits therefore produce the same result, and deleting a literal cannot leave a
22
22
  pattern. Characters that match nothing are skipped rather than rejected, so stray letters around a
23
23
  number still format.
24
24
 
25
- Like the OTP field, the DOM is synced by hand when the mask drops a character — the value did not
25
+ Like the OTP field, the DOM is synced by hand when the mask drops a character - the value did not
26
26
  change, so React would not re-render and the character would linger on screen.
27
27
 
28
28
  ## Examples
@@ -32,7 +32,7 @@ change, so React would not re-render and the character would linger on screen.
32
32
  ## Screen reader and keyboard
33
33
  The on-screen keyboard follows the mask: only `9` tokens (phone, postal code) give
34
34
  `inputMode="numeric"`, otherwise `text`; pass `inputMode` to override it, and `autoComplete` (`tel`,
35
- `postal-code`…) so the browser can fill the field in.
35
+ `postal-code`...) so the browser can fill the field in.
36
36
 
37
37
  ## Label and description
38
38
  `id` goes to the native field, not the host; without one it comes from `KptFormField` or is
@@ -46,3 +46,6 @@ New: `aria-invalid` after touch.
46
46
  ## Accessibility
47
47
  A native text input; the mask doubles as the placeholder, so the expected shape is visible before
48
48
  typing starts.
49
+
50
+ ## Read-only and required
51
+ `readOnly` is the native read-only field. The caret stays where you typed: fixing a digit in the middle of a phone number no longer throws it to the end. The `a` and `*` tokens accept every letter (`ą`, `ł`, `ß`), not only ASCII.
@@ -10,31 +10,33 @@ export interface KptInputNumberProps extends Omit<ComponentPropsWithRef<'div'>,
10
10
  placeholder?: string;
11
11
  /** Dopisek pokazywany za liczbą, np. ' szt.' albo 'ms'. */
12
12
  suffix?: string;
13
- /** Przyciski krokowania. `false` daje samo pole — wariant do zakresów „od–do". */
13
+ /** Przyciski krokowania. `false` daje samo pole - wariant do zakresów "od-do". */
14
14
  showButtons?: boolean;
15
- /** Id elementu fokusowalnego (nie hosta); bez niego generowane, w `KptFormField` — z wrappera. */
15
+ /** Id elementu fokusowalnego (nie hosta); bez niego generowane, w `KptFormField` - z wrappera. */
16
16
  id?: string;
17
17
  /** Nazwa dostępna, gdy kontrolki nie opisuje etykieta (`<label for>` albo `KptFormField`). */
18
18
  ariaLabel?: string | null;
19
19
  /** Oznacza pole jako wymagane atrybutem `aria-required`. */
20
20
  required?: boolean;
21
21
  disabled?: boolean;
22
+ /** Tylko do odczytu - wartość widać i da się ją zaznaczyć, ale nie zmienić (także strzałkami). */
23
+ readOnly?: boolean;
22
24
  invalid?: boolean;
23
25
  touched?: boolean;
24
26
  onTouch?: () => void;
25
27
  }
26
28
  /**
27
- * Pole liczbowe ze strzałkami krokowania. Wartość to `number | null` — puste pole ma znaczenie,
28
- * bo „brak wartości" to nie to samo co zero.
29
+ * Pole liczbowe ze strzałkami krokowania. Wartość to `number | null` - puste pole ma znaczenie,
30
+ * bo "brak wartości" to nie to samo co zero.
29
31
  *
30
- * Pole to `role="spinbutton"` (z `aria-valuemin`/`max`/`now`, a przy `suffix` — `aria-valuetext`):
31
- * `↑`/`↓` zmieniają wartość o `step`, `PageUp`/`PageDown` o dziesięć kroków. Wpisywana liczba nie
32
- * jest przycinana w trakcie pisania (inaczej „150” przy `max` 99 stawało się „99”, zanim user skończył) —
33
- * granice działają przy krokowaniu i na `blur`. Przyciski `−`/`+` stoją poza kolejnością Tab i nie
32
+ * Pole to `role="spinbutton"` (z `aria-valuemin`/`max`/`now`, a przy `suffix` - `aria-valuetext`):
33
+ * `ArrowUp`/`ArrowDown` zmieniają wartość o `step`, `PageUp`/`PageDown` o dziesięć kroków. Wpisywana liczba nie
34
+ * jest przycinana w trakcie pisania (inaczej "150" przy `max` 99 stawało się "99", zanim user skończył) -
35
+ * granice działają przy krokowaniu i na `blur`. Przyciski `-`/`+` stoją poza kolejnością Tab i nie
34
36
  * zabierają fokusu polu.
35
37
  *
36
38
  * @example
37
39
  * <KptInputNumber value={qty} onValueChange={setQty} min={0} max={99} suffix=" szt." />
38
40
  * <KptInputNumber value={from} onValueChange={setFrom} showButtons={false} suffix=" zł" />
39
41
  */
40
- export declare function KptInputNumber({ value, defaultValue, onValueChange, min, max, step, placeholder, suffix, showButtons, id, ariaLabel, required, disabled, invalid, touched, onTouch, locale, dictionary, className, ...rest }: Readonly<KptInputNumberProps>): import("react").JSX.Element;
42
+ export declare function KptInputNumber({ value, defaultValue, onValueChange, min, max, step, placeholder, suffix, showButtons, id, ariaLabel, required, disabled, readOnly, invalid, touched, onTouch, locale, dictionary, className, ...rest }: Readonly<KptInputNumberProps>): import("react").JSX.Element;
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  import { useCallback, useState } from 'react';
3
4
  import { cn } from "../internal/cn.js";
@@ -5,21 +6,24 @@ import { useControllableState } from "../internal/use-controllable-state.js";
5
6
  import { useKptMessages } from "../i18n/i18n-context.js";
6
7
  import { KptIcon } from "../icon/icon.js";
7
8
  import { useKptFieldA11y } from "../form-field/form-field-context.js";
9
+ import { kptRoundTo } from "../internal/step.js";
10
+ /** Liczba dziesiętna wpisana przez człowieka: bez `0x1A`, `1e3` i `Infinity`, które `Number()` też przyjmuje. */
11
+ const DECIMAL = /^[-+]?(?:\d+\.?\d*|\.\d+)$/;
8
12
  /**
9
- * Pole liczbowe ze strzałkami krokowania. Wartość to `number | null` — puste pole ma znaczenie,
10
- * bo „brak wartości" to nie to samo co zero.
13
+ * Pole liczbowe ze strzałkami krokowania. Wartość to `number | null` - puste pole ma znaczenie,
14
+ * bo "brak wartości" to nie to samo co zero.
11
15
  *
12
- * Pole to `role="spinbutton"` (z `aria-valuemin`/`max`/`now`, a przy `suffix` — `aria-valuetext`):
13
- * `↑`/`↓` zmieniają wartość o `step`, `PageUp`/`PageDown` o dziesięć kroków. Wpisywana liczba nie
14
- * jest przycinana w trakcie pisania (inaczej „150” przy `max` 99 stawało się „99”, zanim user skończył) —
15
- * granice działają przy krokowaniu i na `blur`. Przyciski `−`/`+` stoją poza kolejnością Tab i nie
16
+ * Pole to `role="spinbutton"` (z `aria-valuemin`/`max`/`now`, a przy `suffix` - `aria-valuetext`):
17
+ * `ArrowUp`/`ArrowDown` zmieniają wartość o `step`, `PageUp`/`PageDown` o dziesięć kroków. Wpisywana liczba nie
18
+ * jest przycinana w trakcie pisania (inaczej "150" przy `max` 99 stawało się "99", zanim user skończył) -
19
+ * granice działają przy krokowaniu i na `blur`. Przyciski `-`/`+` stoją poza kolejnością Tab i nie
16
20
  * zabierają fokusu polu.
17
21
  *
18
22
  * @example
19
23
  * <KptInputNumber value={qty} onValueChange={setQty} min={0} max={99} suffix=" szt." />
20
24
  * <KptInputNumber value={from} onValueChange={setFrom} showButtons={false} suffix=" zł" />
21
25
  */
22
- export function KptInputNumber({ value, defaultValue = null, onValueChange, min, max, step = 1, placeholder = '', suffix = '', showButtons = true, id, ariaLabel = null, required = false, disabled = false, invalid = false, touched, onTouch, locale, dictionary, className, ...rest }) {
26
+ export function KptInputNumber({ value, defaultValue = null, onValueChange, min, max, step = 1, placeholder = '', suffix = '', showButtons = true, id, ariaLabel = null, required = false, disabled = false, readOnly = false, invalid = false, touched, onTouch, locale, dictionary, className, ...rest }) {
23
27
  const { t, locale: activeLocale } = useKptMessages({ locale, dictionary });
24
28
  const field = useKptFieldA11y(id, 'kpt-input-number');
25
29
  const [current, setCurrent] = useControllableState({
@@ -28,7 +32,7 @@ export function KptInputNumber({ value, defaultValue = null, onValueChange, min,
28
32
  onChange: onValueChange,
29
33
  });
30
34
  const showInvalid = invalid && (touched ?? true);
31
- /** Tekst w trakcie pisania — dopóki jest, pole pokazuje go zamiast sformatowanej wartości. */
35
+ /** Tekst w trakcie pisania - dopóki jest, pole pokazuje go zamiast sformatowanej wartości. */
32
36
  const [draft, setDraft] = useState(null);
33
37
  const clamp = useCallback((n) => {
34
38
  if (min !== undefined && n < min)
@@ -37,15 +41,17 @@ export function KptInputNumber({ value, defaultValue = null, onValueChange, min,
37
41
  return max;
38
42
  return n;
39
43
  }, [min, max]);
40
- // Zapis liczby wg aktywnego języka (PL: `12,5`), bez separatorów tysięcy — pole przyjmuje i kropkę,
44
+ // Zapis liczby wg aktywnego języka (PL: `12,5`), bez separatorów tysięcy - pole przyjmuje i kropkę,
41
45
  // i przecinek, a grupowanie cyfr rozjechałoby się z tym, co user wpisuje.
42
- const format = new Intl.NumberFormat(activeLocale, { useGrouping: false, maximumFractionDigits: 20 });
46
+ // `latn`: cyfry łacińskie także w locale z własnymi (np. `ar`) - inaczej pole pokazywałoby zapis,
47
+ // którego parser niżej nie przeczyta.
48
+ const format = new Intl.NumberFormat(activeLocale, { useGrouping: false, maximumFractionDigits: 20, numberingSystem: 'latn' });
43
49
  const display = current === null || current === undefined || Number.isNaN(current) ? '' : `${format.format(current)}${suffix}`;
44
50
  const canIncrement = max === undefined || (current ?? -Infinity) < max;
45
51
  const canDecrement = min === undefined || (current ?? Infinity) > min;
46
52
  const onInput = (event) => {
47
- // Przecinek jako separator dziesiętny i dopisek trafiają do pola razem z liczbą —
48
- // czyścimy je przed parsowaniem, żeby wpisywanie „12,5 zł" działało naturalnie.
53
+ // Przecinek jako separator dziesiętny i dopisek trafiają do pola razem z liczbą -
54
+ // czyścimy je przed parsowaniem, żeby wpisywanie "12,5 zł" działało naturalnie.
49
55
  setDraft(event.target.value);
50
56
  const raw = event.target.value.replace(suffix, '').replace(',', '.').trim();
51
57
  if (raw === '') {
@@ -53,21 +59,19 @@ export function KptInputNumber({ value, defaultValue = null, onValueChange, min,
53
59
  return;
54
60
  }
55
61
  // Bez przycinania: wartość spoza granic zostaje, a walidacja formularza ją zgłosi.
56
- const parsed = Number(raw);
57
- if (!Number.isNaN(parsed))
58
- setCurrent(parsed);
62
+ if (DECIMAL.test(raw))
63
+ setCurrent(Number(raw));
59
64
  };
60
65
  const stepBy = (count) => {
61
66
  const base = current ?? min ?? 0;
62
- // Zaokrąglenie do precyzji kroku — 0.1 + 0.2 nie może dać 0.30000000000000004.
63
- const decimals = (String(step).split('.')[1] ?? '').length;
67
+ // Precyzja kroku ALBO wartości, zależnie od tego, która jest drobniejsza (1.25 + 1 = 2.25).
64
68
  setDraft(null);
65
- setCurrent(clamp(Number((base + count * step).toFixed(decimals))));
69
+ setCurrent(clamp(kptRoundTo(base + count * step, step, base)));
66
70
  };
67
71
  const onKeyDown = (event) => {
68
72
  const steps = { ArrowUp: 1, ArrowDown: -1, PageUp: 10, PageDown: -10 };
69
73
  const count = steps[event.key];
70
- if (count === undefined || disabled)
74
+ if (count === undefined || disabled || readOnly)
71
75
  return;
72
76
  event.preventDefault();
73
77
  stepBy(count);
@@ -82,10 +86,10 @@ export function KptInputNumber({ value, defaultValue = null, onValueChange, min,
82
86
  setDraft(null);
83
87
  onTouch?.();
84
88
  };
85
- return (_jsx("div", { className: cn('kpt-input-number-host', className), ...rest, children: _jsxs("div", { className: cn('kpt-input-number', disabled && 'kpt-input-number--disabled', showInvalid && 'kpt-input-number--invalid', !showButtons && 'kpt-input-number--plain'), children: [showButtons ? (_jsx("button", { type: "button", className: "kpt-input-number__btn", tabIndex: -1, disabled: disabled || !canDecrement, onMouseDown: (event) => event.preventDefault(), onClick: () => {
89
+ return (_jsx("div", { className: cn('kpt-input-number-host', className), ...rest, children: _jsxs("div", { className: cn('kpt-input-number', disabled && 'kpt-input-number--disabled', showInvalid && 'kpt-input-number--invalid', !showButtons && 'kpt-input-number--plain'), children: [showButtons ? (_jsx("button", { type: "button", className: "kpt-input-number__btn", tabIndex: -1, disabled: disabled || readOnly || !canDecrement, onMouseDown: (event) => event.preventDefault(), onClick: () => {
86
90
  stepBy(-1);
87
91
  onTouch?.();
88
- }, "aria-label": t('inputNumber.decrement'), children: _jsx(KptIcon, { name: "minus" }) })) : null, _jsx("input", { className: "kpt-input-number__input", id: field.id, "aria-label": ariaLabel ?? undefined, "aria-required": required || undefined, "aria-invalid": showInvalid || undefined, "aria-describedby": field.describedBy, type: "text", inputMode: "decimal", role: "spinbutton", "aria-valuemin": min, "aria-valuemax": max, "aria-valuenow": current ?? undefined, "aria-valuetext": suffix && current !== null ? display : undefined, value: draft ?? display, disabled: disabled, placeholder: placeholder, onChange: onInput, onKeyDown: onKeyDown, onBlur: onBlur }), showButtons ? (_jsx("button", { type: "button", className: "kpt-input-number__btn", tabIndex: -1, disabled: disabled || !canIncrement, onMouseDown: (event) => event.preventDefault(), onClick: () => {
92
+ }, "aria-label": t('inputNumber.decrement'), children: _jsx(KptIcon, { name: "minus" }) })) : null, _jsx("input", { className: "kpt-input-number__input", id: field.id, "aria-label": ariaLabel ?? undefined, "aria-required": required || undefined, "aria-invalid": showInvalid || undefined, "aria-describedby": field.describedBy, type: "text", inputMode: "decimal", role: "spinbutton", "aria-valuemin": min, "aria-valuemax": max, "aria-valuenow": current ?? undefined, "aria-valuetext": suffix && current !== null ? display : undefined, value: draft ?? display, disabled: disabled, readOnly: readOnly, "aria-readonly": readOnly || undefined, placeholder: placeholder, onChange: onInput, onKeyDown: onKeyDown, onBlur: onBlur }), showButtons ? (_jsx("button", { type: "button", className: "kpt-input-number__btn", tabIndex: -1, disabled: disabled || readOnly || !canIncrement, onMouseDown: (event) => event.preventDefault(), onClick: () => {
89
93
  stepBy(1);
90
94
  onTouch?.();
91
95
  }, "aria-label": t('inputNumber.increment'), children: _jsx(KptIcon, { name: "plus" }) })) : null] }) }));
@@ -5,19 +5,20 @@ A number field with stepper arrows. Import: `import { KptInputNumber } from '@ko
5
5
  ## DOM
6
6
  <div class="kpt-input-number-host">
7
7
  <div class="kpt-input-number">
8
- <button class="kpt-input-number__btn" aria-label="Decrease">…</button>
8
+ <button class="kpt-input-number__btn" aria-label="Decrease">...</button>
9
9
  <input class="kpt-input-number__input" type="text" inputmode="decimal" value="12 zł">
10
- <button class="kpt-input-number__btn" aria-label="Increase">…</button>
10
+ <button class="kpt-input-number__btn" aria-label="Increase">...</button>
11
11
  </div>
12
12
  </div>
13
13
 
14
14
  ## Props
15
- - `value` / `defaultValue` / `onValueChange` — `number | null`
15
+ - `value` / `defaultValue` / `onValueChange` - `number | null`
16
16
  - `min`, `max`, `step` (default 1)
17
- - `placeholder`, `suffix` — text shown after the number, e.g. ' szt.' or 'ms'
18
- - `showButtons`: boolean (default true) — `false` leaves the bare field
19
- - `disabled`, `invalid`, `touched`, `onTouch()`
20
- - `locale`, `dictionary` — i18n overrides for this component only
17
+ - `placeholder`, `suffix` - text shown after the number, e.g. ' szt.' or 'ms'
18
+ - `showButtons`: boolean (default true) - `false` leaves the bare field
19
+ - `disabled`, `readOnly`, `invalid`, `touched`, `onTouch()` - `readOnly` leaves the value selectable but
20
+ blocks typing, the arrows and the buttons (`aria-readonly`)
21
+ - `locale`, `dictionary` - i18n overrides for this component only
21
22
 
22
23
  ## Null is a value
23
24
  The value is `number | null`, not `number`: an empty field means "no value", which is not the same
@@ -26,8 +27,8 @@ nothing to step from.
26
27
 
27
28
  ## A text input, not a number input
28
29
  The field is `type="text"` with `inputmode="decimal"`, so the suffix can live inside it and a comma
29
- works as the decimal separator — typing "12,5 zł" parses correctly. A native `type="number"` would
30
- reject both. Values out of range are clamped on blur and when stepping — not while typing.
30
+ works as the decimal separator - typing "12,5 zł" parses correctly. A native `type="number"` would
31
+ reject both. Values out of range are clamped on blur and when stepping - not while typing.
31
32
 
32
33
  ## i18n
33
34
  The stepper labels come from `inputNumber.decrement` and `inputNumber.increment`.
@@ -38,14 +39,16 @@ The stepper labels come from `inputNumber.decrement` and `inputNumber.increment`
38
39
 
39
40
  ## Keyboard
40
41
  The field is `role="spinbutton"` with `aria-valuemin`/`aria-valuemax`/`aria-valuenow` (and
41
- `aria-valuetext` with the `suffix`, "5 pcs"). `↑`/`↓` change the value by `step`,
42
- `PageUp`/`PageDown` by ten steps; stepping is rounded to the precision of `step`. A typed value is
43
- **not** clamped while typing ("150" with `max` 99 stays "150", so the form's `max()` can report it) —
44
- it is clamped on blur. The `−`/`+` buttons sit outside the Tab order and do not take focus from the
42
+ `aria-valuetext` with the `suffix`, "5 pcs"). `ArrowUp`/`ArrowDown` change the value by `step`,
43
+ `PageUp`/`PageDown` by ten steps; stepping is rounded to the precision of `step` or of the current
44
+ value, whichever is finer (`1.25` + 1 gives `2.25`, not `2`). Only a plain decimal is accepted while
45
+ typing - `0x1A`, `1e3` and `Infinity` are ignored. A typed value is
46
+ **not** clamped while typing ("150" with `max` 99 stays "150", so the form's `max()` can report it) -
47
+ it is clamped on blur. The `-`/`+` buttons sit outside the Tab order and do not take focus from the
45
48
  field, so focus never drops to `<body>` when a button goes disabled at a limit.
46
49
 
47
50
  ## Screen reader and keyboard
48
- The value is shown in the active locale's notation (PL `12,5`), without thousands separators — the
51
+ The value is shown in the active locale's notation (PL `12,5`), without thousands separators - the
49
52
  field accepts both a dot and a comma, and digit grouping would clash with what the user types.
50
53
 
51
54
  ## Label and description
@@ -1,26 +1,39 @@
1
- import type { ComponentPropsWithRef } from 'react';
1
+ import { type ComponentPropsWithRef } from 'react';
2
2
  import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
3
3
  export interface KptInputOtpProps extends Omit<ComponentPropsWithRef<'div'>, 'onChange' | 'defaultValue'>, KptI18nOverrides {
4
4
  value?: string;
5
5
  defaultValue?: string;
6
6
  onValueChange?: (value: string) => void;
7
7
  length?: number;
8
+ /**
9
+ * Co ile komórek wstawić separator, licząc od lewej (`3` przy `length` 9 -> `[][][]-[][][]-[][][]`).
10
+ * `0` (domyślnie) - bez grup. Separator jest wyłącznie wizualny: wartość to same znaki.
11
+ */
12
+ groupSize?: number;
13
+ /** Znak między grupami komórek. */
14
+ groupSeparator?: string;
8
15
  /** Dopuszcza wyłącznie cyfry i przełącza klawiaturę mobilną na numeryczną. */
9
16
  integerOnly?: boolean;
10
- /** Id pierwszej komórki (nie hosta) — klik w etykietę zaczyna wpisywanie od początku. */
17
+ /** Id pierwszej komórki (nie hosta) - klik w etykietę zaczyna wpisywanie od początku. */
11
18
  id?: string;
12
19
  /** Nazwa grupy komórek, gdy nie opisuje jej etykieta (`KptFormField`). */
13
20
  ariaLabel?: string | null;
14
21
  disabled?: boolean;
22
+ /** Tylko do odczytu - kod widać, nie da się go zmienić. */
23
+ readOnly?: boolean;
24
+ /** Pole wymagane - `aria-required` na komórkach. */
25
+ required?: boolean;
15
26
  invalid?: boolean;
16
27
  touched?: boolean;
17
28
  onTouch?: () => void;
18
29
  }
19
30
  /**
20
- * Pole kodu jednorazowego (OTP/PIN) — N osobnych pól, automatyczne przejście do następnego
21
- * i wklejanie całego kodu naraz. Wartością jest sklejony string.
31
+ * Pole kodu jednorazowego (OTP/PIN) - N osobnych pól, automatyczne przejście do następnego
32
+ * i wklejanie całego kodu naraz. Wartością są wpisane znaki **bez dziur** (`'13'`, nie `'1 3'`), więc
33
+ * walidator długości i backend widzą tyle znaków, ile naprawdę wpisano.
22
34
  *
23
35
  * @example
24
36
  * <KptInputOtp value={code} onValueChange={setCode} length={6} integerOnly />
37
+ * <KptInputOtp value={code} onValueChange={setCode} length={9} groupSize={3} />
25
38
  */
26
- export declare function KptInputOtp({ value, defaultValue, onValueChange, length, integerOnly, id, ariaLabel, disabled, invalid, touched, onTouch, locale, dictionary, className, ...rest }: Readonly<KptInputOtpProps>): import("react").JSX.Element;
39
+ export declare function KptInputOtp({ value, defaultValue, onValueChange, length, groupSize, groupSeparator, integerOnly, id, ariaLabel, disabled, readOnly, required, invalid, touched, onTouch, locale, dictionary, className, ...rest }: Readonly<KptInputOtpProps>): import("react").JSX.Element;