@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
@@ -10,7 +10,7 @@ export interface KptSkeletonProps extends Omit<ComponentPropsWithRef<'span'>, 'c
10
10
  /**
11
11
  * Placeholder ładowania z animacją shimmer.
12
12
  *
13
- * Wariant `text` skraca ostatnią linię do 60% — pełny prostokąt nie wygląda jak akapit, a to
13
+ * Wariant `text` skraca ostatnią linię do 60% - pełny prostokąt nie wygląda jak akapit, a to
14
14
  * właśnie akapit ma udawać. Przy jednej linii nie ma czego udawać, więc ta ma pełną szerokość.
15
15
  *
16
16
  * @example
@@ -3,7 +3,7 @@ import { cn } from "../internal/cn.js";
3
3
  /**
4
4
  * Placeholder ładowania z animacją shimmer.
5
5
  *
6
- * Wariant `text` skraca ostatnią linię do 60% — pełny prostokąt nie wygląda jak akapit, a to
6
+ * Wariant `text` skraca ostatnią linię do 60% - pełny prostokąt nie wygląda jak akapit, a to
7
7
  * właśnie akapit ma udawać. Przy jednej linii nie ma czego udawać, więc ta ma pełną szerokość.
8
8
  *
9
9
  * @example
@@ -6,33 +6,39 @@ Import: `import { KptSlider } from '@konce-pt/react';`
6
6
  ## DOM
7
7
  <!-- single -->
8
8
  <div class="kpt-slider-host">
9
- <input class="kpt-slider__input" type="range" style="--_pct: 40%">
9
+ <div class="kpt-slider__track" style="--_p: 0.4">
10
+ <input class="kpt-slider__input" type="range">
11
+ <span class="kpt-slider__bar" aria-hidden="true"><span class="kpt-slider__bar-fill"></span></span>
12
+ <span class="kpt-slider__knob" aria-hidden="true"></span>
13
+ </div>
10
14
  <span class="kpt-slider__value">40</span>
11
15
  </div>
12
16
 
13
17
  <!-- range -->
14
18
  <div class="kpt-slider-host">
15
- <div class="kpt-slider__range">
16
- <div class="kpt-slider__rail"></div>
17
- <div class="kpt-slider__fill" style="left: 20%; right: 20%"></div>
19
+ <div class="kpt-slider__track kpt-slider__track--range" style="--_lo: 0.2; --_hi: 0.8">
18
20
  <input class="kpt-slider__thumb kpt-slider__thumb--low" type="range" aria-label="Minimum">
19
21
  <input class="kpt-slider__thumb kpt-slider__thumb--high" type="range" aria-label="Maximum">
22
+ <span class="kpt-slider__bar" aria-hidden="true"><span class="kpt-slider__bar-fill"></span></span>
23
+ <span class="kpt-slider__knob kpt-slider__knob--low" aria-hidden="true"></span>
24
+ <span class="kpt-slider__knob kpt-slider__knob--high" aria-hidden="true"></span>
20
25
  </div>
21
- <span class="kpt-slider__value">20–80</span>
26
+ <span class="kpt-slider__value">20-80</span>
22
27
  </div>
23
28
 
24
29
  ## Props
25
- - `value` / `defaultValue` / `onValueChange` — `number`, or `[number, number]` in `range` mode
26
- - `range`: boolean — two handles
30
+ - `value` / `defaultValue` / `onValueChange` - `number`, or `[number, number]` in `range` mode
31
+ - `range`: boolean - two handles
27
32
  - `min` (default 0), `max` (default 100), `step` (default 1)
28
- - `showValue`: boolean — prints the current value next to the slider
33
+ - `showValue`: boolean - prints the current value next to the slider
29
34
  - `disabled`, `onTouch()`
30
35
  - `className`, `ref` and every other `<div>` prop go to the host
31
36
 
32
37
  ## Native inputs all the way down
33
38
  Both handles are native `<input type="range">` stacked on each other, so the keyboard, touch
34
- gestures and screen readers work with no handling of our own — there is no custom drag logic to get
35
- wrong. The rail and the fill are decoration underneath them.
39
+ gestures and screen readers work with no handling of our own. The only custom pointer logic is the
40
+ press on the track next to the handles (see "Pointer"). The track, fill and handles you see are
41
+ decoration drawn under the transparent inputs.
36
42
 
37
43
  ## The handles cannot cross
38
44
  Moving the low handle above the high one clamps it to the high value, and the other way round. The
@@ -43,7 +49,7 @@ pair therefore always stays ordered, so the fill never inverts.
43
49
  <KptSlider range value={span} onValueChange={(v) => setSpan(v as [number, number])} showValue />
44
50
 
45
51
  ## Screen reader and keyboard
46
- `suffix`: string (" zł", "%", " km") — shown after the value and put into `aria-valuetext` ("40
52
+ `suffix`: string (" zł", "%", " km") - shown after the value and put into `aria-valuetext` ("40
47
53
  zł"), so the unit is read too. `aria-invalid` after touch.
48
54
 
49
55
  ## Label and description
@@ -55,9 +61,25 @@ previously hard-coded and mixed-language); inside `kpt-form-field` each thumb re
55
61
  Minimum" through `aria-labelledby`, without one `ariaLabel` gives "<ariaLabel>, Minimum". `id` goes
56
62
  to the single input or the lower thumb.
57
63
 
64
+ ## Pointer
65
+ The handle reacts to the pointer: it grows on hover, and while held it grows more on a spring with
66
+ a soft halo in the primary colour and a `grabbing` cursor. A click on the track (and a key press) glides
67
+ the handle and the fill to the new value instead of jumping; while dragging they follow the pointer with
68
+ no delay. The native `input[type=range]` stays transparent on top (keyboard, screen reader, dragging) and
69
+ the track, fill and handle are drawn next to it from `--_p` (0..1); `range` draws two handles from
70
+ `--_lo`/`--_hi`. The glide is pure CSS and drops out under "reduce motion".
71
+
72
+ In `range` mode a press on the track outside the handles - before the range, after it or inside it -
73
+ moves the nearest handle there, focuses it (arrow keys keep working) and keeps dragging it until
74
+ release. The handles themselves are dragged natively. When both handles sit in the upper half, the
75
+ lower one is stacked on top, so two handles pushed against `max` can still be pulled apart.
76
+
58
77
  ## Tokens
59
- `--kpt-color-primary` (the fill and the handles), `--kpt-color-muted` (the rail).
78
+ `--kpt-color-primary` (the fill and the handles), `--kpt-color-surface-variant` (the track).
60
79
 
61
80
  ## Accessibility
62
81
  Native range inputs carry their own role and value semantics. In `range` mode each handle gets an
63
82
  `aria-label` telling the two apart, because "slider" twice would say nothing.
83
+
84
+ ## Read-only and required
85
+ `readOnly` keeps the thumb focusable but undoes every move (`aria-readonly`). The range thumbs can no longer cross: the clamped value is written back to the native input, so the thumb stops at the other one. `slider` does not accept `aria-required` - the field asterisk and the validator carry it.
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Oznacza tor suwaka jako przeciągany (`data-dragging`) od pierwszego ruchu wskaźnika o kilka pikseli
3
+ * do puszczenia. Samo kliknięcie zostawia tor bez znacznika, więc skok wartości jedzie przejściem CSS.
4
+ * Ta sama funkcja jest w porcie Angulara (`slider/slider-drag.ts`).
5
+ */
6
+ export declare function kptTrackDrag(track: HTMLElement, startX: number): void;
7
+ /** Koniec zakresu: dolny albo górny uchwyt. */
8
+ export type KptRangeEnd = 'low' | 'high';
9
+ export interface KptRangePressOptions {
10
+ min: number;
11
+ max: number;
12
+ step: number;
13
+ low: number;
14
+ high: number;
15
+ /** Ustawia wybrany koniec. Pilnowanie, by uchwyty się nie minęły, zostaje w komponencie. */
16
+ set: (end: KptRangeEnd, value: number) => void;
17
+ /** Puszczenie wskaźnika. */
18
+ release?: () => void;
19
+ }
20
+ /**
21
+ * Wciśnięcie toru zakresu poza uchwytem. Najbliższy uchwyt skacze w to miejsce (wewnątrz zakresu
22
+ * też - ten bliższy), dostaje fokus, żeby dalej działały strzałki, i idzie za wskaźnikiem aż do
23
+ * puszczenia. Natywne suwaki tego nie zrobią: przy dwóch nałożonych inputach wskaźnik łapią tylko
24
+ * kciuki, a kliknięcie w tor trafiłoby w jeden z nich zawsze, niezależnie od odległości.
25
+ */
26
+ export declare function kptRangePress(track: HTMLElement, event: PointerEvent, opts: KptRangePressOptions): void;
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Oznacza tor suwaka jako przeciągany (`data-dragging`) od pierwszego ruchu wskaźnika o kilka pikseli
3
+ * do puszczenia. Samo kliknięcie zostawia tor bez znacznika, więc skok wartości jedzie przejściem CSS.
4
+ * Ta sama funkcja jest w porcie Angulara (`slider/slider-drag.ts`).
5
+ */
6
+ export function kptTrackDrag(track, startX) {
7
+ const move = (e) => {
8
+ if (Math.abs(e.clientX - startX) >= 3)
9
+ track.setAttribute('data-dragging', '');
10
+ };
11
+ const up = () => {
12
+ window.removeEventListener('pointermove', move);
13
+ window.removeEventListener('pointerup', up);
14
+ window.removeEventListener('pointercancel', up);
15
+ track.removeAttribute('data-dragging');
16
+ };
17
+ window.addEventListener('pointermove', move);
18
+ window.addEventListener('pointerup', up);
19
+ window.addEventListener('pointercancel', up);
20
+ }
21
+ /**
22
+ * Wciśnięcie toru zakresu poza uchwytem. Najbliższy uchwyt skacze w to miejsce (wewnątrz zakresu
23
+ * też - ten bliższy), dostaje fokus, żeby dalej działały strzałki, i idzie za wskaźnikiem aż do
24
+ * puszczenia. Natywne suwaki tego nie zrobią: przy dwóch nałożonych inputach wskaźnik łapią tylko
25
+ * kciuki, a kliknięcie w tor trafiłoby w jeden z nich zawsze, niezależnie od odległości.
26
+ */
27
+ export function kptRangePress(track, event, opts) {
28
+ if (event.button !== 0)
29
+ return;
30
+ // Bez tego przeglądarka zaznacza tekst w trakcie ciągnięcia i zabiera fokus z uchwytu.
31
+ event.preventDefault();
32
+ const first = rangeValueAt(track, event.clientX, opts);
33
+ const end = first < opts.low ? 'low' : first > opts.high ? 'high' : first - opts.low <= opts.high - first ? 'low' : 'high';
34
+ let last = first;
35
+ opts.set(end, first);
36
+ track.querySelector(`.kpt-slider__thumb--${end}`)?.focus({ preventScroll: true });
37
+ track.setAttribute('data-pressed', end);
38
+ const move = (e) => {
39
+ const next = rangeValueAt(track, e.clientX, opts);
40
+ if (next === last)
41
+ return;
42
+ last = next;
43
+ opts.set(end, next);
44
+ };
45
+ const up = () => {
46
+ window.removeEventListener('pointermove', move);
47
+ window.removeEventListener('pointerup', up);
48
+ window.removeEventListener('pointercancel', up);
49
+ track.removeAttribute('data-pressed');
50
+ opts.release?.();
51
+ };
52
+ window.addEventListener('pointermove', move);
53
+ window.addEventListener('pointerup', up);
54
+ window.addEventListener('pointercancel', up);
55
+ }
56
+ /**
57
+ * Wartość pod wskaźnikiem, przyciągnięta do kroku tak jak natywny suwak. Środek uchwytu jeździ
58
+ * od połowy jego szerokości do szerokości toru minus połowa - stąd odjęcie szerokości uchwytu.
59
+ */
60
+ function rangeValueAt(track, clientX, { min, max, step }) {
61
+ const rect = track.getBoundingClientRect();
62
+ const thumb = track.querySelector('.kpt-slider__knob')?.offsetWidth ?? 0;
63
+ const usable = rect.width - thumb;
64
+ let frac = usable > 0 ? (clientX - rect.left - thumb / 2) / usable : 0;
65
+ frac = Math.min(1, Math.max(0, frac));
66
+ if (getComputedStyle(track).direction === 'rtl')
67
+ frac = 1 - frac;
68
+ if (max <= min)
69
+ return min;
70
+ if (!(step > 0))
71
+ return min + frac * (max - min);
72
+ let steps = Math.round((frac * (max - min)) / step);
73
+ // Jak natywny suwak: ostatni krok nie wychodzi za `max`, gdy zakres nie dzieli się przez krok.
74
+ if (min + steps * step > max)
75
+ steps -= 1;
76
+ // Ułamkowy krok (0.1) zostawia ogon zmiennoprzecinkowy - przycięcie do liczby miejsc kroku.
77
+ const decimals = String(step).split('.')[1]?.length ?? 0;
78
+ return Number((min + steps * step).toFixed(decimals));
79
+ }
@@ -13,25 +13,28 @@ export interface KptSliderProps extends Omit<ComponentPropsWithRef<'div'>, 'onCh
13
13
  step?: number;
14
14
  /** Pokazuje bieżącą wartość obok suwaka. */
15
15
  showValue?: boolean;
16
- /** Jednostka za liczbą („ zł”, „%”, „ km”) — w pokazanej wartości i w `aria-valuetext`. */
16
+ /** Jednostka za liczbą (" zł", "%", " km") - w pokazanej wartości i w `aria-valuetext`. */
17
17
  suffix?: string;
18
18
  invalid?: boolean;
19
19
  touched?: boolean;
20
- /** Id elementu fokusowalnego (nie hosta); bez niego generowane, w `KptFormField` — z wrappera. */
20
+ /** Id elementu fokusowalnego (nie hosta); bez niego generowane, w `KptFormField` - z wrappera. */
21
21
  id?: string;
22
22
  /** Nazwa dostępna, gdy kontrolki nie opisuje etykieta (`<label for>` albo `KptFormField`). */
23
23
  ariaLabel?: string | null;
24
24
  disabled?: boolean;
25
+ /** Tylko do odczytu - uchwyt można skupić, ale nie przesunąć (`aria-readonly`). */
26
+ readOnly?: boolean;
25
27
  onTouch?: () => void;
26
28
  }
27
29
  /**
28
30
  * Suwak wartości. W trybie `range` udostępnia dwa uchwyty, a wartością jest para `[min, max]`.
29
31
  *
30
- * Oba uchwyty to natywne `<input type="range">` leżące na sobie — dzięki temu klawiatura,
31
- * gesty dotykowe i czytniki ekranu działają bez ani jednej linii własnej obsługi.
32
+ * Oba uchwyty to natywne `<input type="range">` leżące na sobie - klawiatura, gesty dotykowe
33
+ * i czytniki ekranu działają bez własnej obsługi. Własna jest tylko jedna rzecz: wciśnięcie toru
34
+ * obok kciuków przesuwa najbliższy uchwyt (`kptRangePress`).
32
35
  *
33
36
  * @example
34
37
  * <KptSlider value={volume} onValueChange={setVolume} min={0} max={100} step={5} showValue />
35
38
  * <KptSlider range value={range} onValueChange={setRange} showValue />
36
39
  */
37
- export declare function KptSlider({ value, defaultValue, onValueChange, range, min, max, step, showValue, suffix, invalid, touched, id, ariaLabel, disabled, onTouch, locale, dictionary, className, ...rest }: Readonly<KptSliderProps>): import("react").JSX.Element;
40
+ export declare function KptSlider({ value, defaultValue, onValueChange, range, min, max, step, showValue, suffix, invalid, touched, id, ariaLabel, disabled, readOnly, onTouch, locale, dictionary, className, ...rest }: Readonly<KptSliderProps>): import("react").JSX.Element;
@@ -1,22 +1,25 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } 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
6
  import { useKptMessages } from "../i18n/i18n-context.js";
7
+ import { kptRangePress, kptTrackDrag } from "./slider-drag.js";
6
8
  /**
7
9
  * Suwak wartości. W trybie `range` udostępnia dwa uchwyty, a wartością jest para `[min, max]`.
8
10
  *
9
- * Oba uchwyty to natywne `<input type="range">` leżące na sobie — dzięki temu klawiatura,
10
- * gesty dotykowe i czytniki ekranu działają bez ani jednej linii własnej obsługi.
11
+ * Oba uchwyty to natywne `<input type="range">` leżące na sobie - klawiatura, gesty dotykowe
12
+ * i czytniki ekranu działają bez własnej obsługi. Własna jest tylko jedna rzecz: wciśnięcie toru
13
+ * obok kciuków przesuwa najbliższy uchwyt (`kptRangePress`).
11
14
  *
12
15
  * @example
13
16
  * <KptSlider value={volume} onValueChange={setVolume} min={0} max={100} step={5} showValue />
14
17
  * <KptSlider range value={range} onValueChange={setRange} showValue />
15
18
  */
16
- export function KptSlider({ value, defaultValue, onValueChange, range = false, min = 0, max = 100, step = 1, showValue = false, suffix = '', invalid = false, touched, id, ariaLabel = null, disabled = false, onTouch, locale, dictionary, className, ...rest }) {
19
+ export function KptSlider({ value, defaultValue, onValueChange, range = false, min = 0, max = 100, step = 1, showValue = false, suffix = '', invalid = false, touched, id, ariaLabel = null, disabled = false, readOnly = false, onTouch, locale, dictionary, className, ...rest }) {
17
20
  const { t } = useKptMessages({ locale, dictionary });
18
21
  const field = useKptFieldA11y(id, 'kpt-slider');
19
- /** Uchwyt zakresu bez etykiety pola: „Cena, Minimum” z `ariaLabel` albo samo „Minimum”. */
22
+ /** Uchwyt zakresu bez etykiety pola: "Cena, Minimum" z `ariaLabel` albo samo "Minimum". */
20
23
  const thumbLabel = (end) => {
21
24
  if (field.labelledBy)
22
25
  return undefined;
@@ -36,8 +39,35 @@ export function KptSlider({ value, defaultValue, onValueChange, range = false, m
36
39
  const span = max - min;
37
40
  return span <= 0 ? 0 : ((val - min) / span) * 100;
38
41
  };
39
- // Uchwyty nie mogą się wyminąć — dolny nie przekracza górnego i odwrotnie.
40
- const setLow = (next) => setCurrent([Math.min(next, high), high]);
41
- const setHigh = (next) => setCurrent([low, Math.max(next, low)]);
42
- return (_jsx("div", { className: cn('kpt-slider-host', className), ...rest, children: range ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "kpt-slider__range", children: [_jsx("span", { className: "kpt-slider__sr", id: `${field.id}-min-name`, children: t('slider.min') }), _jsx("span", { className: "kpt-slider__sr", id: `${field.id}-max-name`, children: t('slider.max') }), _jsx("div", { className: "kpt-slider__rail" }), _jsx("div", { className: "kpt-slider__fill", style: { left: `${pct(low)}%`, right: `${100 - pct(high)}%` } }), _jsx("input", { className: "kpt-slider__thumb kpt-slider__thumb--low", type: "range", min: min, max: max, step: step, value: low, "aria-valuetext": suffix ? `${low}${suffix}` : undefined, "aria-invalid": invalid && (touched ?? true) ? true : undefined, id: field.id, disabled: disabled, "aria-label": thumbLabel('min'), "aria-labelledby": thumbLabelledBy('min'), "aria-describedby": field.describedBy, onChange: (event) => setLow(Number(event.target.value)), onBlur: () => onTouch?.() }), _jsx("input", { className: "kpt-slider__thumb kpt-slider__thumb--high", type: "range", min: min, max: max, step: step, value: high, "aria-valuetext": suffix ? `${high}${suffix}` : undefined, "aria-invalid": invalid && (touched ?? true) ? true : undefined, disabled: disabled, "aria-label": thumbLabel('max'), "aria-labelledby": thumbLabelledBy('max'), "aria-describedby": field.describedBy, onChange: (event) => setHigh(Number(event.target.value)), onBlur: () => onTouch?.() })] }), showValue ? (_jsxs("span", { className: "kpt-slider__value", children: [low, suffix, "\u2013", high, suffix] })) : null] })) : (_jsxs(_Fragment, { children: [_jsx("input", { className: "kpt-slider__input", type: "range", min: min, max: max, step: step, value: single, "aria-valuetext": suffix ? `${single}${suffix}` : undefined, "aria-invalid": invalid && (touched ?? true) ? true : undefined, id: field.id, disabled: disabled, "aria-label": ariaLabel ?? undefined, "aria-describedby": field.describedBy, style: { ['--_pct']: `${pct(single)}%` }, onChange: (event) => setCurrent(Number(event.target.value)), onBlur: () => onTouch?.() }), showValue ? (_jsxs("span", { className: "kpt-slider__value", children: [single, suffix] })) : null] })) }));
42
+ // Uchwyty nie mogą się wyminąć - dolny nie przekracza górnego i odwrotnie.
43
+ // Suwaki są kontrolowane: przy `readOnly` stan się nie zmienia, więc React cofa uchwyt sam.
44
+ const setLow = (next) => {
45
+ if (!readOnly)
46
+ setCurrent([Math.min(next, high), high]);
47
+ };
48
+ const setHigh = (next) => {
49
+ if (!readOnly)
50
+ setCurrent([low, Math.max(next, low)]);
51
+ };
52
+ // Kciuki ciągnie natywny suwak; wciśnięcie obok nich (przed zakresem, za nim albo w środku)
53
+ // przesuwa najbliższy uchwyt i dalej go ciągnie - patrz kptRangePress.
54
+ const onRangePress = (event) => {
55
+ kptTrackDrag(event.currentTarget, event.clientX);
56
+ if (disabled || event.target instanceof HTMLInputElement)
57
+ return;
58
+ kptRangePress(event.currentTarget, event.nativeEvent, {
59
+ min,
60
+ max,
61
+ step,
62
+ low,
63
+ high,
64
+ set: (end, v) => (end === 'low' ? setLow(v) : setHigh(v)),
65
+ });
66
+ };
67
+ return (_jsx("div", { className: cn('kpt-slider-host', className), ...rest, children: range ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "kpt-slider__track kpt-slider__track--range", style: { ['--_lo']: pct(low) / 100, ['--_hi']: pct(high) / 100 }, "data-low-top": pct(low) > 50 ? '' : undefined, onPointerDown: onRangePress, children: [_jsx("span", { className: "kpt-slider__sr", id: `${field.id}-min-name`, children: t('slider.min') }), _jsx("span", { className: "kpt-slider__sr", id: `${field.id}-max-name`, children: t('slider.max') }), _jsx("input", { className: "kpt-slider__thumb kpt-slider__thumb--low", type: "range", min: min, max: max, step: step, value: low, "aria-valuetext": suffix ? `${low}${suffix}` : undefined, "aria-invalid": invalid && (touched ?? true) ? true : undefined, id: field.id, disabled: disabled, "aria-label": thumbLabel('min'), "aria-labelledby": thumbLabelledBy('min'), "aria-describedby": field.describedBy, "aria-readonly": readOnly || undefined, onChange: (event) => setLow(Number(event.target.value)), onBlur: () => onTouch?.() }), _jsx("input", { className: "kpt-slider__thumb kpt-slider__thumb--high", type: "range", min: min, max: max, step: step, value: high, "aria-valuetext": suffix ? `${high}${suffix}` : undefined, "aria-invalid": invalid && (touched ?? true) ? true : undefined, disabled: disabled, "aria-label": thumbLabel('max'), "aria-labelledby": thumbLabelledBy('max'), "aria-describedby": field.describedBy, "aria-readonly": readOnly || undefined, onChange: (event) => setHigh(Number(event.target.value)), onBlur: () => onTouch?.() }), _jsx("span", { className: "kpt-slider__bar", "aria-hidden": "true", children: _jsx("span", { className: "kpt-slider__bar-fill" }) }), _jsx("span", { className: "kpt-slider__knob kpt-slider__knob--low", "aria-hidden": "true" }), _jsx("span", { className: "kpt-slider__knob kpt-slider__knob--high", "aria-hidden": "true" })] }), showValue ? (_jsxs("span", { className: "kpt-slider__value", children: [low, suffix, "-", high, suffix] })) : null] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "kpt-slider__track", style: { ['--_p']: pct(single) / 100 },
68
+ // Skok po kliknięciu w tor jedzie (przejście w CSS), przeciąganie już nie - patrz kptTrackDrag.
69
+ onPointerDown: (event) => kptTrackDrag(event.currentTarget, event.clientX), children: [_jsx("input", { className: "kpt-slider__input", type: "range", min: min, max: max, step: step, value: single, "aria-valuetext": suffix ? `${single}${suffix}` : undefined, "aria-invalid": invalid && (touched ?? true) ? true : undefined, id: field.id, disabled: disabled, "aria-label": ariaLabel ?? undefined, "aria-describedby": field.describedBy, "aria-readonly": readOnly || undefined, onChange: (event) => {
70
+ if (!readOnly)
71
+ setCurrent(Number(event.target.value));
72
+ }, onBlur: () => onTouch?.() }), _jsx("span", { className: "kpt-slider__bar", "aria-hidden": "true", children: _jsx("span", { className: "kpt-slider__bar-fill" }) }), _jsx("span", { className: "kpt-slider__knob", "aria-hidden": "true" })] }), showValue ? (_jsxs("span", { className: "kpt-slider__value", children: [single, suffix] })) : null] })) }));
43
73
  }
@@ -0,0 +1,97 @@
1
+ # KptSpeechButton, KptSpeechWave, useKptSpeechRecognition (@konce-pt/react)
2
+
3
+ Dictation with a microphone button. A click turns the icon into "stop", an animated wave slides in
4
+ next to it and the recognised words appear live in the target: a textarea, an input, a
5
+ `contenteditable` element, a plain element or a function. Built on the Web Speech API; the session,
6
+ insertion and wave maths live in `@konce-pt/speech`, shared with the Angular port.
7
+ Import: `import { KptSpeechButton, KptSpeechWave, useKptSpeechRecognition } from '@konce-pt/react';`
8
+
9
+ ## The key rule
10
+ Interim text is visible in the target but never reaches your state. A textarea or input shows it in
11
+ the native value without an event; only final text goes in as a regular edit (`setRangeText` +
12
+ `InputEvent`, or `insertText` with native undo when the field has focus), so a controlled field's
13
+ `onChange` sees it like typed text. The core never writes `el.value = x` - React would swallow it.
14
+
15
+ ## DOM
16
+ <span class="kpt-speech-button-host" data-status="listening" data-wave-position="start">
17
+ <span class="kpt-speech-wave-host kpt-speech-button__wave" aria-hidden="true" data-mode="live">...</span>
18
+ <span class="kpt-icon-button-host kpt-speech-button__toggle">
19
+ <button class="kpt-icon-button" aria-label="Dictate" aria-pressed="true">...icon...</button>
20
+ </span>
21
+ <span class="kpt-speech-button__status" aria-live="polite">Listening...</span>
22
+ </span>
23
+
24
+ ## useKptSpeechRecognition(options)
25
+ Dictation without UI - for your own button, a keyboard shortcut, or to call `flush()` before sending.
26
+ Options:
27
+ - `target`: `HTMLElement | RefObject<HTMLElement | null> | KptSpeechTarget | (snapshot) => void`
28
+ - `lang`: string - BCP 47; without it from `KptI18nProvider` and the browser language
29
+ - `continuous`: boolean = true; `mode`: `'insert' | 'append' | 'replace'` = `'insert'` (`replace`
30
+ clears the target the moment dictation starts, undoable with Ctrl+Z)
31
+ - `interim`: `'inline' | 'ghost' | 'none'`; `capitalize`: boolean = true
32
+ - `wave`: `'auto' | 'live' | 'synthetic' | 'none'` = `'auto'`; `bars`: number = 5
33
+ - `silenceMs`, `maxDuration` (ms, 0 = off), `processLocally` (on-device only, Chrome 135+), `disabled`
34
+ - `onStart`, `onEnd(reason)`, `onResult(snapshot)`, `onError(code)`
35
+
36
+ Returns `KptSpeechRecognition`: `status`, `listening`, `support` (`null` until the first client
37
+ render - SSR safe), `available`, `error`, `lastEnd`, `speaking`, `levels`, `waveMode`, `snapshot`
38
+ (`{ finalText, interimText }`), and `start()`, `stop()` (graceful), `flush()` (interim becomes final
39
+ and dictation ends at once), `abort()`, `toggle()`.
40
+
41
+ ## KptSpeechButton - props
42
+ - every option of `useKptSpeechRecognition`, or `speech` - an external controller from the hook (then
43
+ the button's own dictation options are ignored)
44
+ - `unsupported`: `'disable' | 'hide'` = `'disable'` - Firefox, `http:`, or a browser without the
45
+ service: a disabled button with the reason in a tooltip, or nothing
46
+ - `wavePosition`: `'start' | 'end'` = `'start'`
47
+ - `listeningColor`: `'primary' | 'danger' | 'success' | 'warning' | 'info'` = `'primary'` - colour of
48
+ "stop" and the wave while listening (the theme colour by default). Any other colour: set
49
+ `--kpt-speech-color` and `--kpt-speech-on-color` (icon on it) - both inherit, so they also work on
50
+ fields with a built-in microphone or globally in `:root`.
51
+ - `size` = 'md', `variant` = 'text'
52
+ - `aria-label` (default "Dictate" from the dictionary); `locale`, `dictionary`
53
+ - `hostProps` - props for `.kpt-speech-button-host`; `className`, `ref` and other `<button>` props go
54
+ to the inner `<button>`
55
+
56
+ ## Examples
57
+ const noteRef = useRef<HTMLTextAreaElement>(null);
58
+ <textarea ref={noteRef} />
59
+ <KptSpeechButton target={noteRef} />
60
+
61
+ <KptSpeechButton target={(snapshot) => setDraft(snapshot.finalText)} lang="en-US" />
62
+
63
+ const speech = useKptSpeechRecognition({ target: fieldRef });
64
+ <KptSpeechButton speech={speech} />
65
+ <KptButton onClick={() => { speech.flush(); send(); }}>Send</KptButton>
66
+
67
+ ## KptSpeechWave
68
+ Bars next to the microphone, `aria-hidden`. `levels: readonly number[]` (0..1) draws a live wave;
69
+ empty `levels` gives the synthetic one, higher while `active` (voice heard). `bars`: number = 5.
70
+ Colour: `--kpt-speech-wave-color`, otherwise the listening colour of the button.
71
+
72
+ ## Behaviour
73
+ - One dictation per page: starting a microphone stops the previous one.
74
+ - Continuous mode survives Chrome ending recognition after silence or about a minute; on iOS the
75
+ button stops and announces "Tap the microphone to continue".
76
+ - Typing or moving the caret mid-dictation drops the interim text; dictation continues at the caret.
77
+ - A hidden tab stops dictation; unmounting releases the microphone.
78
+
79
+ ## Screen reader and keyboard
80
+ - The button name stays the same ("Dictate"); the state is carried by `aria-pressed`.
81
+ - A polite live region announces start, stop and errors - never the transcript.
82
+ - Clicking the button does not move focus out of the target. Escape on the button or in the target
83
+ stops dictation.
84
+
85
+ ## Browsers and privacy
86
+ Chrome, Edge and Safari recognise speech; Firefox does not. A secure context is required. Chrome sends
87
+ audio to a Google service unless `processLocally` is set - mention it in the privacy policy.
88
+
89
+ ## Built into fields
90
+ `KptInput`, `KptTextarea` and `KptChatComposer` take `speech` (`speechLang`, `speechMode`, `speechColor`) - the same
91
+ layout as in Angular; they use `unsupported="hide"` and reserve room only when `available` is true.
92
+ `KptRichText` has the `dictate` tool, `KptAutocomplete` takes `speech` (default `replace`) and
93
+ `KptDataTable` takes `filterSpeech`, `KptMapSearch` takes `speech` (forwarded by `KptMap` as
94
+ `searchSpeech`). Search fields clear on click and listen for one utterance.
95
+
96
+ The hook option `enabled` (default true) creates no session and no page listeners when false - for
97
+ components where the microphone is optional.
@@ -0,0 +1,44 @@
1
+ import type { ComponentPropsWithRef } from 'react';
2
+ import type { KptButtonSize, KptButtonVariant } from '../button/button.tsx';
3
+ import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
4
+ import type { KptHostProps } from '../internal/types.ts';
5
+ import { type KptSpeechRecognition, type KptSpeechRecognitionOptions } from './use-speech-recognition.ts';
6
+ /** Co zrobić, gdy przeglądarka nie rozpoznaje mowy. */
7
+ export type KptSpeechUnsupported = 'hide' | 'disable';
8
+ /**
9
+ * Kolor przycisku "stop" i fali w trakcie nasłuchu. Domyślnie kolor motywu (`primary`); dowolny inny
10
+ * przez `--kpt-speech-color` i `--kpt-speech-on-color` (dziedziczone, więc działają też w polach).
11
+ */
12
+ export type KptSpeechColor = 'primary' | 'danger' | 'success' | 'warning' | 'info';
13
+ export interface KptSpeechButtonProps extends KptSpeechRecognitionOptions, Omit<ComponentPropsWithRef<'button'>, 'children' | 'type' | 'onError' | 'disabled'>, KptI18nOverrides {
14
+ /**
15
+ * Zewnętrzny kontroler z `useKptSpeechRecognition` - gdy aplikacja sama chce wołać `flush()`,
16
+ * `stop()` albo czytać `snapshot`. Wtedy opcje dyktowania z propsów przycisku są pomijane.
17
+ */
18
+ speech?: KptSpeechRecognition;
19
+ /** Brak wsparcia: `disable` (domyślnie, z powodem w podpowiedzi) albo `hide`. */
20
+ unsupported?: KptSpeechUnsupported;
21
+ /** Po której stronie przycisku stoi fala. */
22
+ wavePosition?: 'start' | 'end';
23
+ size?: KptButtonSize;
24
+ variant?: KptButtonVariant;
25
+ /** Kolor "stop" i fali w trakcie nasłuchu - domyślnie kolor motywu. */
26
+ listeningColor?: KptSpeechColor;
27
+ /** Nazwa przycisku; bez niej "Dyktuj" ze słownika. Stała - stan niesie `aria-pressed`. */
28
+ 'aria-label'?: string;
29
+ /** Propsy wrappera hosta (`.kpt-speech-button-host`). */
30
+ hostProps?: KptHostProps<'span'>;
31
+ }
32
+ /**
33
+ * Mikrofon do dyktowania. Klik zamienia ikonę w "stop", obok wjeżdża animowana fala, a słowa
34
+ * płyną na żywo do celu: textarea, inputu, contenteditable, zwykłego elementu albo funkcji.
35
+ *
36
+ * Tekst tymczasowy widać w celu, ale do stanu kontrolowanego pola trafia wyłącznie tekst
37
+ * zatwierdzony - przez zwykłą edycję, którą `onChange` widzi jak wpisany tekst.
38
+ *
39
+ * @example
40
+ * const noteRef = useRef<HTMLTextAreaElement>(null);
41
+ * <textarea ref={noteRef} />
42
+ * <KptSpeechButton target={noteRef} />
43
+ */
44
+ export declare function KptSpeechButton({ speech, target, lang, continuous, mode, interim, capitalize, wave, bars, silenceMs, maxDuration, processLocally, disabled, onStart, onEnd, onResult, onError, unsupported, wavePosition, size, variant, listeningColor, 'aria-label': ariaLabel, hostProps, locale, dictionary, className, onClick, onMouseDown, onKeyDown, ...rest }: Readonly<KptSpeechButtonProps>): import("react").JSX.Element;
@@ -0,0 +1,86 @@
1
+ 'use client';
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { speechErrorMessageKey } from '@konce-pt/speech';
4
+ import { KptIcon } from "../icon/icon.js";
5
+ import { KptIconButton } from "../icon-button/icon-button.js";
6
+ import { KptTooltip } from "../tooltip/tooltip.js";
7
+ import { useKptMessages } from "../i18n/i18n-context.js";
8
+ import { cn } from "../internal/cn.js";
9
+ import { KptSpeechWave } from "./speech-wave.js";
10
+ import { useKptSpeechRecognition, } from "./use-speech-recognition.js";
11
+ /**
12
+ * Mikrofon do dyktowania. Klik zamienia ikonę w "stop", obok wjeżdża animowana fala, a słowa
13
+ * płyną na żywo do celu: textarea, inputu, contenteditable, zwykłego elementu albo funkcji.
14
+ *
15
+ * Tekst tymczasowy widać w celu, ale do stanu kontrolowanego pola trafia wyłącznie tekst
16
+ * zatwierdzony - przez zwykłą edycję, którą `onChange` widzi jak wpisany tekst.
17
+ *
18
+ * @example
19
+ * const noteRef = useRef<HTMLTextAreaElement>(null);
20
+ * <textarea ref={noteRef} />
21
+ * <KptSpeechButton target={noteRef} />
22
+ */
23
+ export function KptSpeechButton({ speech, target, lang, continuous, mode, interim, capitalize, wave, bars = 5, silenceMs, maxDuration, processLocally, disabled = false, onStart, onEnd, onResult, onError, unsupported = 'disable', wavePosition = 'start', size = 'md', variant = 'text', listeningColor = 'primary', 'aria-label': ariaLabel, hostProps, locale, dictionary, className, onClick, onMouseDown, onKeyDown, ...rest }) {
24
+ const { t } = useKptMessages({ locale, dictionary });
25
+ // Hook jest wołany zawsze (zasady hooków); z zewnętrznym kontrolerem własny jest wyłączony
26
+ // (`enabled: false`) - bez sesji i bez nasłuchów strony.
27
+ const own = useKptSpeechRecognition({
28
+ enabled: !speech,
29
+ target,
30
+ lang,
31
+ continuous,
32
+ mode,
33
+ interim,
34
+ capitalize,
35
+ wave,
36
+ bars,
37
+ silenceMs,
38
+ maxDuration,
39
+ processLocally,
40
+ disabled,
41
+ onStart,
42
+ onEnd,
43
+ onResult,
44
+ onError,
45
+ });
46
+ const ctl = speech ?? own;
47
+ const known = ctl.support !== null;
48
+ const hidden = unsupported === 'hide' && (!known || !ctl.available);
49
+ const isDisabled = disabled || !ctl.available;
50
+ // Powód blokady albo ostatni błąd - w podpowiedzi. Cisza nie jest błędem wartym dymka.
51
+ let hint = '';
52
+ if (known && !ctl.available) {
53
+ hint = t(speechErrorMessageKey(ctl.support?.reason ?? 'unsupported'));
54
+ }
55
+ else if (ctl.error && ctl.error !== 'no-speech') {
56
+ hint = t(speechErrorMessageKey(ctl.error));
57
+ }
58
+ // Region statusu ogłasza stan i błędy, nigdy treść - czytnik czytający transkrypt zostałby
59
+ // nagrany przez mikrofon i wpisany z powrotem do pola.
60
+ let announcement = '';
61
+ if (ctl.error)
62
+ announcement = t(speechErrorMessageKey(ctl.error));
63
+ else if (ctl.listening)
64
+ announcement = t('speech.listening');
65
+ else if (ctl.lastEnd && ctl.lastEnd !== 'error') {
66
+ // iOS: restart bez dotknięcia jest zawodny - sesja staje, a użytkownik dostaje wskazówkę.
67
+ const iosPause = ctl.support?.ios && (continuous ?? true) && (ctl.lastEnd === 'ended' || ctl.lastEnd === 'silence');
68
+ announcement = t(iosPause ? 'speech.tapToContinue' : 'speech.stopped');
69
+ }
70
+ const { className: hostClassName, ...hostRest } = hostProps ?? {};
71
+ return (_jsx("span", { className: cn('kpt-speech-button-host', hostClassName), "data-status": ctl.status, "data-wave-position": wavePosition, "data-color": listeningColor, hidden: hidden || undefined, ...hostRest, children: !hidden ? (_jsxs(_Fragment, { children: [ctl.listening && ctl.waveMode !== 'none' ? (_jsx(KptSpeechWave, { className: "kpt-speech-button__wave", levels: ctl.waveMode === 'live' ? ctl.levels : [], active: ctl.speaking, bars: bars })) : null, _jsx(KptTooltip, { text: hint, children: _jsx(KptIconButton, { className: className, hostProps: { className: 'kpt-speech-button__toggle' }, variant: ctl.listening ? 'filled' : variant, size: size, disabled: isDisabled, "aria-label": ariaLabel ?? t('speech.dictate'), "aria-pressed": ctl.listening, onMouseDown: (event) => {
72
+ onMouseDown?.(event);
73
+ // Klik nie zabiera fokusu z celu - kursor zostaje tam, gdzie trafi tekst.
74
+ event.preventDefault();
75
+ }, onClick: (event) => {
76
+ onClick?.(event);
77
+ if (!event.defaultPrevented)
78
+ ctl.toggle();
79
+ }, onKeyDown: (event) => {
80
+ onKeyDown?.(event);
81
+ if (event.key === 'Escape' && ctl.listening) {
82
+ event.preventDefault();
83
+ ctl.stop();
84
+ }
85
+ }, ...rest, children: _jsx(KptIcon, { name: ctl.listening ? 'player-stop' : 'microphone' }) }) }), _jsx("span", { className: "kpt-speech-button__status", "aria-live": "polite", children: announcement })] })) : null }));
86
+ }
@@ -0,0 +1,19 @@
1
+ import type { ComponentPropsWithRef } from 'react';
2
+ export interface KptSpeechWaveProps extends Omit<ComponentPropsWithRef<'span'>, 'children'> {
3
+ /** Poziomy 0..1 z miernika; pusta lista przełącza falę w tryb syntetyczny. */
4
+ levels?: readonly number[];
5
+ /** Tryb syntetyczny: silnik słyszy mowę - słupki skaczą wyżej. */
6
+ active?: boolean;
7
+ /** Liczba słupków w trybie syntetycznym (na żywo decyduje długość `levels`). */
8
+ bars?: number;
9
+ }
10
+ /**
11
+ * Animowana fala dźwięku - słupki obok mikrofonu. Czysta dekoracja (`aria-hidden`): stan
12
+ * nasłuchu niesie przycisk (`aria-pressed`) i region statusu, nie rysunek. Te same klasy co
13
+ * `kpt-speech-wave` w Angularze, więc styl jest wspólny.
14
+ *
15
+ * @example
16
+ * <KptSpeechWave levels={speech.levels} />
17
+ * <KptSpeechWave active={speech.speaking} />
18
+ */
19
+ export declare function KptSpeechWave({ levels, active, bars, className, ...rest }: Readonly<KptSpeechWaveProps>): import("react").JSX.Element;
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cn } from "../internal/cn.js";
3
+ /**
4
+ * Animowana fala dźwięku - słupki obok mikrofonu. Czysta dekoracja (`aria-hidden`): stan
5
+ * nasłuchu niesie przycisk (`aria-pressed`) i region statusu, nie rysunek. Te same klasy co
6
+ * `kpt-speech-wave` w Angularze, więc styl jest wspólny.
7
+ *
8
+ * @example
9
+ * <KptSpeechWave levels={speech.levels} />
10
+ * <KptSpeechWave active={speech.speaking} />
11
+ */
12
+ export function KptSpeechWave({ levels = [], active = false, bars = 5, className, ...rest }) {
13
+ const live = levels.length > 0;
14
+ const slots = live ? levels : Array.from({ length: Math.max(1, bars) }, () => null);
15
+ return (_jsx("span", { className: cn('kpt-speech-wave-host', className), "aria-hidden": "true", "data-mode": live ? 'live' : 'synthetic', "data-active": active ? '' : undefined, ...rest, children: slots.map((level, index) => (_jsx("span", { className: "kpt-speech-wave__bar", style: { '--_i': index, ...(level === null ? {} : { '--_level': level }) } }, index))) }));
16
+ }