@konce-pt/react 0.12.0 → 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 (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 +4 -4
  111. package/dist/dialog/dialog.js +4 -4
  112. package/dist/dialog/llms.txt +11 -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
@@ -5,24 +5,24 @@ editing. The value is HTML.
5
5
  Import: `import { KptRichText, type KptRichTool } from '@konce-pt/react';`
6
6
 
7
7
  ## Props
8
- - `value` / `defaultValue` / `onValueChange` — the HTML
9
- - `toolbar`: `KptRichTool[] | KptRichTool[][]` — a flat list, or groups = sections separated by a
8
+ - `value` / `defaultValue` / `onValueChange` - the HTML
9
+ - `toolbar`: `KptRichTool[] | KptRichTool[][]` - a flat list, or groups = sections separated by a
10
10
  divider. Leaving a tool or a group out disables it; the full toolbar in 5 sections by default.
11
11
  - `placeholder`, `maxListLevel` (6), `disabled`, `invalid`, `touched`, `onTouch()`
12
- - `mentions`: `readonly KptMentionItem[]`, `mentionLimit` (8), `onMention(item)` — suggestions
12
+ - `mentions`: `readonly KptMentionItem[]`, `mentionLimit` (8), `onMention(item)` - suggestions
13
13
  for `@`; an empty list turns the mechanism off, so `@` stays an ordinary character
14
14
  - `className`, `ref` and every other `<div>` prop go to the host
15
15
 
16
16
  ## Mentions (`@`)
17
17
  With `mentions` set, typing `@` opens a suggestion list anchored at the caret: arrows move through
18
18
  it, Enter or Tab inserts, Escape closes. The query is read from the **text node** at the caret and
19
- recognised by `mentionQuery()` from `@konce-pt/mention` — the same rules as in the Angular port.
19
+ recognised by `mentionQuery()` from `@konce-pt/mention` - the same rules as in the Angular port.
20
20
  An inserted mention is `<span class="kpt-mention" data-mention="id" contenteditable="false">@Name`
21
21
  plus a non-breaking space: the id survives saving, the flag keeps the mention in one piece and the
22
22
  space gives the caret a place to stand after it.
23
23
 
24
24
  ## Tools (KptRichTool)
25
- Clipboard: `copy`, `paste` (sanitized), `pasteText`. Style: `heading` (a panel: Normal + H1–H6).
25
+ Clipboard: `copy`, `paste` (sanitized), `pasteText`. Style: `heading` (a panel: Normal + H1-H6).
26
26
  Format: `bold`, `italic`, `underline`, `strike`, `ul`, `ol`, `indent`, `outdent`.
27
27
  Colours: `foreColor`, `backColor` (a palette + a native colour input). Alignment: `alignLeft`,
28
28
  `alignCenter`, `alignRight`, `alignJustify`. Plus `link`, `table`, `clear`, `html`.
@@ -30,43 +30,43 @@ Colours: `foreColor`, `backColor` (a palette + a native colour input). Alignment
30
30
  ## The editor is not rendered by React
31
31
  `contentEditable` cannot be a React-rendered subtree: re-rendering would wipe the selection and the
32
32
  caret on every keystroke. The value is written into the DOM by hand, only when it differs from what
33
- is already there — so typing never triggers a rewrite, and sanitization runs solely on writes
33
+ is already there - so typing never triggers a rewrite, and sanitization runs solely on writes
34
34
  coming from the application or the HTML source view.
35
35
 
36
36
  ## Everything that enters the DOM is sanitized
37
37
  Not just paste: a `value` from a database, a CMS or another user is untrusted too. Tags outside the
38
38
  allowlist are unwrapped (the text survives), `on*` handlers and `javascript:` URLs are dropped,
39
39
  `style` is filtered declaration by declaration. `sanitize.ts` knows nothing about React.
40
- Links keep only relative addresses and `http(s):`, `mailto:`, `tel:` — the same allowlist as the link tool, so
40
+ Links keep only relative addresses and `http(s):`, `mailto:`, `tel:` - the same allowlist as the link tool, so
41
41
  `data:`, `vbscript:` or `blob:` from a paste are dropped. `class` keeps only the editor's own classes
42
42
  (`kpt-rt-*`, `kpt-mention`), a link with `target` always gets `rel="noopener noreferrer"`, and when the
43
- sanitizer removed something, the cleaned HTML is written back to `value` — the model never holds the
43
+ sanitizer removed something, the cleaned HTML is written back to `value` - the model never holds the
44
44
  unsafe original (a pure formatting difference does not touch it, so a clean value does not dirty a form).
45
45
 
46
46
  ## Lists are indented by our own DOM code
47
47
  `execCommand('indent')` produces invalid `ul > ul` in Chrome, so nesting goes through `lists.ts`
48
48
  (`li > ul > li`, a sublist inherits the parent's type). Tab and Shift+Tab indent and outdent, but
49
- only when the operation actually changed something — otherwise Tab stays native and the editor
49
+ only when the operation actually changed something - otherwise Tab stays native and the editor
50
50
  never becomes a keyboard trap (WCAG 2.1.2). In a table cell Tab moves to the next cell; a list
51
51
  inside the cell takes precedence.
52
52
 
53
53
  ## The toolbar is one Tab stop
54
54
  The toolbar is a named `role="toolbar"` ("Formatting", `richText.toolbar`) with a roving tabindex:
55
- `←`/`→` (mirrored in RTL) and `Home`/`End` move between tools, skipping disabled ones; the stop
55
+ `ArrowLeft`/`ArrowRight` (mirrored in RTL) and `Home`/`End` move between tools, skipping disabled ones; the stop
56
56
  remembers the last tool, so Tab from the toolbar goes straight to the editor.
57
57
 
58
58
  Tool panels open in the overlay container at the end of `<body>`, so focus moves into them:
59
- the paragraph style list is a `listbox` (`↑`/`↓`, `Home`/`End`, `Enter`/`Space` picks, `Escape`/`Tab` close);
60
- the table grid is one stop (arrows change the size 1–8, the size is announced, `Enter` inserts);
61
- the colour palette starts on the first swatch (`←`/`→` between swatches, `Tab` cycles inside the
59
+ the paragraph style list is a `listbox` (`ArrowUp`/`ArrowDown`, `Home`/`End`, `Enter`/`Space` picks, `Escape`/`Tab` close);
60
+ the table grid is one stop (arrows change the size 1-8, the size is announced, `Enter` inserts);
61
+ the colour palette starts on the first swatch (`ArrowLeft`/`ArrowRight` between swatches, `Tab` cycles inside the
62
62
  panel). `Escape` closes any of them and returns focus to the tool.
63
63
 
64
64
  ## Tables
65
- Insert from an 8×8 grid popover. Dragging across cells makes a rectangular selection that grows
65
+ Insert from an 8x8 grid popover. Dragging across cells makes a rectangular selection that grows
66
66
  until it fully covers merged cells on its edges. Right-click opens the menu: insert and delete rows
67
- and columns, header row and column (td↔th), cell background, alignment, borders, merge and split,
67
+ and columns, header row and column (td<->th), cell background, alignment, borders, merge and split,
68
68
  delete the table. Dragging a column's right edge changes its width. `tables.ts` holds the grid
69
- math — also framework-free.
69
+ math - also framework-free.
70
70
 
71
71
  ## Point-anchored panels
72
72
  The table menu and its popovers have no trigger element, so they do not go through `KptOverlay`:
@@ -98,8 +98,17 @@ their hex codes. The table size grid is still mouse-only.
98
98
  `id` goes to the focusable element, not the host; without one it comes from `KptFormField` or is
99
99
  generated. A `div` with a role is not named by `label[for]`, so inside `KptFormField` it gets
100
100
  `aria-labelledby` pointing at the wrapper's label (unless `ariaLabel` is set), and the hint or error
101
- is added to `aria-describedby`. New: `id`, `ariaLabel` and `required` (→ `aria-required`) on the
101
+ is added to `aria-describedby`. New: `id`, `ariaLabel` and `required` (-> `aria-required`) on the
102
102
  `role="textbox"` area.
103
103
 
104
104
  ## Read-only and required
105
105
  `readOnly` keeps the content selectable and copyable but not editable (`aria-readonly`); the toolbar is disabled.
106
+
107
+ ## Dictation
108
+ The `dictate` tool (not in the default toolbar) dictates at the caret: final text goes in through
109
+ `insertText` (native undo, `input` -> value); interim text is an overlay next to the caret
110
+ (`.kpt-speech-ghost`) and never enters the HTML. Hidden without browser support, stopped by the HTML
111
+ source view and by `disabled` / `readOnly`. Language: `speechLang` (BCP 47, default from
112
+ `KptI18nProvider`), listening colour: `speechColor` (theme colour by default).
113
+
114
+ <KptRichText value={body} onValueChange={setBody} toolbar={[['bold', 'italic'], ['dictate']]} />
@@ -1,8 +1,9 @@
1
1
  import { type ComponentPropsWithRef } from 'react';
2
2
  import { type KptMentionItem } from '@konce-pt/mention';
3
3
  import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
4
- export type KptRichTool = 'copy' | 'paste' | 'pasteText' | 'heading' | 'bold' | 'italic' | 'underline' | 'strike' | 'ul' | 'ol' | 'indent' | 'outdent' | 'foreColor' | 'backColor' | 'alignLeft' | 'alignCenter' | 'alignRight' | 'alignJustify' | 'link' | 'table' | 'clear' | 'html';
5
- /** Pasek: płaska lista narzędzi albo grupy — sekcje rozdzielone separatorem. */
4
+ import { type KptSpeechColor } from '../speech/speech-button.tsx';
5
+ export type KptRichTool = 'copy' | 'paste' | 'pasteText' | 'heading' | 'bold' | 'italic' | 'underline' | 'strike' | 'ul' | 'ol' | 'indent' | 'outdent' | 'foreColor' | 'backColor' | 'alignLeft' | 'alignCenter' | 'alignRight' | 'alignJustify' | 'link' | 'table' | 'clear' | 'html' | 'dictate';
6
+ /** Pasek: płaska lista narzędzi albo grupy - sekcje rozdzielone separatorem. */
6
7
  export type KptRichToolbar = readonly KptRichTool[] | readonly (readonly KptRichTool[])[];
7
8
  export interface KptRichTextProps extends Omit<ComponentPropsWithRef<'div'>, 'onChange' | 'children'>, KptI18nOverrides {
8
9
  /** Wartość jako HTML. */
@@ -11,16 +12,16 @@ export interface KptRichTextProps extends Omit<ComponentPropsWithRef<'div'>, 'on
11
12
  onValueChange?: (value: string) => void;
12
13
  toolbar?: KptRichToolbar;
13
14
  placeholder?: string;
14
- /** Maksymalna głębokość zagnieżdżenia list (Tab, „Dodaj wcięcie"). */
15
+ /** Maksymalna głębokość zagnieżdżenia list (Tab, "Dodaj wcięcie"). */
15
16
  maxListLevel?: number;
16
- /** Id elementu fokusowalnego (nie hosta); bez niego generowane, w `KptFormField` — z wrappera. */
17
+ /** Id elementu fokusowalnego (nie hosta); bez niego generowane, w `KptFormField` - z wrappera. */
17
18
  id?: string;
18
19
  /** Nazwa dostępna, gdy kontrolki nie opisuje etykieta (`<label for>` albo `KptFormField`). */
19
20
  ariaLabel?: string | null;
20
21
  /** Oznacza pole jako wymagane atrybutem `aria-required`. */
21
22
  required?: boolean;
22
23
  disabled?: boolean;
23
- /** Tylko do odczytu — treść da się zaznaczyć i skopiować, nie edytować (`aria-readonly`). */
24
+ /** Tylko do odczytu - treść da się zaznaczyć i skopiować, nie edytować (`aria-readonly`). */
24
25
  readOnly?: boolean;
25
26
  invalid?: boolean;
26
27
  touched?: boolean;
@@ -32,7 +33,11 @@ export interface KptRichTextProps extends Omit<ComponentPropsWithRef<'div'>, 'on
32
33
  mentions?: readonly KptMentionItem[];
33
34
  /** Ile podpowiedzi naraz. */
34
35
  mentionLimit?: number;
35
- /** Wstawiona wzmianka — moment na powiadomienie osoby albo dopięcie powiązania. */
36
+ /** Wstawiona wzmianka - moment na powiadomienie osoby albo dopięcie powiązania. */
36
37
  onMention?: (item: KptMentionItem) => void;
38
+ /** Język narzędzia `dictate` (BCP 47); bez niego z `KptI18nProvider`. */
39
+ speechLang?: string;
40
+ /** Kolor "stop" i fali w trakcie nasłuchu; domyślnie kolor motywu. */
41
+ speechColor?: KptSpeechColor;
37
42
  }
38
- export declare function KptRichText({ value, defaultValue, onValueChange, toolbar, placeholder, maxListLevel, id, ariaLabel, required, disabled: disabledProp, readOnly, invalid, touched, onTouch, mentions, mentionLimit, onMention, locale, dictionary, className, ...rest }: Readonly<KptRichTextProps>): import("react").JSX.Element;
43
+ export declare function KptRichText({ value, defaultValue, onValueChange, toolbar, placeholder, maxListLevel, id, ariaLabel, required, disabled: disabledProp, readOnly, invalid, touched, onTouch, mentions, mentionLimit, onMention, speechLang, speechColor, locale, dictionary, className, ...rest }: Readonly<KptRichTextProps>): import("react").JSX.Element;
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { useCallback, useEffect, useRef, useState, } from 'react';
3
+ import { useCallback, useEffect, useEffectEvent, useRef, useState, } from 'react';
4
4
  import { createPortal } from 'react-dom';
5
5
  import { cn } from "../internal/cn.js";
6
6
  import { useControllableState } from "../internal/use-controllable-state.js";
@@ -9,6 +9,8 @@ import { useKptOverlayContainer } from "../internal/overlay-container.js";
9
9
  import { KptOverlay } from "../internal/overlay.js";
10
10
  import { useKptMessages } from "../i18n/i18n-context.js";
11
11
  import { KptIcon } from "../icon/icon.js";
12
+ import { KptSpeechButton } from "../speech/speech-button.js";
13
+ import { useKptSpeechRecognition } from "../speech/use-speech-recognition.js";
12
14
  import { sanitizeHtml, sanitizeHtmlReport, sanitizeUrl } from "./sanitize.js";
13
15
  import { canIndentItem, canOutdentItem, selectedItems, shiftItems } from "./lists.js";
14
16
  import { CELL_SELECTED, deleteColumn, deleteRow, insertColumn, insertRow, mergeCells, regionBetween, splitCell, tableHtml, toggleHeaderColumn, toggleHeaderRow, } from "./tables.js";
@@ -20,7 +22,7 @@ const ICON = {
20
22
  copy: 'copy',
21
23
  paste: 'clipboard',
22
24
  pasteText: 'clipboard-text',
23
- heading: 'code', // nieużywane — „heading" renderuje select
25
+ heading: 'code', // nieużywane - "heading" renderuje select
24
26
  bold: 'bold',
25
27
  italic: 'italic',
26
28
  underline: 'underline',
@@ -39,8 +41,9 @@ const ICON = {
39
41
  table: 'table',
40
42
  clear: 'clear-formatting',
41
43
  html: 'code',
44
+ dictate: 'microphone', // nieużywane - "dictate" renderuje KptSpeechButton
42
45
  };
43
- /** Narzędzia zwykłe → komenda `execCommand` (zarazem nazwa dla `queryCommandState`). */
46
+ /** Narzędzia zwykłe -> komenda `execCommand` (zarazem nazwa dla `queryCommandState`). */
44
47
  const COMMAND = {
45
48
  bold: 'bold',
46
49
  italic: 'italic',
@@ -54,7 +57,7 @@ const COMMAND = {
54
57
  alignJustify: 'justifyFull',
55
58
  clear: 'removeFormat',
56
59
  };
57
- /** Paleta kolorów z nazwami (`richText.swatches.*`) — sam kod szesnastkowy nic czytnikowi nie mówi. */
60
+ /** Paleta kolorów z nazwami (`richText.swatches.*`) - sam kod szesnastkowy nic czytnikowi nie mówi. */
58
61
  const SWATCHES = [
59
62
  { color: '#000000', name: 'black' },
60
63
  { color: '#434343', name: 'darkGray' },
@@ -98,10 +101,10 @@ function legacyState(cmd) {
98
101
  return false;
99
102
  }
100
103
  }
101
- export function KptRichText({ value, defaultValue = '', onValueChange, toolbar = DEFAULT_TOOLBAR, placeholder = '', maxListLevel = 6, id, ariaLabel = null, required = false, disabled: disabledProp = false, readOnly = false, invalid = false, touched, onTouch, mentions = [], mentionLimit = KPT_MENTION_LIMIT, onMention, locale, dictionary, className, ...rest }) {
102
- /** Edycja zablokowana: wyłączone pole albo tylko do odczytu — narzędzia i skróty nie działają. */
104
+ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar = DEFAULT_TOOLBAR, placeholder = '', maxListLevel = 6, id, ariaLabel = null, required = false, disabled: disabledProp = false, readOnly = false, invalid = false, touched, onTouch, mentions = [], mentionLimit = KPT_MENTION_LIMIT, onMention, speechLang, speechColor, locale, dictionary, className, ...rest }) {
105
+ /** Edycja zablokowana: wyłączone pole albo tylko do odczytu - narzędzia i skróty nie działają. */
103
106
  const disabled = disabledProp || readOnly;
104
- /** `contenteditable` nie dostaje nazwy z `label[for]` — stąd `aria-labelledby` na etykietę wrappera. */
107
+ /** `contenteditable` nie dostaje nazwy z `label[for]` - stąd `aria-labelledby` na etykietę wrappera. */
105
108
  const field = useKptFieldA11y(id, 'kpt-rich-text');
106
109
  const { t } = useKptMessages({ locale, dictionary });
107
110
  const container = useKptOverlayContainer();
@@ -109,7 +112,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
109
112
  const savedRange = useRef(null);
110
113
  const [current, setCurrent] = useControllableState({ value, defaultValue, onChange: onValueChange });
111
114
  const showInvalid = invalid && (touched ?? true);
112
- // Kotwice narzędzi z panelem — `KptOverlay` przypina panel do konkretnego przycisku.
115
+ // Kotwice narzędzi z panelem - `KptOverlay` przypina panel do konkretnego przycisku.
113
116
  const [styleAnchor, setStyleAnchor] = useState(null);
114
117
  const [foreAnchor, setForeAnchor] = useState(null);
115
118
  const [backAnchor, setBackAnchor] = useState(null);
@@ -118,10 +121,10 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
118
121
  /** Okienko adresu linku (zamiast `window.prompt()`) i wpisywany adres. */
119
122
  const [linkOpen, setLinkOpen] = useState(false);
120
123
  const [linkDraft, setLinkDraft] = useState('');
121
- /** Menu tabeli otwarte z klawiatury — po zamontowaniu fokus idzie na pierwszą pozycję. */
124
+ /** Menu tabeli otwarte z klawiatury - po zamontowaniu fokus idzie na pierwszą pozycję. */
122
125
  const menuFromKeyboard = useRef(false);
123
126
  const [styleOpen, setStyleOpen] = useState(false);
124
- /** Narzędzie, na którym ostatnio stał fokus paska — przystanek Tab przy powrocie. */
127
+ /** Narzędzie, na którym ostatnio stał fokus paska - przystanek Tab przy powrocie. */
125
128
  const [lastTool, setLastTool] = useState(null);
126
129
  const [colorOpen, setColorOpen] = useState(false);
127
130
  const [colorKind, setColorKind] = useState('foreColor');
@@ -132,13 +135,13 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
132
135
  const [sourceView, setSourceView] = useState(false);
133
136
  const [menuOpen, setMenuOpen] = useState(false);
134
137
  const [menuPoint, setMenuPoint] = useState({ x: 0, y: 0 });
135
- // ── Wzmianki (`@`) ──
138
+ // -- Wzmianki (`@`) --
136
139
  const [mentionOpen, setMentionOpen] = useState(false);
137
140
  const [mentionMatches, setMentionMatches] = useState([]);
138
141
  const [mentionActive, setMentionActive] = useState(0);
139
- /** Zaczepienie listy w miejscu kursora — punkt, nie element, bo `@` nie ma własnego elementu. */
142
+ /** Zaczepienie listy w miejscu kursora - punkt, nie element, bo `@` nie ma własnego elementu. */
140
143
  const [mentionPoint, setMentionPoint] = useState({ x: 0, y: 0 });
141
- /** Węzeł tekstowy i zakres zapytania `@…`, który podmieni wybrana pozycja. */
144
+ /** Węzeł tekstowy i zakres zapytania `@...`, który podmieni wybrana pozycja. */
142
145
  const mentionRange = useRef(null);
143
146
  const [borderOpen, setBorderOpen] = useState(false);
144
147
  const [borderTarget, setBorderTarget] = useState('table');
@@ -158,6 +161,18 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
158
161
  : Array.isArray(toolbar[0])
159
162
  ? toolbar
160
163
  : [toolbar];
164
+ // -- Dyktowanie (narzędzie `dictate`) --
165
+ // Cel to sam obszar edycji: tekst zatwierdzony wchodzi przez `insertText` w miejsce kursora
166
+ // (natywne undo, zdarzenie `input` -> wartość), tymczasowy jest nakładką przy kursorze i nie trafia
167
+ // do HTML-a. Bez narzędzia w pasku kontroler nie tworzy sesji.
168
+ const hasDictate = sections.some((section) => section.includes('dictate'));
169
+ const mic = useKptSpeechRecognition({ enabled: hasDictate, target: editorRef, lang: speechLang, disabled });
170
+ // Widok źródła zdejmuje obszar edycji, a blokada zabrania pisania - dyktowanie przerywamy.
171
+ const stopDictation = useEffectEvent(() => mic.abort());
172
+ useEffect(() => {
173
+ if (sourceView || disabled)
174
+ stopDictation();
175
+ }, [sourceView, disabled]);
161
176
  const pushValue = useCallback(() => {
162
177
  const el = editorRef.current;
163
178
  if (el)
@@ -165,10 +180,10 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
165
180
  }, [setCurrent]);
166
181
  /*
167
182
  * Zapis z zewnątrz wstawiamy ręcznie, bo `contenteditable` nie może być renderowany przez
168
- * Reacta — przerysowanie kasowałoby zaznaczenie i pozycję kursora przy każdym znaku.
183
+ * Reacta - przerysowanie kasowałoby zaznaczenie i pozycję kursora przy każdym znaku.
169
184
  * Porównanie z surowym `innerHTML` sprawia, że pisanie w edytorze nie wchodzi w tę gałąź,
170
185
  * więc sanityzacja włącza się wyłącznie przy zapisie z aplikacji albo z trybu HTML. Gdy
171
- * sanityzator coś usunął, oczyszczona treść wraca też do wartości — inaczej aplikacja dostawałaby
186
+ * sanityzator coś usunął, oczyszczona treść wraca też do wartości - inaczej aplikacja dostawałaby
172
187
  * z powrotem niebezpieczny HTML. Sama różnica zapisu (cudzysłowy) wartości nie rusza.
173
188
  */
174
189
  useEffect(() => {
@@ -185,7 +200,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
185
200
  const selection = window.getSelection();
186
201
  return !!el && !!selection && selection.rangeCount > 0 && el.contains(selection.anchorNode);
187
202
  }, []);
188
- /** Tag bloku, w którym stoi kursor — dla selecta stylu; domyślnie paragraf. */
203
+ /** Tag bloku, w którym stoi kursor - dla selecta stylu; domyślnie paragraf. */
189
204
  const blockTag = useCallback(() => {
190
205
  let node = window.getSelection()?.anchorNode;
191
206
  while (node && node !== editorRef.current) {
@@ -216,19 +231,19 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
216
231
  selCells.current = new Set();
217
232
  setSelCount(0);
218
233
  }, []);
219
- /** Zamknięcie podpowiedzi — czyści też zapamiętany zakres zapytania. */
234
+ /** Zamknięcie podpowiedzi - czyści też zapamiętany zakres zapytania. */
220
235
  const closeMention = useCallback(() => {
221
236
  mentionRange.current = null;
222
237
  setMentionMatches([]);
223
238
  setMentionOpen(false);
224
239
  }, []);
225
- /** Punkt pod kursorem — lista podpowiedzi zaczepia się o niego, bo `@` nie ma swojego elementu. */
240
+ /** Punkt pod kursorem - lista podpowiedzi zaczepia się o niego, bo `@` nie ma swojego elementu. */
226
241
  const caretPoint = useCallback((node, offset) => {
227
242
  const range = document.createRange();
228
243
  range.setStart(node, Math.min(offset, node.length));
229
244
  range.collapse(true);
230
245
  const rect = range.getBoundingClientRect();
231
- // Zwinięty zakres na początku wiersza potrafi dać zerowy prostokąt — wtedy bierzemy
246
+ // Zwinięty zakres na początku wiersza potrafi dać zerowy prostokąt - wtedy bierzemy
232
247
  // krawędź samego edytora, żeby lista nie wylądowała w rogu ekranu.
233
248
  if (rect.width === 0 && rect.height === 0) {
234
249
  const fallback = editorRef.current?.getBoundingClientRect();
@@ -237,11 +252,11 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
237
252
  return { x: rect.left, y: rect.bottom };
238
253
  }, []);
239
254
  /**
240
- * Sprawdza, czy kursor stoi w zapytaniu `@…`, i ustawia listę podpowiedzi.
255
+ * Sprawdza, czy kursor stoi w zapytaniu `@...`, i ustawia listę podpowiedzi.
241
256
  *
242
257
  * Zapytanie liczymy z **węzła tekstowego**, nie z całego HTML-a: w treści siedzą tagi, a `@`
243
258
  * ma widzieć tylko to, co użytkownik naprawdę napisał w tym miejscu. Rozpoznanie zostawiamy
244
- * `mentionQuery()` z `@konce-pt/mention` — te same reguły działają w obu portach.
259
+ * `mentionQuery()` z `@konce-pt/mention` - te same reguły działają w obu portach.
245
260
  */
246
261
  const syncMention = useCallback(() => {
247
262
  if (disabled || !mentions.length) {
@@ -271,10 +286,10 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
271
286
  setMentionOpen(true);
272
287
  }, [caretPoint, closeMention, disabled, mentionLimit, mentions]);
273
288
  /**
274
- * Wstawia wzmiankę w miejsce zapytania `@…`.
289
+ * Wstawia wzmiankę w miejsce zapytania `@...`.
275
290
  *
276
291
  * Wzmianka jest elementem, nie samym tekstem: `data-mention` niesie identyfikator, który
277
- * przeżyje zmianę nazwy, a `contenteditable="false"` trzyma ją w jednym kawałku — bez tego
292
+ * przeżyje zmianę nazwy, a `contenteditable="false"` trzyma ją w jednym kawałku - bez tego
278
293
  * kursor wchodziłby w środek nazwiska i psuł zapis. Spacja po wzmiance daje gdzie postawić
279
294
  * kursor: bez niej nie da się wyjść poza element, który nie jest edytowalny.
280
295
  */
@@ -309,7 +324,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
309
324
  useEffect(() => {
310
325
  const onSelectionChange = () => {
311
326
  refreshState();
312
- // Kursor może odjechać od `@` także bez pisania (klik, strzałki) — wtedy podpowiedzi
327
+ // Kursor może odjechać od `@` także bez pisania (klik, strzałki) - wtedy podpowiedzi
313
328
  // muszą zniknąć, inaczej Enter wstawiłby wzmiankę w zupełnie innym miejscu.
314
329
  syncMention();
315
330
  };
@@ -374,7 +389,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
374
389
  selection.addRange(savedRange.current);
375
390
  }
376
391
  };
377
- // ── Schowek ──
392
+ // -- Schowek --
378
393
  const copySelection = async () => {
379
394
  const selection = window.getSelection();
380
395
  if (!selection || selection.rangeCount === 0 || selection.isCollapsed)
@@ -411,7 +426,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
411
426
  pushValue();
412
427
  }
413
428
  catch {
414
- /* brak uprawnień albo API — ignorujemy */
429
+ /* brak uprawnień albo API - ignorujemy */
415
430
  }
416
431
  };
417
432
  const pastePlain = async () => {
@@ -422,10 +437,10 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
422
437
  pushValue();
423
438
  }
424
439
  catch {
425
- /* brak uprawnień albo API — ignorujemy */
440
+ /* brak uprawnień albo API - ignorujemy */
426
441
  }
427
442
  };
428
- // ── Narzędzia ──
443
+ // -- Narzędzia --
429
444
  const exec = (item) => {
430
445
  if (disabled)
431
446
  return;
@@ -470,7 +485,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
470
485
  };
471
486
  /**
472
487
  * Tab: wcięcie w liście, przeskok komórki w tabeli. Poza nimi i na krawędziach zostawiamy Tab
473
- * natywny — inaczej edytor byłby pułapką klawiaturową (WCAG 2.1.2).
488
+ * natywny - inaczej edytor byłby pułapką klawiaturową (WCAG 2.1.2).
474
489
  */
475
490
  /** Klawiatura listy podpowiedzi; `true` = zdarzenie obsłużone i edytor go już nie zobaczy. */
476
491
  const onMentionKeyDown = (event) => {
@@ -531,7 +546,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
531
546
  const cells = [...table.querySelectorAll('td, th')];
532
547
  const next = cells[cells.indexOf(cell) + (event.shiftKey ? -1 : 1)];
533
548
  if (!next)
534
- return; // koniec tabeli — Tab zostaje natywny
549
+ return; // koniec tabeli - Tab zostaje natywny
535
550
  event.preventDefault();
536
551
  const range = document.createRange();
537
552
  range.selectNodeContents(next);
@@ -541,7 +556,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
541
556
  clearCellSelection();
542
557
  refreshState();
543
558
  };
544
- /** Natywne wklejanie — czyścimy treść, żeby nie wnosić śmieci z edytorów tekstu i stron. */
559
+ /** Natywne wklejanie - czyścimy treść, żeby nie wnosić śmieci z edytorów tekstu i stron. */
545
560
  const onPaste = (event) => {
546
561
  if (disabled)
547
562
  return;
@@ -554,7 +569,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
554
569
  legacyExec('insertText', text);
555
570
  pushValue();
556
571
  };
557
- // ── Kolory ──
572
+ // -- Kolory --
558
573
  const activeCells = () => {
559
574
  if (selCells.current.size)
560
575
  return [...selCells.current];
@@ -576,7 +591,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
576
591
  legacyExec('backColor', color);
577
592
  pushValue();
578
593
  };
579
- // ── Tabele ──
594
+ // -- Tabele --
580
595
  const pickStyle = (tag) => {
581
596
  setStyleOpen(false);
582
597
  focusEditor();
@@ -585,7 +600,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
585
600
  pushValue();
586
601
  refreshState();
587
602
  };
588
- /** Siatka tabeli z klawiatury: strzałki zmieniają rozmiar (1–8), Enter wstawia, Escape wraca. */
603
+ /** Siatka tabeli z klawiatury: strzałki zmieniają rozmiar (1-8), Enter wstawia, Escape wraca. */
589
604
  const onGridKeyDown = (event) => {
590
605
  const clamp = (n) => Math.min(GRID_ROWS.length, Math.max(1, n));
591
606
  const r = grid.r || 1;
@@ -621,7 +636,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
621
636
  const afterTableOp = () => {
622
637
  const fromMenu = !!document.activeElement?.closest('.kpt-rt-menu');
623
638
  setMenuOpen(false);
624
- // Pozycja wybrana z klawiatury znika razem z menu — fokus wraca do edytora, nie na <body>.
639
+ // Pozycja wybrana z klawiatury znika razem z menu - fokus wraca do edytora, nie na <body>.
625
640
  if (fromMenu)
626
641
  focusEditor();
627
642
  pushValue();
@@ -637,7 +652,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
637
652
  if (handleMenuKeydown(event.currentTarget, event.nativeEvent) === 'close')
638
653
  closeMenuToEditor();
639
654
  };
640
- /** Menu tabeli z klawiatury: klawisz menu albo `Shift+F10` w komórce — jak natywne menu kontekstowe. */
655
+ /** Menu tabeli z klawiatury: klawisz menu albo `Shift+F10` w komórce - jak natywne menu kontekstowe. */
641
656
  const openMenuFromKeyboard = () => {
642
657
  const node = window.getSelection()?.anchorNode ?? null;
643
658
  const el = node instanceof Element ? node : (node?.parentElement ?? null);
@@ -653,7 +668,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
653
668
  setMenuOpen(true);
654
669
  return true;
655
670
  };
656
- /** Okienko adresu zamiast `window.prompt()` — nazwane pole, `Enter` wstawia, `Escape` wraca do tekstu. */
671
+ /** Okienko adresu zamiast `window.prompt()` - nazwane pole, `Enter` wstawia, `Escape` wraca do tekstu. */
657
672
  const openLink = () => {
658
673
  if (disabled)
659
674
  return;
@@ -753,7 +768,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
753
768
  }
754
769
  pushValue();
755
770
  };
756
- // ── Prezentacja narzędzi ──
771
+ // -- Prezentacja narzędzi --
757
772
  const styleOptions = [
758
773
  { tag: 'p', label: t('richText.styleNormal') },
759
774
  ...[1, 2, 3, 4, 5, 6].map((level) => ({
@@ -768,17 +783,19 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
768
783
  const cmd = COMMAND[item];
769
784
  return !!cmd && activeCmds.has(cmd);
770
785
  };
771
- /** `aria-pressed` tylko dla przełączników — wcięcia są akcjami, nie stanem. */
786
+ /** `aria-pressed` tylko dla przełączników - wcięcia są akcjami, nie stanem. */
772
787
  const pressed = (item) => item === 'indent' || item === 'outdent' ? undefined : isActive(item);
773
788
  const enabled = (item) => {
774
789
  if (item === 'indent')
775
790
  return canIndent;
776
791
  if (item === 'outdent')
777
792
  return canOutdent;
793
+ if (item === 'dictate')
794
+ return mic.available && !sourceView;
778
795
  return true;
779
796
  };
780
797
  /**
781
- * Panele narzędzi żyją w nakładce na końcu `<body>` — po zamontowaniu fokus wchodzi do środka,
798
+ * Panele narzędzi żyją w nakładce na końcu `<body>` - po zamontowaniu fokus wchodzi do środka,
782
799
  * inaczej Tab musiałby przejść przez całą stronę, żeby do nich dojść (WCAG 2.1.1).
783
800
  */
784
801
  // Refy stabilne (`useCallback`): nowa funkcja przy każdym renderze byłaby wołana od nowa
@@ -826,7 +843,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
826
843
  };
827
844
  const paletteContent = (_jsxs("div", { ref: focusSwatchOnMount, className: "kpt-rich-text__palette", role: "group", "aria-label": t(colorKind === 'backColor' ? 'richText.backColor' : 'richText.foreColor'), onMouseDown: (event) => event.preventDefault(), onKeyDown: onPaletteKeyDown, children: [_jsx("div", { className: "kpt-rich-text__swatches", children: SWATCHES.map((swatch) => (_jsx("button", { type: "button", className: "kpt-rich-text__swatch", style: { background: swatch.color }, "aria-label": t(`richText.swatches.${swatch.name}`), onClick: () => applyColor(swatch.color) }, swatch.color))) }), _jsxs("label", { className: "kpt-rich-text__picker", children: [_jsx("span", { children: t('richText.paletteCustom') }), _jsx("input", { type: "color", onInput: (event) => applyColor(event.target.value) })] })] }));
828
845
  /**
829
- * Panel otwarty przy dolnej albo prawej krawędzi okna wychodziłby poza ekran — CDK robił to
846
+ * Panel otwarty przy dolnej albo prawej krawędzi okna wychodziłby poza ekran - CDK robił to
830
847
  * w Angularze sam, tutaj przesuwamy go po zamontowaniu, gdy już znać jego rozmiar.
831
848
  */
832
849
  const clampToViewport = (el) => {
@@ -839,13 +856,13 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
839
856
  el.style.top = `${top}px`;
840
857
  };
841
858
  /**
842
- * Panel przypięty do punktu kursora — menu tabeli i jego popovery nie mają wyzwalacza, więc
859
+ * Panel przypięty do punktu kursora - menu tabeli i jego popovery nie mają wyzwalacza, więc
843
860
  * nie przechodzą przez `KptOverlay`; pozycję niosą współrzędne kliknięcia, tak jak
844
861
  * w `KptContextMenu`.
845
862
  */
846
863
  const pointPanel = (content, onClose) => {
847
864
  if (!container)
848
- return null; // render na serwerze — nie ma czego portalować
865
+ return null; // render na serwerze - nie ma czego portalować
849
866
  const layer = { zIndex: 'var(--kpt-z-overlay, 1100)' };
850
867
  return createPortal(_jsxs(_Fragment, { children: [_jsx("div", { style: { position: 'fixed', inset: 0, ...layer }, onClick: onClose, onContextMenu: (event) => {
851
868
  event.preventDefault();
@@ -853,33 +870,35 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
853
870
  } }), _jsx("div", { ref: clampToViewport, style: { position: 'fixed', left: menuPoint.x, top: menuPoint.y, ...layer }, children: content })] }), container);
854
871
  };
855
872
  /**
856
- * Przystanek Tab paska: ostatnio użyte narzędzie, a gdy jest wyłączone albo go nie ma —
873
+ * Przystanek Tab paska: ostatnio użyte narzędzie, a gdy jest wyłączone albo go nie ma -
857
874
  * pierwsze dostępne. Dzięki temu `Tab` z paska idzie od razu do edytora, a nie przez kilkanaście
858
875
  * przycisków.
859
876
  */
860
877
  const usableTools = sections.flat().filter((item) => !disabled && enabled(item));
861
878
  const toolStop = lastTool && usableTools.includes(lastTool) ? lastTool : (usableTools[0] ?? null);
862
879
  const rovingProps = (item) => ({ 'data-tool': item, tabIndex: item === toolStop ? 0 : -1 });
863
- /** Strzałki po pasku (w RTL odwrócone), `Home`/`End` — skrajne; wyłączone narzędzia są pomijane. */
880
+ /** Strzałki po pasku (w RTL odwrócone), `Home`/`End` - skrajne; wyłączone narzędzia są pomijane. */
864
881
  const onToolbarKeyDown = (event) => {
865
882
  const bar = event.currentTarget;
866
- const buttons = [...bar.querySelectorAll('[data-tool]')];
883
+ const tools = [...bar.querySelectorAll('[data-tool]')];
884
+ // Narzędzie może być komponentem (`dictate` to KptSpeechButton) - fokus dostaje jego przycisk.
885
+ const buttons = tools.map((tool) => (tool instanceof HTMLButtonElement ? tool : tool.querySelector('button')));
867
886
  const current = buttons.indexOf(event.target);
868
887
  if (current < 0)
869
888
  return;
870
889
  const rtl = getComputedStyle(bar).direction === 'rtl';
871
890
  const key = rtl && event.key === 'ArrowLeft' ? 'ArrowRight' : rtl && event.key === 'ArrowRight' ? 'ArrowLeft' : event.key;
872
- const next = nextRovingIndex(buttons.map((button) => button.disabled), current, key);
891
+ const next = nextRovingIndex(buttons.map((button) => !button || button.disabled), current, key);
873
892
  if (next === null)
874
893
  return;
875
894
  event.preventDefault();
876
- setLastTool(buttons[next].dataset['tool']);
877
- buttons[next].focus();
895
+ setLastTool(tools[next].dataset['tool']);
896
+ buttons[next]?.focus();
878
897
  };
879
898
  const toolButton = (item) => (_jsx("button", { ...rovingProps(item), ref: item === 'link' ? setLinkAnchor : undefined, "aria-haspopup": item === 'link' ? 'dialog' : undefined, "aria-expanded": item === 'link' ? linkOpen : undefined, type: "button", className: cn('kpt-rich-text__tool', isActive(item) && 'is-active'), "aria-label": t(`richText.${item}`), "aria-pressed": pressed(item), title: t(`richText.${item}`), disabled: disabled || !enabled(item), onMouseDown: (event) => event.preventDefault(), onClick: () => exec(item), children: _jsx(KptIcon, { name: ICON[item] }) }, item));
880
899
  /**
881
900
  * Akcje menu tabeli po identyfikatorze. Handlery żyją tutaj, a nie jako domknięcia przekazywane
882
- * do `menuItem()` — funkcja wołana w renderze nie dostaje wtedy niczego, co czyta refy.
901
+ * do `menuItem()` - funkcja wołana w renderze nie dostaje wtedy niczego, co czyta refy.
883
902
  */
884
903
  const runTableAction = (action) => {
885
904
  switch (action) {
@@ -1004,6 +1023,11 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
1004
1023
  if (item === 'table') {
1005
1024
  return (_jsx("button", { ref: setTableAnchor, ...rovingProps(item), type: "button", className: cn('kpt-rich-text__tool', tableOpen && 'is-active'), "aria-label": t('richText.table'), title: t('richText.table'), disabled: disabled, onMouseDown: (event) => event.preventDefault(), onClick: () => exec(item), children: _jsx(KptIcon, { name: ICON[item] }) }, item));
1006
1025
  }
1026
+ if (item === 'dictate') {
1027
+ // `data-tool` na hoście - roving znajduje narzędzie, a fokus dostaje jego przycisk.
1028
+ const host = { className: 'kpt-rich-text__dictate', 'data-tool': item };
1029
+ return (_jsx(KptSpeechButton, { listeningColor: speechColor, speech: mic, size: "sm", unsupported: "hide", wavePosition: "end", tabIndex: item === toolStop ? 0 : -1, disabled: disabled || sourceView, hostProps: host }, item));
1030
+ }
1007
1031
  return toolButton(item);
1008
1032
  })] }, index))) }), sourceView ? (_jsx("textarea", { className: "kpt-rich-text__source", value: current, disabled: disabled, spellCheck: false, "aria-label": t('richText.htmlSource'), onChange: (event) => setCurrent(event.target.value), onBlur: () => onTouch?.() })) : (_jsx("div", { ref: editorRef, className: "kpt-rich-text__editor", contentEditable: !disabled,
1009
1033
  // Wyłączony edytor (contentEditable=false) wypada z kolejności Tab jak wyłączone pole.
@@ -1023,7 +1047,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
1023
1047
  insertMention(item);
1024
1048
  }, onMouseEnter: () => setMentionActive(index), children: [_jsx("span", { className: "kpt-rt-mentions__label", children: item.label }), item.kind ? _jsx("span", { className: "kpt-rt-mentions__kind", children: item.kind }) : null] }, item.id))) }), container)
1025
1049
  : null, _jsx(KptOverlay, { open: linkOpen, anchor: linkAnchor, onClose: () => closeLink(false), offset: 4, children: _jsxs("form", { className: "kpt-rt-link", role: "dialog",
1026
- // Bez walidacji przeglądarki: „example.com” jest poprawnym wpisem — `sanitizeUrl` dopisze https://.
1050
+ // Bez walidacji przeglądarki: "example.com" jest poprawnym wpisem - `sanitizeUrl` dopisze https://.
1027
1051
  noValidate: true, "aria-label": t('richText.link'), onSubmit: (event) => {
1028
1052
  event.preventDefault();
1029
1053
  applyLink();
@@ -1034,7 +1058,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
1034
1058
  event.stopPropagation();
1035
1059
  closeLink(true);
1036
1060
  }, children: [_jsx("label", { className: "kpt-rt-link__label", htmlFor: `${field.id}-link`, children: t('richText.linkPrompt') }), _jsx("input", { ref: (el) => {
1037
- // Panel jest niewidoczny do pierwszego pomiaru — fokus po klatce.
1061
+ // Panel jest niewidoczny do pierwszego pomiaru - fokus po klatce.
1038
1062
  if (el)
1039
1063
  requestAnimationFrame(() => el.focus());
1040
1064
  }, className: "kpt-rt-link__input", id: `${field.id}-link`, type: "url", inputMode: "url", placeholder: "https://", value: linkDraft, onChange: (event) => setLinkDraft(event.target.value) }), _jsx("button", { type: "submit", className: "kpt-rt-link__apply", children: t('richText.linkApply') })] }) }), _jsx(KptOverlay, { open: styleOpen, anchor: styleAnchor, onClose: () => setStyleOpen(false), offset: 4, children: _jsx("ul", { ref: focusPanelOnMount, className: "kpt-rich-text__style-panel", role: "listbox", tabIndex: -1, "aria-label": t('richText.heading'), "aria-activedescendant": `${field.id}-style-${styleActive}`, onKeyDown: (event) => {
@@ -1061,9 +1085,9 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
1061
1085
  setTableOpen(false);
1062
1086
  setGrid({ r: 0, c: 0 });
1063
1087
  }, offset: 4, children: _jsxs("div", { className: "kpt-rt-grid-pop", onMouseDown: (event) => event.preventDefault(), children: [_jsx("div", { ref: focusGridOnMount, className: "kpt-rt-grid", role: "group",
1064
- // Jeden przystanek ze strzałkami (rozmiar tabeli) — grupa z własną obsługą klawiatury.
1088
+ // Jeden przystanek ze strzałkami (rozmiar tabeli) - grupa z własną obsługą klawiatury.
1065
1089
  // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex -- patrz wyżej
1066
- tabIndex: 0, "aria-label": t('richText.table'), "aria-describedby": `${field.id}-grid-size`, "aria-keyshortcuts": "ArrowUp ArrowDown ArrowLeft ArrowRight Enter Escape", onKeyDown: onGridKeyDown, onMouseLeave: () => setGrid({ r: 0, c: 0 }), children: GRID_ROWS.map((r) => (_jsx("div", { className: "kpt-rt-grid__row", children: GRID_COLS.map((c) => (_jsx("span", { className: cn('kpt-rt-grid__cell', r <= grid.r && c <= grid.c && 'is-on'), onMouseEnter: () => setGrid({ r, c }), onClick: () => insertTableAt(r, c) }, c))) }, r))) }), _jsxs("div", { className: "kpt-rt-grid__label", id: `${field.id}-grid-size`, "aria-live": "polite", children: [grid.r, " \u00D7 ", grid.c] })] }) }), menuOpen
1090
+ tabIndex: 0, "aria-label": t('richText.table'), "aria-describedby": `${field.id}-grid-size`, "aria-keyshortcuts": "ArrowUp ArrowDown ArrowLeft ArrowRight Enter Escape", onKeyDown: onGridKeyDown, onMouseLeave: () => setGrid({ r: 0, c: 0 }), children: GRID_ROWS.map((r) => (_jsx("div", { className: "kpt-rt-grid__row", children: GRID_COLS.map((c) => (_jsx("span", { className: cn('kpt-rt-grid__cell', r <= grid.r && c <= grid.c && 'is-on'), onMouseEnter: () => setGrid({ r, c }), onClick: () => insertTableAt(r, c) }, c))) }, r))) }), _jsxs("div", { className: "kpt-rt-grid__label", id: `${field.id}-grid-size`, "aria-live": "polite", children: [grid.r, " x ", grid.c] })] }) }), menuOpen
1067
1091
  ? pointPanel(_jsxs("div", { ref: (el) => {
1068
1092
  if (el && menuFromKeyboard.current) {
1069
1093
  menuFromKeyboard.current = false;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Czyści HTML i mówi, czy coś usunął. `changed: false` znaczy, że treść była czysta — wtedy
2
+ * Czyści HTML i mówi, czy coś usunął. `changed: false` znaczy, że treść była czysta - wtedy
3
3
  * wołający nie musi nadpisywać wartości modelu (serializacja DOM potrafi zmienić zapis, np.
4
4
  * cudzysłowy, bez zmiany treści, a taka różnica nie może brudzić formularza).
5
5
  */
@@ -13,7 +13,7 @@ export declare function sanitizeHtmlReport(html: string): {
13
13
  */
14
14
  export declare function sanitizeHtml(html: string): string;
15
15
  /**
16
- * Waliduje URL z narzędzia „link". Brak schematu → dopisujemy `https://`; `#` i `/` przechodzą
17
- * bez zmian; schemat spoza allowlisty → `null`, czyli link nie zostaje wstawiony.
16
+ * Waliduje URL z narzędzia "link". Brak schematu -> dopisujemy `https://`; `#` i `/` przechodzą
17
+ * bez zmian; schemat spoza allowlisty -> `null`, czyli link nie zostaje wstawiony.
18
18
  */
19
19
  export declare function sanitizeUrl(url: string): string | null;