@konce-pt/react 0.12.0 → 0.15.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 (378) hide show
  1. package/README.md +122 -64
  2. package/dist/accordion/accordion.d.ts +14 -6
  3. package/dist/accordion/accordion.js +14 -7
  4. package/dist/accordion/llms.txt +22 -10
  5. package/dist/alert/alert.d.ts +2 -2
  6. package/dist/alert/alert.js +2 -2
  7. package/dist/alert/llms.txt +10 -10
  8. package/dist/app-shell/app-shell.d.ts +14 -14
  9. package/dist/app-shell/app-shell.js +11 -11
  10. package/dist/app-shell/llms.txt +13 -13
  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 +7 -7
  14. package/dist/auth/llms.txt +9 -9
  15. package/dist/autocomplete/autocomplete.d.ts +19 -6
  16. package/dist/autocomplete/autocomplete.js +23 -10
  17. package/dist/autocomplete/llms.txt +16 -5
  18. package/dist/avatar/avatar.d.ts +6 -6
  19. package/dist/avatar/avatar.js +5 -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 +3 -3
  23. package/dist/avatar-group/llms.txt +8 -8
  24. package/dist/backdrop/backdrop.d.ts +7 -7
  25. package/dist/backdrop/backdrop.js +16 -16
  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 +4 -4
  31. package/dist/bottom-sheet/bottom-sheet.js +2 -2
  32. package/dist/bottom-sheet/llms.txt +9 -9
  33. package/dist/breadcrumb/breadcrumb.d.ts +2 -2
  34. package/dist/breadcrumb/breadcrumb.js +3 -3
  35. package/dist/breadcrumb/llms.txt +3 -3
  36. package/dist/button/button.d.ts +3 -3
  37. package/dist/button/button.js +8 -8
  38. package/dist/button/llms.txt +27 -19
  39. package/dist/button-group/button-group.d.ts +9 -9
  40. package/dist/button-group/button-group.js +30 -8
  41. package/dist/button-group/llms.txt +20 -14
  42. package/dist/card/card.d.ts +8 -8
  43. package/dist/card/card.js +6 -6
  44. package/dist/card/llms.txt +20 -20
  45. package/dist/carousel/carousel.d.ts +6 -6
  46. package/dist/carousel/carousel.js +4 -4
  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 +1 -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 +1 -1
  53. package/dist/charts/chart.js +8 -8
  54. package/dist/charts/llms.txt +6 -6
  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 +42 -18
  58. package/dist/chat/llms.txt +32 -17
  59. package/dist/chat-composer/chat-composer.d.ts +17 -7
  60. package/dist/chat-composer/chat-composer.js +21 -8
  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 +9 -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 +17 -7
  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 +3 -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 +26 -7
  74. package/dist/checkbox/llms.txt +24 -12
  75. package/dist/chip/chip.d.ts +4 -4
  76. package/dist/chip/chip.js +2 -2
  77. package/dist/chip/llms.txt +11 -11
  78. package/dist/chips-input/chips-input.d.ts +4 -4
  79. package/dist/chips-input/chips-input.js +35 -9
  80. package/dist/chips-input/llms.txt +15 -8
  81. package/dist/clock/clock.d.ts +4 -4
  82. package/dist/clock/clock.js +7 -7
  83. package/dist/clock/llms.txt +8 -8
  84. package/dist/color-picker/color-picker.d.ts +6 -6
  85. package/dist/color-picker/color-picker.js +3 -3
  86. package/dist/color-picker/llms.txt +5 -5
  87. package/dist/confirm/confirm-container.d.ts +3 -3
  88. package/dist/confirm/confirm-container.js +5 -5
  89. package/dist/confirm/confirm-store.d.ts +4 -4
  90. package/dist/confirm/confirm-store.js +7 -7
  91. package/dist/confirm/llms.txt +6 -6
  92. package/dist/context-menu/context-menu.d.ts +1 -1
  93. package/dist/context-menu/context-menu.js +5 -5
  94. package/dist/context-menu/llms.txt +7 -7
  95. package/dist/data-table/data-table-types.d.ts +5 -5
  96. package/dist/data-table/data-table.d.ts +29 -8
  97. package/dist/data-table/data-table.js +340 -95
  98. package/dist/data-table/export-csv.d.ts +3 -3
  99. package/dist/data-table/export-csv.js +4 -4
  100. package/dist/data-table/llms.txt +127 -94
  101. package/dist/data-view/data-view.d.ts +13 -5
  102. package/dist/data-view/data-view.js +67 -6
  103. package/dist/data-view/llms.txt +29 -10
  104. package/dist/date-range/date-range.d.ts +9 -9
  105. package/dist/date-range/date-range.js +24 -24
  106. package/dist/date-range/llms.txt +20 -20
  107. package/dist/datepicker/datepicker.d.ts +10 -10
  108. package/dist/datepicker/datepicker.js +29 -29
  109. package/dist/datepicker/llms.txt +24 -24
  110. package/dist/dialog/dialog.d.ts +12 -7
  111. package/dist/dialog/dialog.js +4 -4
  112. package/dist/dialog/llms.txt +14 -11
  113. package/dist/divider/divider.d.ts +1 -1
  114. package/dist/divider/llms.txt +4 -4
  115. package/dist/drawer/drawer.d.ts +5 -5
  116. package/dist/drawer/drawer.js +2 -2
  117. package/dist/drawer/llms.txt +11 -11
  118. package/dist/empty/empty.d.ts +2 -2
  119. package/dist/empty/empty.js +1 -1
  120. package/dist/empty/llms.txt +9 -9
  121. package/dist/fab/fab.d.ts +1 -1
  122. package/dist/fab/fab.js +1 -1
  123. package/dist/fab/llms.txt +10 -6
  124. package/dist/fieldset/fieldset.d.ts +18 -4
  125. package/dist/fieldset/fieldset.js +9 -6
  126. package/dist/fieldset/llms.txt +26 -9
  127. package/dist/file-upload/file-upload.d.ts +3 -3
  128. package/dist/file-upload/file-upload.js +7 -7
  129. package/dist/file-upload/llms.txt +7 -7
  130. package/dist/form-field/form-field-context.d.ts +4 -4
  131. package/dist/form-field/form-field.d.ts +12 -4
  132. package/dist/form-field/form-field.js +26 -6
  133. package/dist/form-field/llms.txt +25 -11
  134. package/dist/galleria/galleria.d.ts +3 -3
  135. package/dist/galleria/galleria.js +5 -5
  136. package/dist/galleria/llms.txt +7 -7
  137. package/dist/grid/col.d.ts +4 -4
  138. package/dist/grid/col.js +2 -2
  139. package/dist/grid/flex.d.ts +3 -3
  140. package/dist/grid/flex.js +1 -1
  141. package/dist/grid/grid.d.ts +7 -7
  142. package/dist/grid/grid.js +3 -3
  143. package/dist/grid/layout.d.ts +2 -2
  144. package/dist/grid/layout.js +2 -2
  145. package/dist/grid/llms.txt +15 -15
  146. package/dist/grid-entry/public-api.js +1 -1
  147. package/dist/i18n/i18n-context.d.ts +3 -3
  148. package/dist/i18n/i18n-context.js +7 -7
  149. package/dist/icon/icon-registry.d.ts +3 -3
  150. package/dist/icon/icon-registry.js +3 -3
  151. package/dist/icon/icon.d.ts +4 -4
  152. package/dist/icon/icon.js +6 -6
  153. package/dist/icon/llms.txt +13 -13
  154. package/dist/icon-button/icon-button.d.ts +2 -2
  155. package/dist/icon-button/icon-button.js +1 -1
  156. package/dist/icon-button/llms.txt +9 -5
  157. package/dist/icons-entry/public-api.d.ts +2 -2
  158. package/dist/icons-entry/public-api.js +3 -3
  159. package/dist/image/image.js +1 -1
  160. package/dist/image/llms.txt +13 -13
  161. package/dist/input/input.d.ts +18 -5
  162. package/dist/input/input.js +21 -6
  163. package/dist/input/llms.txt +25 -11
  164. package/dist/input-mask/input-mask.d.ts +1 -1
  165. package/dist/input-mask/input-mask.js +2 -2
  166. package/dist/input-mask/llms.txt +5 -5
  167. package/dist/input-number/input-number.d.ts +9 -9
  168. package/dist/input-number/input-number.js +11 -11
  169. package/dist/input-number/llms.txt +14 -14
  170. package/dist/input-otp/input-otp.d.ts +6 -6
  171. package/dist/input-otp/input-otp.js +10 -10
  172. package/dist/input-otp/llms.txt +9 -9
  173. package/dist/internal/calendar-focus.d.ts +1 -1
  174. package/dist/internal/calendar-focus.js +3 -3
  175. package/dist/internal/cn.d.ts +1 -1
  176. package/dist/internal/mask.d.ts +2 -2
  177. package/dist/internal/mask.js +4 -4
  178. package/dist/internal/option-key.d.ts +2 -2
  179. package/dist/internal/option-key.js +2 -2
  180. package/dist/internal/option-nav.d.ts +4 -4
  181. package/dist/internal/option-nav.js +5 -5
  182. package/dist/internal/overlay-container.js +1 -1
  183. package/dist/internal/overlay.d.ts +3 -3
  184. package/dist/internal/overlay.js +8 -8
  185. package/dist/internal/password-strength.d.ts +2 -2
  186. package/dist/internal/password-strength.js +6 -6
  187. package/dist/internal/roving.d.ts +2 -2
  188. package/dist/internal/roving.js +3 -3
  189. package/dist/internal/scroll-lock.d.ts +1 -1
  190. package/dist/internal/scroll-lock.js +2 -2
  191. package/dist/internal/slot-children.d.ts +3 -3
  192. package/dist/internal/slot-children.js +4 -4
  193. package/dist/internal/slot.d.ts +3 -3
  194. package/dist/internal/slot.js +2 -2
  195. package/dist/internal/step.d.ts +3 -3
  196. package/dist/internal/step.js +4 -4
  197. package/dist/internal/tween.d.ts +5 -5
  198. package/dist/internal/tween.js +5 -5
  199. package/dist/internal/types.d.ts +2 -2
  200. package/dist/internal/use-breakpoint.d.ts +2 -2
  201. package/dist/internal/use-breakpoint.js +4 -4
  202. package/dist/internal/use-controllable-state.d.ts +1 -1
  203. package/dist/internal/use-controllable-state.js +3 -3
  204. package/dist/internal/use-id.d.ts +2 -2
  205. package/dist/internal/use-id.js +2 -2
  206. package/dist/internal/use-modal-focus.d.ts +1 -1
  207. package/dist/internal/use-modal-focus.js +3 -3
  208. package/dist/knob/knob.d.ts +4 -4
  209. package/dist/knob/knob.js +14 -10
  210. package/dist/knob/llms.txt +17 -12
  211. package/dist/listbox/listbox.d.ts +6 -6
  212. package/dist/listbox/listbox.js +9 -9
  213. package/dist/listbox/llms.txt +10 -10
  214. package/dist/loader/llms.txt +56 -0
  215. package/dist/loader/loader.d.ts +49 -0
  216. package/dist/loader/loader.js +68 -0
  217. package/dist/map/llms.txt +25 -22
  218. package/dist/map/map-create-dialog.d.ts +4 -4
  219. package/dist/map/map-create-dialog.js +10 -10
  220. package/dist/map/map-hours-field.d.ts +4 -4
  221. package/dist/map/map-hours-field.js +7 -7
  222. package/dist/map/map-image-field.d.ts +2 -2
  223. package/dist/map/map-image-field.js +4 -4
  224. package/dist/map/map-legend.d.ts +4 -4
  225. package/dist/map/map-legend.js +2 -2
  226. package/dist/map/map-poi-card.d.ts +3 -3
  227. package/dist/map/map-poi-card.js +9 -9
  228. package/dist/map/map-search.d.ts +17 -6
  229. package/dist/map/map-search.js +21 -18
  230. package/dist/map/map.d.ts +21 -14
  231. package/dist/map/map.js +30 -29
  232. package/dist/map/ports.d.ts +9 -9
  233. package/dist/map/ports.js +6 -6
  234. package/dist/map-entry/public-api.js +2 -2
  235. package/dist/megamenu/llms.txt +9 -9
  236. package/dist/megamenu/megamenu.d.ts +6 -6
  237. package/dist/megamenu/megamenu.js +10 -10
  238. package/dist/mention/llms.txt +15 -15
  239. package/dist/mention/mention.d.ts +5 -5
  240. package/dist/mention/mention.js +2 -2
  241. package/dist/mention/mentions.d.ts +1 -1
  242. package/dist/mention/mentions.js +1 -1
  243. package/dist/menu/llms.txt +12 -12
  244. package/dist/menu/menu-item.d.ts +4 -4
  245. package/dist/menu/menu-keys.d.ts +2 -2
  246. package/dist/menu/menu-keys.js +5 -5
  247. package/dist/menu/menu.d.ts +2 -2
  248. package/dist/menu/menu.js +6 -6
  249. package/dist/menubar/llms.txt +10 -10
  250. package/dist/menubar/menubar.d.ts +5 -5
  251. package/dist/menubar/menubar.js +10 -10
  252. package/dist/meter-group/llms.txt +12 -12
  253. package/dist/meter-group/meter-group.d.ts +2 -2
  254. package/dist/meter-group/meter-group.js +1 -1
  255. package/dist/motion/motion-context.d.ts +22 -0
  256. package/dist/motion/motion-context.js +28 -0
  257. package/dist/order-list/llms.txt +22 -10
  258. package/dist/order-list/order-list.d.ts +12 -4
  259. package/dist/order-list/order-list.js +104 -21
  260. package/dist/paginator/llms.txt +8 -8
  261. package/dist/paginator/paginator.js +1 -1
  262. package/dist/panel/llms.txt +25 -13
  263. package/dist/panel/panel.d.ts +11 -3
  264. package/dist/panel/panel.js +7 -4
  265. package/dist/password/llms.txt +16 -8
  266. package/dist/password/password.d.ts +2 -2
  267. package/dist/password/password.js +24 -3
  268. package/dist/peek/llms.txt +10 -10
  269. package/dist/peek/peek.d.ts +6 -6
  270. package/dist/peek/peek.js +7 -7
  271. package/dist/pick-list/llms.txt +33 -13
  272. package/dist/pick-list/pick-list.d.ts +12 -4
  273. package/dist/pick-list/pick-list.js +111 -23
  274. package/dist/popover/llms.txt +11 -11
  275. package/dist/popover/popover.d.ts +4 -4
  276. package/dist/popover/popover.js +5 -5
  277. package/dist/progress/llms.txt +17 -17
  278. package/dist/progress/progress.d.ts +7 -7
  279. package/dist/progress/progress.js +6 -6
  280. package/dist/public-api.d.ts +15 -3
  281. package/dist/public-api.js +13 -7
  282. package/dist/radio-group/llms.txt +13 -6
  283. package/dist/radio-group/radio-group.d.ts +11 -4
  284. package/dist/radio-group/radio-group.js +15 -7
  285. package/dist/rating/llms.txt +20 -13
  286. package/dist/rating/rating-pointer.d.ts +16 -0
  287. package/dist/rating/rating-pointer.js +26 -0
  288. package/dist/rating/rating.d.ts +6 -6
  289. package/dist/rating/rating.js +76 -12
  290. package/dist/rich-text/lists.d.ts +2 -2
  291. package/dist/rich-text/lists.js +7 -7
  292. package/dist/rich-text/llms.txt +26 -17
  293. package/dist/rich-text/rich-text.d.ts +12 -7
  294. package/dist/rich-text/rich-text.js +78 -54
  295. package/dist/rich-text/sanitize.d.ts +3 -3
  296. package/dist/rich-text/sanitize.js +12 -12
  297. package/dist/rich-text/tables.d.ts +3 -3
  298. package/dist/rich-text/tables.js +3 -3
  299. package/dist/roadmap/llms.txt +15 -15
  300. package/dist/roadmap/roadmap.d.ts +6 -6
  301. package/dist/roadmap/roadmap.js +19 -19
  302. package/dist/roadmap-entry/public-api.js +2 -2
  303. package/dist/scroll-top/llms.txt +6 -6
  304. package/dist/scroll-top/scroll-top.d.ts +1 -1
  305. package/dist/scroll-top/scroll-top.js +1 -1
  306. package/dist/select/llms.txt +19 -12
  307. package/dist/select/select.d.ts +8 -8
  308. package/dist/select/select.js +47 -21
  309. package/dist/shared/types.d.ts +5 -5
  310. package/dist/shared/types.js +1 -1
  311. package/dist/sidenav/llms.txt +9 -9
  312. package/dist/sidenav/sidenav.d.ts +1 -1
  313. package/dist/sidenav/sidenav.js +1 -1
  314. package/dist/skeleton/llms.txt +3 -3
  315. package/dist/skeleton/skeleton.d.ts +1 -1
  316. package/dist/skeleton/skeleton.js +1 -1
  317. package/dist/slider/llms.txt +32 -13
  318. package/dist/slider/slider-drag.d.ts +26 -0
  319. package/dist/slider/slider-drag.js +79 -0
  320. package/dist/slider/slider.d.ts +6 -5
  321. package/dist/slider/slider.js +27 -8
  322. package/dist/speech/llms.txt +97 -0
  323. package/dist/speech/speech-button.d.ts +44 -0
  324. package/dist/speech/speech-button.js +86 -0
  325. package/dist/speech/speech-wave.d.ts +19 -0
  326. package/dist/speech/speech-wave.js +16 -0
  327. package/dist/speech/use-speech-recognition.d.ts +81 -0
  328. package/dist/speech/use-speech-recognition.js +179 -0
  329. package/dist/speed-dial/llms.txt +14 -8
  330. package/dist/speed-dial/speed-dial.d.ts +1 -1
  331. package/dist/speed-dial/speed-dial.js +1 -1
  332. package/dist/spinner/llms.txt +7 -7
  333. package/dist/spinner/spinner.d.ts +1 -1
  334. package/dist/spinner/spinner.js +1 -1
  335. package/dist/split-button/llms.txt +21 -15
  336. package/dist/split-button/split-button.d.ts +2 -2
  337. package/dist/split-button/split-button.js +4 -4
  338. package/dist/splitter/llms.txt +11 -11
  339. package/dist/splitter/splitter.d.ts +3 -3
  340. package/dist/splitter/splitter.js +5 -5
  341. package/dist/stepper/llms.txt +33 -14
  342. package/dist/stepper/stepper.d.ts +6 -6
  343. package/dist/stepper/stepper.js +53 -10
  344. package/dist/switch/llms.txt +12 -5
  345. package/dist/switch/switch.d.ts +10 -3
  346. package/dist/switch/switch.js +12 -5
  347. package/dist/switch-group/llms.txt +9 -9
  348. package/dist/switch-group/switch-group.d.ts +4 -4
  349. package/dist/switch-group/switch-group.js +4 -4
  350. package/dist/tabs/llms.txt +10 -10
  351. package/dist/tabs/tabs.d.ts +5 -5
  352. package/dist/tabs/tabs.js +8 -8
  353. package/dist/textarea/llms.txt +23 -12
  354. package/dist/textarea/textarea.d.ts +19 -6
  355. package/dist/textarea/textarea.js +21 -7
  356. package/dist/timeline/llms.txt +4 -4
  357. package/dist/timeline/timeline.d.ts +1 -1
  358. package/dist/timeline/timeline.js +1 -1
  359. package/dist/toast/llms.txt +47 -17
  360. package/dist/toast/toast-container.d.ts +9 -3
  361. package/dist/toast/toast-container.js +17 -9
  362. package/dist/toast/toast-store.d.ts +40 -4
  363. package/dist/toast/toast-store.js +36 -1
  364. package/dist/toolbar/llms.txt +7 -7
  365. package/dist/toolbar/toolbar.d.ts +3 -3
  366. package/dist/toolbar/toolbar.js +2 -2
  367. package/dist/tooltip/llms.txt +10 -10
  368. package/dist/tooltip/tooltip.d.ts +3 -3
  369. package/dist/tooltip/tooltip.js +18 -18
  370. package/dist/tree/llms.txt +25 -12
  371. package/dist/tree/tree-keys.d.ts +5 -5
  372. package/dist/tree/tree-keys.js +7 -7
  373. package/dist/tree/tree.d.ts +14 -7
  374. package/dist/tree/tree.js +53 -18
  375. package/dist/update/llms.txt +54 -0
  376. package/dist/update/update.d.ts +49 -0
  377. package/dist/update/update.js +82 -0
  378. package/package.json +15 -13
@@ -4,11 +4,13 @@ import { cn } from "../internal/cn.js";
4
4
  import { useControllableState } from "../internal/use-controllable-state.js";
5
5
  import { joinIds, useKptFieldA11y } from "../form-field/form-field-context.js";
6
6
  import { useKptMessages } from "../i18n/i18n-context.js";
7
+ import { kptRangePress, kptTrackDrag } from "./slider-drag.js";
7
8
  /**
8
9
  * Suwak wartości. W trybie `range` udostępnia dwa uchwyty, a wartością jest para `[min, max]`.
9
10
  *
10
- * Oba uchwyty to natywne `<input type="range">` leżące na sobie — dzięki temu klawiatura,
11
- * 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`).
12
14
  *
13
15
  * @example
14
16
  * <KptSlider value={volume} onValueChange={setVolume} min={0} max={100} step={5} showValue />
@@ -17,7 +19,7 @@ import { useKptMessages } from "../i18n/i18n-context.js";
17
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 }) {
18
20
  const { t } = useKptMessages({ locale, dictionary });
19
21
  const field = useKptFieldA11y(id, 'kpt-slider');
20
- /** 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". */
21
23
  const thumbLabel = (end) => {
22
24
  if (field.labelledBy)
23
25
  return undefined;
@@ -37,7 +39,7 @@ export function KptSlider({ value, defaultValue, onValueChange, range = false, m
37
39
  const span = max - min;
38
40
  return span <= 0 ? 0 : ((val - min) / span) * 100;
39
41
  };
40
- // Uchwyty nie mogą się wyminąć — dolny nie przekracza górnego i odwrotnie.
42
+ // Uchwyty nie mogą się wyminąć - dolny nie przekracza górnego i odwrotnie.
41
43
  // Suwaki są kontrolowane: przy `readOnly` stan się nie zmienia, więc React cofa uchwyt sam.
42
44
  const setLow = (next) => {
43
45
  if (!readOnly)
@@ -47,8 +49,25 @@ export function KptSlider({ value, defaultValue, onValueChange, range = false, m
47
49
  if (!readOnly)
48
50
  setCurrent([low, Math.max(next, low)]);
49
51
  };
50
- 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, "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?.() })] }), 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)}%` }, "aria-readonly": readOnly || undefined, onChange: (event) => {
51
- if (!readOnly)
52
- setCurrent(Number(event.target.value));
53
- }, onBlur: () => onTouch?.() }), showValue ? (_jsxs("span", { className: "kpt-slider__value", children: [single, suffix] })) : null] })) }));
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] })) }));
54
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
+ }
@@ -0,0 +1,81 @@
1
+ import { type RefObject } from 'react';
2
+ import { type KptSpeechEndReason, type KptSpeechErrorCode, type KptSpeechInterimDisplay, type KptSpeechMode, type KptSpeechSnapshot, type KptSpeechStatus, type KptSpeechSupport, type KptSpeechTargetInput, type KptSpeechWaveMode } from '@konce-pt/speech';
3
+ /** Cel dyktowania: element, `ref` do elementu, gotowy `KptSpeechTarget` albo funkcja. */
4
+ export type KptSpeechTargetRef = KptSpeechTargetInput | RefObject<HTMLElement | null>;
5
+ export interface KptSpeechRecognitionOptions {
6
+ /** Gdzie trafia tekst. Bez celu kontroler tylko zgłasza `onResult`. */
7
+ target?: KptSpeechTargetRef | null;
8
+ /** Język rozpoznawania (BCP 47); bez niego z `KptI18nProvider` i języka przeglądarki. */
9
+ lang?: string;
10
+ /** Słuchaj do "stop" (domyślnie) albo do końca jednej wypowiedzi. */
11
+ continuous?: boolean;
12
+ /** `insert` (w kursor, domyślnie), `append` albo `replace` (czyści cel w chwili startu). */
13
+ mode?: KptSpeechMode;
14
+ /** Pokaz tekstu tymczasowego; bez wartości - domyślny dla rodzaju celu. */
15
+ interim?: KptSpeechInterimDisplay;
16
+ /** Wielka litera na początku zdania. Domyślnie `true`. */
17
+ capitalize?: boolean;
18
+ /** Fala: `auto` (na żywo na desktopie, syntetyczna na telefonie), `live`, `synthetic`, `none`. */
19
+ wave?: KptSpeechWaveMode;
20
+ /** Słupki fali na żywo. Domyślnie 5. */
21
+ bars?: number;
22
+ /** Zatrzymaj po tylu ms bez nowego słowa; `0` - nigdy. */
23
+ silenceMs?: number;
24
+ /** Twardy limit długości dyktowania w ms; `0` - bez limitu. */
25
+ maxDuration?: number;
26
+ /** Rozpoznawanie wyłącznie w urządzeniu (Chrome 135+). */
27
+ processLocally?: boolean;
28
+ disabled?: boolean;
29
+ /**
30
+ * Czy kontroler w ogóle istnieje. `false` nie tworzy sesji ani nasłuchów strony - dla pól,
31
+ * w których mikrofon jest opcją (`speech` w `KptInput`). Domyślnie `true`.
32
+ */
33
+ enabled?: boolean;
34
+ onStart?: () => void;
35
+ onEnd?: (reason: KptSpeechEndReason) => void;
36
+ /** Stan transkryptu po każdej zmianie: zatwierdzony i tymczasowy osobno. */
37
+ onResult?: (snapshot: KptSpeechSnapshot) => void;
38
+ onError?: (code: KptSpeechErrorCode) => void;
39
+ }
40
+ export interface KptSpeechRecognition {
41
+ status: KptSpeechStatus;
42
+ /** Trwa nasłuch (także start i łagodne zatrzymywanie). */
43
+ listening: boolean;
44
+ /** Wsparcie przeglądarki - `null` do pierwszego renderu po stronie klienta (SSR). */
45
+ support: KptSpeechSupport | null;
46
+ /** Da się dyktować: przeglądarka rozpoznaje mowę, a usługa nie odmówiła (`network`). */
47
+ available: boolean;
48
+ /** Ostatni błąd albo `null`. */
49
+ error: KptSpeechErrorCode | null;
50
+ /** Powód końca ostatniego dyktowania; `null` przed pierwszym i w trakcie nasłuchu. */
51
+ lastEnd: KptSpeechEndReason | null;
52
+ /** Silnik słyszy mowę - napęd fali syntetycznej. */
53
+ speaking: boolean;
54
+ /** Poziomy fali na żywo (0..1), puste poza nasłuchem i w trybie syntetycznym. */
55
+ levels: readonly number[];
56
+ /** Rozstrzygnięty tryb fali. */
57
+ waveMode: Exclude<KptSpeechWaveMode, 'auto'>;
58
+ snapshot: KptSpeechSnapshot;
59
+ start: () => void;
60
+ /** Łagodny stop: ostatnie słowa jeszcze się zatwierdzą. */
61
+ stop: () => void;
62
+ /** Zatwierdź to, co widać, i skończ od razu - przed wysłaniem treści. */
63
+ flush: () => void;
64
+ /** Przerwij bez zatwierdzania tekstu tymczasowego. */
65
+ abort: () => void;
66
+ toggle: () => void;
67
+ }
68
+ /**
69
+ * Dyktowanie bez UI - sesja Web Speech API, wstawianie w cel i poziomy fali z `@konce-pt/speech`.
70
+ * `KptSpeechButton` używa go pod spodem; własny przycisk albo skrót klawiszowy dostaje to samo.
71
+ *
72
+ * Tekst tymczasowy widać w celu, ale do wartości (stanu kontrolowanego pola) trafia wyłącznie
73
+ * zatwierdzony - przez zwykłą edycję, którą `onChange` widzi jak wpisany tekst.
74
+ *
75
+ * @example
76
+ * const fieldRef = useRef<HTMLTextAreaElement>(null);
77
+ * const speech = useKptSpeechRecognition({ target: fieldRef });
78
+ * <KptSpeechButton speech={speech} />
79
+ * <KptButton onClick={() => { speech.flush(); send(); }}>Send</KptButton>
80
+ */
81
+ export declare function useKptSpeechRecognition(options?: KptSpeechRecognitionOptions): KptSpeechRecognition;
@@ -0,0 +1,179 @@
1
+ 'use client';
2
+ import { useEffect, useEffectEvent, useLayoutEffect, useRef, useState, useSyncExternalStore } from 'react';
3
+ import { browserSpeechEngine, createSpeechSession, defaultSpeechLang, mirrorSpeechLevels, onSpeechPageHidden, resolveSpeechTarget, resolveWaveMode, speechSupport, speechUnavailable, startSpeechMeter, } from '@konce-pt/speech';
4
+ import { useKptI18n } from "../i18n/i18n-context.js";
5
+ // Wsparcie przeglądarki nie zmienia się w trakcie życia strony - liczone raz, czytane przez
6
+ // `useSyncExternalStore`, żeby serwer i hydracja widziały "nieznane", a klient od razu prawdę.
7
+ let supportCache = null;
8
+ const readSupport = () => (supportCache ??= speechSupport());
9
+ const readServerSupport = () => null;
10
+ const subscribeNever = () => () => undefined;
11
+ const EMPTY_SNAPSHOT = { finalText: '', interimText: '' };
12
+ const NO_LEVELS = [];
13
+ function isRef(value) {
14
+ return typeof value === 'object' && value !== null && 'current' in value && !('begin' in value);
15
+ }
16
+ /**
17
+ * Dyktowanie bez UI - sesja Web Speech API, wstawianie w cel i poziomy fali z `@konce-pt/speech`.
18
+ * `KptSpeechButton` używa go pod spodem; własny przycisk albo skrót klawiszowy dostaje to samo.
19
+ *
20
+ * Tekst tymczasowy widać w celu, ale do wartości (stanu kontrolowanego pola) trafia wyłącznie
21
+ * zatwierdzony - przez zwykłą edycję, którą `onChange` widzi jak wpisany tekst.
22
+ *
23
+ * @example
24
+ * const fieldRef = useRef<HTMLTextAreaElement>(null);
25
+ * const speech = useKptSpeechRecognition({ target: fieldRef });
26
+ * <KptSpeechButton speech={speech} />
27
+ * <KptButton onClick={() => { speech.flush(); send(); }}>Send</KptButton>
28
+ */
29
+ export function useKptSpeechRecognition(options = {}) {
30
+ const { locale } = useKptI18n();
31
+ const support = useSyncExternalStore(subscribeNever, readSupport, readServerSupport);
32
+ const enabled = options.enabled ?? true;
33
+ const [status, setStatus] = useState('idle');
34
+ const [error, setError] = useState(null);
35
+ const [speaking, setSpeaking] = useState(false);
36
+ const [levels, setLevels] = useState(NO_LEVELS);
37
+ const [meterFailed, setMeterFailed] = useState(false);
38
+ const [unavailable, setUnavailable] = useState(false);
39
+ const [snapshot, setSnapshot] = useState(EMPTY_SNAPSHOT);
40
+ const [lastEnd, setLastEnd] = useState(null);
41
+ const sessionRef = useRef(null);
42
+ const targetRef = useRef(null);
43
+ const targetElementRef = useRef(null);
44
+ const meterRef = useRef(null);
45
+ const listening = status === 'starting' || status === 'listening' || status === 'stopping';
46
+ const available = enabled && !!support?.supported && !unavailable && !speechUnavailable();
47
+ const resolvedWave = support ? resolveWaveMode(options.wave ?? 'auto', support) : 'none';
48
+ const waveMode = resolvedWave === 'live' && meterFailed ? 'synthetic' : resolvedWave;
49
+ const emitStart = useEffectEvent(() => options.onStart?.());
50
+ const emitEnd = useEffectEvent((reason) => options.onEnd?.(reason));
51
+ const emitResult = useEffectEvent((next) => options.onResult?.(next));
52
+ const emitError = useEffectEvent((code) => options.onError?.(code));
53
+ // Escape w celu zatrzymuje dyktowanie - jak na samym przycisku.
54
+ const onTargetKeydown = useRef((event) => {
55
+ if (event.key !== 'Escape')
56
+ return;
57
+ const session = sessionRef.current;
58
+ if (!session || session.status === 'idle' || session.status === 'error')
59
+ return;
60
+ event.preventDefault();
61
+ session.stop();
62
+ });
63
+ // Sesja żyje tyle, co komponent. Słuchacze wołają zdarzenia efektu - zawsze z najnowszymi
64
+ // propsami, bez odtwarzania sesji przy każdym renderze.
65
+ useEffect(() => {
66
+ if (!enabled)
67
+ return;
68
+ const engine = browserSpeechEngine();
69
+ if (!engine)
70
+ return;
71
+ const releaseTarget = () => {
72
+ targetRef.current?.end();
73
+ targetRef.current = null;
74
+ targetElementRef.current?.removeEventListener('keydown', onTargetKeydown.current);
75
+ targetElementRef.current = null;
76
+ meterRef.current?.stop();
77
+ meterRef.current = null;
78
+ setLevels(NO_LEVELS);
79
+ setSpeaking(false);
80
+ };
81
+ const session = createSpeechSession({ engine }, { lang: 'en-US' }, {
82
+ status: (next) => {
83
+ setStatus(next);
84
+ if (next === 'starting')
85
+ emitStart();
86
+ },
87
+ interim: (text) => targetRef.current?.interim(text),
88
+ commit: (text) => targetRef.current?.commit(text),
89
+ snapshot: (next) => {
90
+ setSnapshot(next);
91
+ emitResult(next);
92
+ },
93
+ speech: (active) => setSpeaking(active),
94
+ error: (code) => {
95
+ setError(code);
96
+ if (speechUnavailable())
97
+ setUnavailable(true);
98
+ emitError(code);
99
+ },
100
+ end: (reason) => {
101
+ setLastEnd(reason);
102
+ releaseTarget();
103
+ emitEnd(reason);
104
+ },
105
+ });
106
+ sessionRef.current = session;
107
+ const offPageHidden = onSpeechPageHidden(() => session.stop());
108
+ return () => {
109
+ offPageHidden();
110
+ session.abort();
111
+ releaseTarget();
112
+ sessionRef.current = null;
113
+ };
114
+ }, [enabled]);
115
+ // Kontrolowane pole nadpisuje `value` stanem przy każdym renderze rodzica - a stan nie zna
116
+ // tekstu tymczasowego, więc React by go zmywał (przy fali na żywo co klatkę). Po każdym commicie,
117
+ // jeszcze przed malowaniem, cel przywraca tekst tymczasowy na swoje miejsce: nic nie mruga.
118
+ useLayoutEffect(() => {
119
+ targetRef.current?.refresh?.();
120
+ });
121
+ const start = () => {
122
+ const session = sessionRef.current;
123
+ if (!session || !support || !available || options.disabled)
124
+ return;
125
+ if (session.status !== 'idle' && session.status !== 'error')
126
+ return;
127
+ setError(null);
128
+ setLastEnd(null);
129
+ setMeterFailed(false);
130
+ setSnapshot(EMPTY_SNAPSHOT);
131
+ const raw = options.target;
132
+ const target = isRef(raw) ? raw.current : raw;
133
+ if (target) {
134
+ targetElementRef.current = target instanceof HTMLElement ? target : null;
135
+ targetElementRef.current?.addEventListener('keydown', onTargetKeydown.current);
136
+ const capitalize = options.capitalize ?? true;
137
+ targetRef.current = resolveSpeechTarget(target, options.interim ? { capitalize, interim: options.interim } : { capitalize });
138
+ targetRef.current.begin(options.mode ?? 'insert');
139
+ }
140
+ session.configure({
141
+ lang: options.lang ?? defaultSpeechLang(locale, globalThis.navigator?.language),
142
+ continuous: options.continuous ?? true,
143
+ processLocally: options.processLocally ?? false,
144
+ autoRestart: !support.ios,
145
+ silenceMs: options.silenceMs ?? 0,
146
+ maxDurationMs: options.maxDuration ?? 0,
147
+ });
148
+ // Miernik startuje jeszcze w geście kliknięcia - poza nim `AudioContext` jest zawieszony.
149
+ if (waveMode === 'live') {
150
+ meterRef.current = startSpeechMeter({
151
+ bars: options.bars ?? 5,
152
+ onLevels: (next) => setLevels(mirrorSpeechLevels(next)),
153
+ onFail: () => setMeterFailed(true),
154
+ });
155
+ }
156
+ session.start();
157
+ };
158
+ const stop = () => sessionRef.current?.stop();
159
+ const flush = () => sessionRef.current?.flush();
160
+ const abort = () => sessionRef.current?.abort();
161
+ const toggle = () => (listening ? stop() : start());
162
+ return {
163
+ status,
164
+ listening,
165
+ support,
166
+ available,
167
+ error,
168
+ lastEnd,
169
+ speaking,
170
+ levels,
171
+ waveMode,
172
+ snapshot,
173
+ start,
174
+ stop,
175
+ flush,
176
+ abort,
177
+ toggle,
178
+ };
179
+ }