@konce-pt/react 0.11.2 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (382) hide show
  1. package/README.md +128 -64
  2. package/dist/accordion/accordion.d.ts +14 -6
  3. package/dist/accordion/accordion.js +25 -17
  4. package/dist/accordion/llms.txt +22 -10
  5. package/dist/alert/alert.d.ts +2 -2
  6. package/dist/alert/alert.js +3 -2
  7. package/dist/alert/llms.txt +10 -9
  8. package/dist/app-shell/app-shell.d.ts +14 -14
  9. package/dist/app-shell/app-shell.js +22 -15
  10. package/dist/app-shell/llms.txt +16 -15
  11. package/dist/auth/auth-types.d.ts +6 -6
  12. package/dist/auth/auth.d.ts +2 -2
  13. package/dist/auth/auth.js +24 -15
  14. package/dist/auth/llms.txt +12 -11
  15. package/dist/autocomplete/autocomplete.d.ts +19 -6
  16. package/dist/autocomplete/autocomplete.js +32 -11
  17. package/dist/autocomplete/llms.txt +19 -5
  18. package/dist/avatar/avatar.d.ts +6 -6
  19. package/dist/avatar/avatar.js +6 -5
  20. package/dist/avatar/llms.txt +10 -10
  21. package/dist/avatar-group/avatar-group.d.ts +4 -4
  22. package/dist/avatar-group/avatar-group.js +4 -3
  23. package/dist/avatar-group/llms.txt +8 -8
  24. package/dist/backdrop/backdrop.d.ts +7 -26
  25. package/dist/backdrop/backdrop.js +32 -27
  26. package/dist/backdrop/llms.txt +27 -27
  27. package/dist/badge/badge.d.ts +3 -3
  28. package/dist/badge/badge.js +1 -1
  29. package/dist/badge/llms.txt +11 -11
  30. package/dist/bottom-sheet/bottom-sheet.d.ts +8 -3
  31. package/dist/bottom-sheet/bottom-sheet.js +5 -4
  32. package/dist/bottom-sheet/llms.txt +11 -8
  33. package/dist/breadcrumb/breadcrumb.d.ts +8 -2
  34. package/dist/breadcrumb/breadcrumb.js +11 -3
  35. package/dist/breadcrumb/llms.txt +10 -2
  36. package/dist/button/button.d.ts +3 -3
  37. package/dist/button/button.js +10 -9
  38. package/dist/button/llms.txt +27 -19
  39. package/dist/button-group/button-group.d.ts +18 -5
  40. package/dist/button-group/button-group.js +54 -6
  41. package/dist/button-group/llms.txt +27 -18
  42. package/dist/card/card.d.ts +8 -8
  43. package/dist/card/card.js +10 -9
  44. package/dist/card/llms.txt +20 -20
  45. package/dist/carousel/carousel.d.ts +6 -6
  46. package/dist/carousel/carousel.js +8 -5
  47. package/dist/carousel/llms.txt +10 -10
  48. package/dist/charts/chart-legend.d.ts +3 -3
  49. package/dist/charts/chart-legend.js +2 -1
  50. package/dist/charts/chart-tooltip.d.ts +3 -3
  51. package/dist/charts/chart-tooltip.js +1 -1
  52. package/dist/charts/chart.d.ts +4 -3
  53. package/dist/charts/chart.js +18 -10
  54. package/dist/charts/llms.txt +10 -7
  55. package/dist/charts-entry/public-api.js +2 -2
  56. package/dist/chat/chat.d.ts +17 -10
  57. package/dist/chat/chat.js +77 -23
  58. package/dist/chat/llms.txt +39 -15
  59. package/dist/chat-composer/chat-composer.d.ts +20 -7
  60. package/dist/chat-composer/chat-composer.js +29 -7
  61. package/dist/chat-composer/llms.txt +20 -11
  62. package/dist/chat-dock/chat-dock.d.ts +11 -4
  63. package/dist/chat-dock/chat-dock.js +10 -6
  64. package/dist/chat-dock/llms.txt +15 -14
  65. package/dist/chat-entry/public-api.js +1 -1
  66. package/dist/chat-message/chat-message.d.ts +12 -5
  67. package/dist/chat-message/chat-message.js +19 -8
  68. package/dist/chat-message/llms.txt +16 -15
  69. package/dist/chat-typing/chat-typing.d.ts +2 -2
  70. package/dist/chat-typing/chat-typing.js +4 -3
  71. package/dist/chat-typing/llms.txt +6 -6
  72. package/dist/checkbox/checkbox.d.ts +13 -6
  73. package/dist/checkbox/checkbox.js +31 -5
  74. package/dist/checkbox/llms.txt +26 -11
  75. package/dist/chip/chip.d.ts +4 -4
  76. package/dist/chip/chip.js +3 -2
  77. package/dist/chip/llms.txt +11 -11
  78. package/dist/chips-input/chips-input.d.ts +6 -4
  79. package/dist/chips-input/chips-input.js +47 -12
  80. package/dist/chips-input/llms.txt +18 -8
  81. package/dist/clock/clock.d.ts +11 -6
  82. package/dist/clock/clock.js +38 -8
  83. package/dist/clock/llms.txt +12 -8
  84. package/dist/color-picker/color-picker.d.ts +9 -5
  85. package/dist/color-picker/color-picker.js +37 -18
  86. package/dist/color-picker/llms.txt +8 -5
  87. package/dist/confirm/confirm-container.d.ts +3 -3
  88. package/dist/confirm/confirm-container.js +6 -5
  89. package/dist/confirm/confirm-store.d.ts +4 -4
  90. package/dist/confirm/confirm-store.js +11 -5
  91. package/dist/confirm/llms.txt +8 -7
  92. package/dist/context-menu/context-menu.d.ts +1 -1
  93. package/dist/context-menu/context-menu.js +37 -14
  94. package/dist/context-menu/llms.txt +10 -7
  95. package/dist/data-table/data-table-types.d.ts +5 -5
  96. package/dist/data-table/data-table.d.ts +37 -5
  97. package/dist/data-table/data-table.js +426 -119
  98. package/dist/data-table/export-csv.d.ts +3 -3
  99. package/dist/data-table/export-csv.js +8 -10
  100. package/dist/data-table/llms.txt +127 -79
  101. package/dist/data-view/data-view.d.ts +13 -5
  102. package/dist/data-view/data-view.js +68 -6
  103. package/dist/data-view/llms.txt +29 -10
  104. package/dist/date-range/date-range.d.ts +20 -8
  105. package/dist/date-range/date-range.js +86 -63
  106. package/dist/date-range/llms.txt +32 -18
  107. package/dist/datepicker/datepicker.d.ts +22 -8
  108. package/dist/datepicker/datepicker.js +103 -71
  109. package/dist/datepicker/llms.txt +36 -22
  110. package/dist/dialog/dialog.d.ts +4 -4
  111. package/dist/dialog/dialog.js +32 -11
  112. package/dist/dialog/llms.txt +13 -10
  113. package/dist/divider/divider.d.ts +7 -1
  114. package/dist/divider/divider.js +3 -2
  115. package/dist/divider/llms.txt +4 -3
  116. package/dist/drawer/drawer.d.ts +9 -4
  117. package/dist/drawer/drawer.js +5 -4
  118. package/dist/drawer/llms.txt +13 -10
  119. package/dist/empty/empty.d.ts +2 -2
  120. package/dist/empty/empty.js +1 -1
  121. package/dist/empty/llms.txt +9 -9
  122. package/dist/fab/fab.d.ts +1 -1
  123. package/dist/fab/fab.js +1 -1
  124. package/dist/fab/llms.txt +10 -6
  125. package/dist/fieldset/fieldset.d.ts +18 -4
  126. package/dist/fieldset/fieldset.js +12 -6
  127. package/dist/fieldset/llms.txt +26 -9
  128. package/dist/file-upload/file-upload.d.ts +8 -2
  129. package/dist/file-upload/file-upload.js +22 -11
  130. package/dist/file-upload/llms.txt +12 -9
  131. package/dist/form-field/form-field-context.d.ts +4 -4
  132. package/dist/form-field/form-field.d.ts +12 -4
  133. package/dist/form-field/form-field.js +27 -6
  134. package/dist/form-field/llms.txt +25 -11
  135. package/dist/galleria/galleria.d.ts +3 -3
  136. package/dist/galleria/galleria.js +9 -6
  137. package/dist/galleria/llms.txt +7 -7
  138. package/dist/grid/col.d.ts +4 -4
  139. package/dist/grid/col.js +2 -2
  140. package/dist/grid/flex.d.ts +3 -3
  141. package/dist/grid/flex.js +1 -1
  142. package/dist/grid/grid.d.ts +7 -7
  143. package/dist/grid/grid.js +3 -3
  144. package/dist/grid/layout.d.ts +2 -2
  145. package/dist/grid/layout.js +2 -2
  146. package/dist/grid/llms.txt +15 -15
  147. package/dist/grid-entry/public-api.js +1 -1
  148. package/dist/i18n/i18n-context.d.ts +6 -6
  149. package/dist/i18n/i18n-context.js +27 -5
  150. package/dist/icon/icon-registry.d.ts +3 -3
  151. package/dist/icon/icon-registry.js +3 -3
  152. package/dist/icon/icon.d.ts +4 -4
  153. package/dist/icon/icon.js +10 -7
  154. package/dist/icon/llms.txt +13 -13
  155. package/dist/icon-button/icon-button.d.ts +2 -2
  156. package/dist/icon-button/icon-button.js +1 -1
  157. package/dist/icon-button/llms.txt +9 -5
  158. package/dist/icons-entry/public-api.d.ts +2 -2
  159. package/dist/icons-entry/public-api.js +3 -3
  160. package/dist/image/image.js +5 -5
  161. package/dist/image/llms.txt +13 -13
  162. package/dist/input/input.d.ts +18 -5
  163. package/dist/input/input.js +22 -6
  164. package/dist/input/llms.txt +25 -11
  165. package/dist/input-mask/input-mask.d.ts +7 -2
  166. package/dist/input-mask/input-mask.js +17 -43
  167. package/dist/input-mask/llms.txt +8 -5
  168. package/dist/input-number/input-number.d.ts +11 -9
  169. package/dist/input-number/input-number.js +25 -21
  170. package/dist/input-number/llms.txt +17 -14
  171. package/dist/input-otp/input-otp.d.ts +18 -5
  172. package/dist/input-otp/input-otp.js +48 -24
  173. package/dist/input-otp/llms.txt +18 -7
  174. package/dist/internal/calendar-focus.d.ts +1 -1
  175. package/dist/internal/calendar-focus.js +3 -3
  176. package/dist/internal/cn.d.ts +1 -1
  177. package/dist/internal/mask.d.ts +14 -0
  178. package/dist/internal/mask.js +62 -0
  179. package/dist/internal/number-group.d.ts +22 -0
  180. package/dist/internal/number-group.js +53 -0
  181. package/dist/internal/option-key.d.ts +6 -0
  182. package/dist/internal/option-key.js +8 -0
  183. package/dist/internal/option-nav.d.ts +4 -4
  184. package/dist/internal/option-nav.js +5 -5
  185. package/dist/internal/overlay-container.js +1 -1
  186. package/dist/internal/overlay.d.ts +3 -3
  187. package/dist/internal/overlay.js +40 -10
  188. package/dist/internal/password-strength.d.ts +6 -0
  189. package/dist/internal/password-strength.js +74 -0
  190. package/dist/internal/roving.d.ts +2 -2
  191. package/dist/internal/roving.js +3 -3
  192. package/dist/internal/scroll-lock.d.ts +6 -0
  193. package/dist/internal/scroll-lock.js +35 -0
  194. package/dist/internal/slot-children.d.ts +13 -0
  195. package/dist/internal/slot-children.js +31 -0
  196. package/dist/internal/slot.d.ts +3 -3
  197. package/dist/internal/slot.js +2 -2
  198. package/dist/internal/step.d.ts +12 -0
  199. package/dist/internal/step.js +31 -0
  200. package/dist/internal/tween.d.ts +5 -5
  201. package/dist/internal/tween.js +7 -8
  202. package/dist/internal/types.d.ts +2 -2
  203. package/dist/internal/use-breakpoint.d.ts +2 -2
  204. package/dist/internal/use-breakpoint.js +4 -4
  205. package/dist/internal/use-controllable-state.d.ts +5 -0
  206. package/dist/internal/use-controllable-state.js +25 -4
  207. package/dist/internal/use-id.d.ts +2 -2
  208. package/dist/internal/use-id.js +2 -2
  209. package/dist/internal/use-modal-focus.d.ts +2 -1
  210. package/dist/internal/use-modal-focus.js +14 -3
  211. package/dist/knob/knob.d.ts +7 -4
  212. package/dist/knob/knob.js +27 -22
  213. package/dist/knob/llms.txt +18 -11
  214. package/dist/listbox/listbox.d.ts +8 -6
  215. package/dist/listbox/listbox.js +37 -17
  216. package/dist/listbox/llms.txt +12 -9
  217. package/dist/loader/llms.txt +56 -0
  218. package/dist/loader/loader.d.ts +49 -0
  219. package/dist/loader/loader.js +68 -0
  220. package/dist/map/llms.txt +30 -23
  221. package/dist/map/map-create-dialog.d.ts +4 -4
  222. package/dist/map/map-create-dialog.js +36 -28
  223. package/dist/map/map-hours-field.d.ts +4 -4
  224. package/dist/map/map-hours-field.js +8 -7
  225. package/dist/map/map-image-field.d.ts +2 -2
  226. package/dist/map/map-image-field.js +6 -5
  227. package/dist/map/map-legend.d.ts +4 -4
  228. package/dist/map/map-legend.js +3 -2
  229. package/dist/map/map-poi-card.d.ts +3 -3
  230. package/dist/map/map-poi-card.js +10 -9
  231. package/dist/map/map-search.d.ts +17 -6
  232. package/dist/map/map-search.js +70 -43
  233. package/dist/map/map.d.ts +25 -13
  234. package/dist/map/map.js +86 -57
  235. package/dist/map/ports.d.ts +9 -9
  236. package/dist/map/ports.js +6 -6
  237. package/dist/map-entry/public-api.js +2 -2
  238. package/dist/megamenu/llms.txt +18 -7
  239. package/dist/megamenu/megamenu.d.ts +12 -6
  240. package/dist/megamenu/megamenu.js +23 -14
  241. package/dist/mention/llms.txt +15 -15
  242. package/dist/mention/mention.d.ts +5 -5
  243. package/dist/mention/mention.js +2 -2
  244. package/dist/mention/mentions.d.ts +1 -1
  245. package/dist/mention/mentions.js +1 -1
  246. package/dist/menu/llms.txt +12 -12
  247. package/dist/menu/menu-item.d.ts +4 -4
  248. package/dist/menu/menu-keys.d.ts +2 -2
  249. package/dist/menu/menu-keys.js +5 -5
  250. package/dist/menu/menu-rows.js +1 -0
  251. package/dist/menu/menu.d.ts +2 -2
  252. package/dist/menu/menu.js +15 -8
  253. package/dist/menubar/llms.txt +19 -8
  254. package/dist/menubar/menubar.d.ts +11 -5
  255. package/dist/menubar/menubar.js +23 -11
  256. package/dist/meter-group/llms.txt +12 -12
  257. package/dist/meter-group/meter-group.d.ts +2 -2
  258. package/dist/meter-group/meter-group.js +2 -1
  259. package/dist/motion/motion-context.d.ts +22 -0
  260. package/dist/motion/motion-context.js +28 -0
  261. package/dist/order-list/llms.txt +24 -9
  262. package/dist/order-list/order-list.d.ts +12 -4
  263. package/dist/order-list/order-list.js +123 -25
  264. package/dist/paginator/llms.txt +8 -8
  265. package/dist/paginator/paginator.js +7 -4
  266. package/dist/panel/llms.txt +25 -13
  267. package/dist/panel/panel.d.ts +11 -3
  268. package/dist/panel/panel.js +12 -7
  269. package/dist/password/llms.txt +19 -7
  270. package/dist/password/password.d.ts +2 -2
  271. package/dist/password/password.js +27 -16
  272. package/dist/peek/llms.txt +11 -11
  273. package/dist/peek/peek.d.ts +6 -6
  274. package/dist/peek/peek.js +41 -19
  275. package/dist/pick-list/llms.txt +35 -12
  276. package/dist/pick-list/pick-list.d.ts +12 -4
  277. package/dist/pick-list/pick-list.js +128 -24
  278. package/dist/popover/llms.txt +13 -10
  279. package/dist/popover/popover.d.ts +6 -4
  280. package/dist/popover/popover.js +14 -7
  281. package/dist/progress/llms.txt +17 -17
  282. package/dist/progress/progress.d.ts +7 -7
  283. package/dist/progress/progress.js +7 -6
  284. package/dist/public-api.d.ts +16 -4
  285. package/dist/public-api.js +13 -7
  286. package/dist/radio-group/llms.txt +16 -6
  287. package/dist/radio-group/radio-group.d.ts +12 -3
  288. package/dist/radio-group/radio-group.js +23 -4
  289. package/dist/rating/llms.txt +20 -13
  290. package/dist/rating/rating-pointer.d.ts +16 -0
  291. package/dist/rating/rating-pointer.js +26 -0
  292. package/dist/rating/rating.d.ts +6 -6
  293. package/dist/rating/rating.js +77 -12
  294. package/dist/rich-text/lists.d.ts +2 -2
  295. package/dist/rich-text/lists.js +7 -7
  296. package/dist/rich-text/llms.txt +35 -12
  297. package/dist/rich-text/rich-text.d.ts +13 -14
  298. package/dist/rich-text/rich-text.js +311 -142
  299. package/dist/rich-text/sanitize.d.ts +11 -2
  300. package/dist/rich-text/sanitize.js +80 -32
  301. package/dist/rich-text/tables.d.ts +3 -3
  302. package/dist/rich-text/tables.js +3 -3
  303. package/dist/roadmap/llms.txt +23 -17
  304. package/dist/roadmap/roadmap.d.ts +6 -6
  305. package/dist/roadmap/roadmap.js +102 -25
  306. package/dist/roadmap-entry/public-api.js +2 -2
  307. package/dist/scroll-top/llms.txt +6 -6
  308. package/dist/scroll-top/scroll-top.d.ts +1 -1
  309. package/dist/scroll-top/scroll-top.js +2 -1
  310. package/dist/select/llms.txt +22 -12
  311. package/dist/select/select.d.ts +10 -8
  312. package/dist/select/select.js +87 -47
  313. package/dist/shared/types.d.ts +22 -1
  314. package/dist/shared/types.js +9 -1
  315. package/dist/sidenav/llms.txt +9 -9
  316. package/dist/sidenav/sidenav.d.ts +2 -3
  317. package/dist/sidenav/sidenav.js +5 -4
  318. package/dist/skeleton/llms.txt +3 -3
  319. package/dist/skeleton/skeleton.d.ts +1 -1
  320. package/dist/skeleton/skeleton.js +1 -1
  321. package/dist/slider/llms.txt +34 -12
  322. package/dist/slider/slider-drag.d.ts +26 -0
  323. package/dist/slider/slider-drag.js +79 -0
  324. package/dist/slider/slider.d.ts +8 -5
  325. package/dist/slider/slider.js +38 -8
  326. package/dist/speech/llms.txt +97 -0
  327. package/dist/speech/speech-button.d.ts +44 -0
  328. package/dist/speech/speech-button.js +86 -0
  329. package/dist/speech/speech-wave.d.ts +19 -0
  330. package/dist/speech/speech-wave.js +16 -0
  331. package/dist/speech/use-speech-recognition.d.ts +81 -0
  332. package/dist/speech/use-speech-recognition.js +179 -0
  333. package/dist/speed-dial/llms.txt +14 -8
  334. package/dist/speed-dial/speed-dial.d.ts +1 -1
  335. package/dist/speed-dial/speed-dial.js +3 -2
  336. package/dist/spinner/llms.txt +7 -7
  337. package/dist/spinner/spinner.d.ts +1 -1
  338. package/dist/spinner/spinner.js +2 -1
  339. package/dist/split-button/llms.txt +21 -15
  340. package/dist/split-button/split-button.d.ts +2 -2
  341. package/dist/split-button/split-button.js +13 -6
  342. package/dist/splitter/llms.txt +11 -11
  343. package/dist/splitter/splitter.d.ts +3 -3
  344. package/dist/splitter/splitter.js +25 -10
  345. package/dist/stepper/llms.txt +35 -13
  346. package/dist/stepper/stepper.d.ts +6 -6
  347. package/dist/stepper/stepper.js +60 -13
  348. package/dist/switch/llms.txt +15 -5
  349. package/dist/switch/switch.d.ts +10 -3
  350. package/dist/switch/switch.js +18 -4
  351. package/dist/switch-group/llms.txt +11 -8
  352. package/dist/switch-group/switch-group.d.ts +6 -4
  353. package/dist/switch-group/switch-group.js +9 -7
  354. package/dist/tabs/llms.txt +12 -9
  355. package/dist/tabs/tabs.d.ts +5 -5
  356. package/dist/tabs/tabs.js +36 -20
  357. package/dist/textarea/llms.txt +23 -12
  358. package/dist/textarea/textarea.d.ts +19 -6
  359. package/dist/textarea/textarea.js +22 -7
  360. package/dist/timeline/llms.txt +4 -4
  361. package/dist/timeline/timeline.d.ts +1 -1
  362. package/dist/timeline/timeline.js +1 -1
  363. package/dist/toast/llms.txt +50 -17
  364. package/dist/toast/toast-container.d.ts +9 -3
  365. package/dist/toast/toast-container.js +18 -9
  366. package/dist/toast/toast-store.d.ts +42 -2
  367. package/dist/toast/toast-store.js +36 -1
  368. package/dist/toolbar/llms.txt +7 -7
  369. package/dist/toolbar/toolbar.d.ts +3 -3
  370. package/dist/toolbar/toolbar.js +2 -2
  371. package/dist/tooltip/llms.txt +14 -12
  372. package/dist/tooltip/tooltip.d.ts +5 -4
  373. package/dist/tooltip/tooltip.js +100 -41
  374. package/dist/tree/llms.txt +25 -11
  375. package/dist/tree/tree-keys.d.ts +5 -5
  376. package/dist/tree/tree-keys.js +7 -7
  377. package/dist/tree/tree.d.ts +17 -5
  378. package/dist/tree/tree.js +62 -20
  379. package/dist/update/llms.txt +54 -0
  380. package/dist/update/update.d.ts +49 -0
  381. package/dist/update/update.js +82 -0
  382. package/package.json +18 -15
@@ -1,5 +1,6 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useCallback, useEffect, useRef, useState, } from 'react';
3
+ import { useCallback, useEffect, useEffectEvent, useRef, useState, } from 'react';
3
4
  import { createPortal } from 'react-dom';
4
5
  import { cn } from "../internal/cn.js";
5
6
  import { useControllableState } from "../internal/use-controllable-state.js";
@@ -8,17 +9,20 @@ import { useKptOverlayContainer } from "../internal/overlay-container.js";
8
9
  import { KptOverlay } from "../internal/overlay.js";
9
10
  import { useKptMessages } from "../i18n/i18n-context.js";
10
11
  import { KptIcon } from "../icon/icon.js";
11
- import { sanitizeHtml, sanitizeUrl } from "./sanitize.js";
12
+ import { KptSpeechButton } from "../speech/speech-button.js";
13
+ import { useKptSpeechRecognition } from "../speech/use-speech-recognition.js";
14
+ import { sanitizeHtml, sanitizeHtmlReport, sanitizeUrl } from "./sanitize.js";
12
15
  import { canIndentItem, canOutdentItem, selectedItems, shiftItems } from "./lists.js";
13
16
  import { CELL_SELECTED, deleteColumn, deleteRow, insertColumn, insertRow, mergeCells, regionBetween, splitCell, tableHtml, toggleHeaderColumn, toggleHeaderRow, } from "./tables.js";
14
17
  import { useKptFieldA11y } from "../form-field/form-field-context.js";
15
18
  import { nextRovingIndex } from "../internal/roving.js";
19
+ import { trapTab } from "../internal/calendar-focus.js";
16
20
  import { focusMenuEdge, handleMenuKeydown } from "../menu/menu-keys.js";
17
21
  const ICON = {
18
22
  copy: 'copy',
19
23
  paste: 'clipboard',
20
24
  pasteText: 'clipboard-text',
21
- heading: 'code', // nieużywane — „heading" renderuje select
25
+ heading: 'code', // nieużywane - "heading" renderuje select
22
26
  bold: 'bold',
23
27
  italic: 'italic',
24
28
  underline: 'underline',
@@ -37,8 +41,9 @@ const ICON = {
37
41
  table: 'table',
38
42
  clear: 'clear-formatting',
39
43
  html: 'code',
44
+ dictate: 'microphone', // nieużywane - "dictate" renderuje KptSpeechButton
40
45
  };
41
- /** Narzędzia zwykłe → komenda `execCommand` (zarazem nazwa dla `queryCommandState`). */
46
+ /** Narzędzia zwykłe -> komenda `execCommand` (zarazem nazwa dla `queryCommandState`). */
42
47
  const COMMAND = {
43
48
  bold: 'bold',
44
49
  italic: 'italic',
@@ -52,7 +57,7 @@ const COMMAND = {
52
57
  alignJustify: 'justifyFull',
53
58
  clear: 'removeFormat',
54
59
  };
55
- /** 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. */
56
61
  const SWATCHES = [
57
62
  { color: '#000000', name: 'black' },
58
63
  { color: '#434343', name: 'darkGray' },
@@ -84,26 +89,22 @@ const GRID_COLS = Array.from({ length: 8 }, (_, i) => i + 1);
84
89
  * (Ctrl+Z). Własne komendy wymagałyby własnej historii, czyli przepisania edytora.
85
90
  */
86
91
  function legacyExec(cmd, value) {
92
+ // eslint-disable-next-line @typescript-eslint/no-deprecated -- brak zamiennika, patrz JSDoc
87
93
  return document.execCommand(cmd, false, value); // NOSONAR: brak zamiennika, patrz JSDoc
88
94
  }
89
95
  function legacyState(cmd) {
90
96
  try {
97
+ // eslint-disable-next-line @typescript-eslint/no-deprecated -- brak zamiennika, patrz `legacyExec`
91
98
  return document.queryCommandState(cmd); // NOSONAR: brak zamiennika
92
99
  }
93
100
  catch {
94
101
  return false;
95
102
  }
96
103
  }
97
- /**
98
- * Lekki edytor tekstu sformatowanego na `contenteditable`, z konfigurowalnym paskiem sekcji
99
- * i edycją tabel. Wartością jest HTML.
100
- *
101
- * @example
102
- * <KptRichText value={html} onValueChange={setHtml} />
103
- * <KptRichText value={html} onValueChange={setHtml} toolbar={[['bold', 'italic'], ['link']]} />
104
- */
105
- export function KptRichText({ value, defaultValue = '', onValueChange, toolbar = DEFAULT_TOOLBAR, placeholder = '', maxListLevel = 6, id, ariaLabel = null, required = false, disabled = false, invalid = false, touched, onTouch, mentions = [], mentionLimit = KPT_MENTION_LIMIT, onMention, locale, dictionary, className, ...rest }) {
106
- /** `contenteditable` nie dostaje nazwy z `label[for]` — stąd `aria-labelledby` na etykietę wrappera. */
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ą. */
106
+ const disabled = disabledProp || readOnly;
107
+ /** `contenteditable` nie dostaje nazwy z `label[for]` - stąd `aria-labelledby` na etykietę wrappera. */
107
108
  const field = useKptFieldA11y(id, 'kpt-rich-text');
108
109
  const { t } = useKptMessages({ locale, dictionary });
109
110
  const container = useKptOverlayContainer();
@@ -111,7 +112,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
111
112
  const savedRange = useRef(null);
112
113
  const [current, setCurrent] = useControllableState({ value, defaultValue, onChange: onValueChange });
113
114
  const showInvalid = invalid && (touched ?? true);
114
- // Kotwice narzędzi z panelem — `KptOverlay` przypina panel do konkretnego przycisku.
115
+ // Kotwice narzędzi z panelem - `KptOverlay` przypina panel do konkretnego przycisku.
115
116
  const [styleAnchor, setStyleAnchor] = useState(null);
116
117
  const [foreAnchor, setForeAnchor] = useState(null);
117
118
  const [backAnchor, setBackAnchor] = useState(null);
@@ -120,25 +121,27 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
120
121
  /** Okienko adresu linku (zamiast `window.prompt()`) i wpisywany adres. */
121
122
  const [linkOpen, setLinkOpen] = useState(false);
122
123
  const [linkDraft, setLinkDraft] = useState('');
123
- /** 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ę. */
124
125
  const menuFromKeyboard = useRef(false);
125
126
  const [styleOpen, setStyleOpen] = useState(false);
126
- /** 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. */
127
128
  const [lastTool, setLastTool] = useState(null);
128
129
  const [colorOpen, setColorOpen] = useState(false);
129
130
  const [colorKind, setColorKind] = useState('foreColor');
130
131
  const [tableOpen, setTableOpen] = useState(false);
132
+ /** Aktywna opcja listy stylów (aria-activedescendant). */
133
+ const [styleActive, setStyleActive] = useState(0);
131
134
  const [grid, setGrid] = useState({ r: 0, c: 0 });
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');
@@ -148,7 +151,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
148
151
  const [currentBlock, setCurrentBlock] = useState('p');
149
152
  const [selCount, setSelCount] = useState(0);
150
153
  const ctxCell = useRef(null);
151
- const canSplitCtx = useRef(false);
154
+ const [canSplitCtx, setCanSplitCtx] = useState(false);
152
155
  const selCells = useRef(new Set());
153
156
  const selAnchor = useRef(null);
154
157
  const selecting = useRef(false);
@@ -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,21 +180,27 @@ 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
- * więc sanityzacja włącza się wyłącznie przy zapisie z aplikacji albo z trybu HTML.
185
+ * więc sanityzacja włącza się wyłącznie przy zapisie z aplikacji albo z trybu HTML. Gdy
186
+ * sanityzator coś usunął, oczyszczona treść wraca też do wartości - inaczej aplikacja dostawałaby
187
+ * z powrotem niebezpieczny HTML. Sama różnica zapisu (cudzysłowy) wartości nie rusza.
171
188
  */
172
189
  useEffect(() => {
173
190
  const el = editorRef.current;
174
- if (el && el.innerHTML !== current)
175
- el.innerHTML = sanitizeHtml(current);
176
- }, [current, sourceView]);
191
+ if (!el || el.innerHTML === current)
192
+ return;
193
+ const clean = sanitizeHtmlReport(current);
194
+ el.innerHTML = clean.html;
195
+ if (clean.changed)
196
+ setCurrent(clean.html);
197
+ }, [current, setCurrent, sourceView]);
177
198
  const inEditor = useCallback(() => {
178
199
  const el = editorRef.current;
179
200
  const selection = window.getSelection();
180
201
  return !!el && !!selection && selection.rangeCount > 0 && el.contains(selection.anchorNode);
181
202
  }, []);
182
- /** 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. */
183
204
  const blockTag = useCallback(() => {
184
205
  let node = window.getSelection()?.anchorNode;
185
206
  while (node && node !== editorRef.current) {
@@ -210,19 +231,19 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
210
231
  selCells.current = new Set();
211
232
  setSelCount(0);
212
233
  }, []);
213
- /** Zamknięcie podpowiedzi — czyści też zapamiętany zakres zapytania. */
234
+ /** Zamknięcie podpowiedzi - czyści też zapamiętany zakres zapytania. */
214
235
  const closeMention = useCallback(() => {
215
236
  mentionRange.current = null;
216
237
  setMentionMatches([]);
217
238
  setMentionOpen(false);
218
239
  }, []);
219
- /** 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. */
220
241
  const caretPoint = useCallback((node, offset) => {
221
242
  const range = document.createRange();
222
243
  range.setStart(node, Math.min(offset, node.length));
223
244
  range.collapse(true);
224
245
  const rect = range.getBoundingClientRect();
225
- // 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
226
247
  // krawędź samego edytora, żeby lista nie wylądowała w rogu ekranu.
227
248
  if (rect.width === 0 && rect.height === 0) {
228
249
  const fallback = editorRef.current?.getBoundingClientRect();
@@ -231,11 +252,11 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
231
252
  return { x: rect.left, y: rect.bottom };
232
253
  }, []);
233
254
  /**
234
- * Sprawdza, czy kursor stoi w zapytaniu `@…`, i ustawia listę podpowiedzi.
255
+ * Sprawdza, czy kursor stoi w zapytaniu `@...`, i ustawia listę podpowiedzi.
235
256
  *
236
257
  * Zapytanie liczymy z **węzła tekstowego**, nie z całego HTML-a: w treści siedzą tagi, a `@`
237
258
  * ma widzieć tylko to, co użytkownik naprawdę napisał w tym miejscu. Rozpoznanie zostawiamy
238
- * `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.
239
260
  */
240
261
  const syncMention = useCallback(() => {
241
262
  if (disabled || !mentions.length) {
@@ -265,10 +286,10 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
265
286
  setMentionOpen(true);
266
287
  }, [caretPoint, closeMention, disabled, mentionLimit, mentions]);
267
288
  /**
268
- * Wstawia wzmiankę w miejsce zapytania `@…`.
289
+ * Wstawia wzmiankę w miejsce zapytania `@...`.
269
290
  *
270
291
  * Wzmianka jest elementem, nie samym tekstem: `data-mention` niesie identyfikator, który
271
- * 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
272
293
  * kursor wchodziłby w środek nazwiska i psuł zapis. Spacja po wzmiance daje gdzie postawić
273
294
  * kursor: bez niej nie da się wyjść poza element, który nie jest edytowalny.
274
295
  */
@@ -303,7 +324,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
303
324
  useEffect(() => {
304
325
  const onSelectionChange = () => {
305
326
  refreshState();
306
- // 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
307
328
  // muszą zniknąć, inaczej Enter wstawiłby wzmiankę w zupełnie innym miejscu.
308
329
  syncMention();
309
330
  };
@@ -325,7 +346,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
325
346
  if (!(target instanceof Element))
326
347
  return;
327
348
  const focus = target.closest('td, th');
328
- if (!focus || focus.closest('table') !== selAnchor.current.closest('table'))
349
+ if (focus?.closest('table') !== selAnchor.current.closest('table'))
329
350
  return;
330
351
  if (focus === selAnchor.current && !selecting.current)
331
352
  return;
@@ -368,7 +389,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
368
389
  selection.addRange(savedRange.current);
369
390
  }
370
391
  };
371
- // ── Schowek ──
392
+ // -- Schowek --
372
393
  const copySelection = async () => {
373
394
  const selection = window.getSelection();
374
395
  if (!selection || selection.rangeCount === 0 || selection.isCollapsed)
@@ -405,7 +426,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
405
426
  pushValue();
406
427
  }
407
428
  catch {
408
- /* brak uprawnień albo API — ignorujemy */
429
+ /* brak uprawnień albo API - ignorujemy */
409
430
  }
410
431
  };
411
432
  const pastePlain = async () => {
@@ -416,10 +437,10 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
416
437
  pushValue();
417
438
  }
418
439
  catch {
419
- /* brak uprawnień albo API — ignorujemy */
440
+ /* brak uprawnień albo API - ignorujemy */
420
441
  }
421
442
  };
422
- // ── Narzędzia ──
443
+ // -- Narzędzia --
423
444
  const exec = (item) => {
424
445
  if (disabled)
425
446
  return;
@@ -464,7 +485,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
464
485
  };
465
486
  /**
466
487
  * Tab: wcięcie w liście, przeskok komórki w tabeli. Poza nimi i na krawędziach zostawiamy Tab
467
- * 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).
468
489
  */
469
490
  /** Klawiatura listy podpowiedzi; `true` = zdarzenie obsłużone i edytor go już nie zobaczy. */
470
491
  const onMentionKeyDown = (event) => {
@@ -525,7 +546,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
525
546
  const cells = [...table.querySelectorAll('td, th')];
526
547
  const next = cells[cells.indexOf(cell) + (event.shiftKey ? -1 : 1)];
527
548
  if (!next)
528
- return; // koniec tabeli — Tab zostaje natywny
549
+ return; // koniec tabeli - Tab zostaje natywny
529
550
  event.preventDefault();
530
551
  const range = document.createRange();
531
552
  range.selectNodeContents(next);
@@ -535,7 +556,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
535
556
  clearCellSelection();
536
557
  refreshState();
537
558
  };
538
- /** 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. */
539
560
  const onPaste = (event) => {
540
561
  if (disabled)
541
562
  return;
@@ -548,7 +569,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
548
569
  legacyExec('insertText', text);
549
570
  pushValue();
550
571
  };
551
- // ── Kolory ──
572
+ // -- Kolory --
552
573
  const activeCells = () => {
553
574
  if (selCells.current.size)
554
575
  return [...selCells.current];
@@ -570,7 +591,40 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
570
591
  legacyExec('backColor', color);
571
592
  pushValue();
572
593
  };
573
- // ── Tabele ──
594
+ // -- Tabele --
595
+ const pickStyle = (tag) => {
596
+ setStyleOpen(false);
597
+ focusEditor();
598
+ restoreSelection();
599
+ legacyExec('formatBlock', `<${tag}>`);
600
+ pushValue();
601
+ refreshState();
602
+ };
603
+ /** Siatka tabeli z klawiatury: strzałki zmieniają rozmiar (1-8), Enter wstawia, Escape wraca. */
604
+ const onGridKeyDown = (event) => {
605
+ const clamp = (n) => Math.min(GRID_ROWS.length, Math.max(1, n));
606
+ const r = grid.r || 1;
607
+ const c = grid.c || 1;
608
+ if (event.key === 'ArrowDown')
609
+ setGrid({ r: clamp(r + 1), c });
610
+ else if (event.key === 'ArrowUp')
611
+ setGrid({ r: clamp(r - 1), c });
612
+ else if (event.key === 'ArrowRight')
613
+ setGrid({ r, c: clamp(c + 1) });
614
+ else if (event.key === 'ArrowLeft')
615
+ setGrid({ r, c: clamp(c - 1) });
616
+ else if (event.key === 'Enter' || event.key === ' ')
617
+ insertTableAt(r, c);
618
+ else if (event.key === 'Escape') {
619
+ setTableOpen(false);
620
+ setGrid({ r: 0, c: 0 });
621
+ tableAnchor?.focus();
622
+ }
623
+ else
624
+ return;
625
+ event.preventDefault();
626
+ event.stopPropagation();
627
+ };
574
628
  const insertTableAt = (rows, cols) => {
575
629
  setTableOpen(false);
576
630
  setGrid({ r: 0, c: 0 });
@@ -582,7 +636,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
582
636
  const afterTableOp = () => {
583
637
  const fromMenu = !!document.activeElement?.closest('.kpt-rt-menu');
584
638
  setMenuOpen(false);
585
- // 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>.
586
640
  if (fromMenu)
587
641
  focusEditor();
588
642
  pushValue();
@@ -598,7 +652,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
598
652
  if (handleMenuKeydown(event.currentTarget, event.nativeEvent) === 'close')
599
653
  closeMenuToEditor();
600
654
  };
601
- /** 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. */
602
656
  const openMenuFromKeyboard = () => {
603
657
  const node = window.getSelection()?.anchorNode ?? null;
604
658
  const el = node instanceof Element ? node : (node?.parentElement ?? null);
@@ -608,13 +662,13 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
608
662
  const rect = cell.getBoundingClientRect();
609
663
  saveSelection();
610
664
  ctxCell.current = cell;
611
- canSplitCtx.current = (cell.colSpan || 1) > 1 || (cell.rowSpan || 1) > 1;
665
+ setCanSplitCtx((cell.colSpan || 1) > 1 || (cell.rowSpan || 1) > 1);
612
666
  setMenuPoint({ x: rect.left, y: rect.bottom });
613
667
  menuFromKeyboard.current = true;
614
668
  setMenuOpen(true);
615
669
  return true;
616
670
  };
617
- /** 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. */
618
672
  const openLink = () => {
619
673
  if (disabled)
620
674
  return;
@@ -650,7 +704,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
650
704
  event.preventDefault();
651
705
  saveSelection();
652
706
  ctxCell.current = cell;
653
- canSplitCtx.current = (cell.colSpan || 1) > 1 || (cell.rowSpan || 1) > 1;
707
+ setCanSplitCtx((cell.colSpan || 1) > 1 || (cell.rowSpan || 1) > 1);
654
708
  setMenuPoint({ x: event.clientX, y: event.clientY });
655
709
  setMenuOpen(true);
656
710
  };
@@ -714,7 +768,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
714
768
  }
715
769
  pushValue();
716
770
  };
717
- // ── Prezentacja narzędzi ──
771
+ // -- Prezentacja narzędzi --
718
772
  const styleOptions = [
719
773
  { tag: 'p', label: t('richText.styleNormal') },
720
774
  ...[1, 2, 3, 4, 5, 6].map((level) => ({
@@ -729,18 +783,67 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
729
783
  const cmd = COMMAND[item];
730
784
  return !!cmd && activeCmds.has(cmd);
731
785
  };
732
- /** `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. */
733
787
  const pressed = (item) => item === 'indent' || item === 'outdent' ? undefined : isActive(item);
734
788
  const enabled = (item) => {
735
789
  if (item === 'indent')
736
790
  return canIndent;
737
791
  if (item === 'outdent')
738
792
  return canOutdent;
793
+ if (item === 'dictate')
794
+ return mic.available && !sourceView;
739
795
  return true;
740
796
  };
741
- const palette = (onPick, customLabel) => (_jsxs("div", { className: "kpt-rich-text__palette", onMouseDown: (event) => event.preventDefault(), 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: () => onPick(swatch.color) }, swatch.color))) }), _jsxs("label", { className: "kpt-rich-text__picker", children: [_jsx("span", { children: customLabel }), _jsx("input", { type: "color", onInput: (event) => onPick(event.target.value) })] })] }));
742
797
  /**
743
- * Panel otwarty przy dolnej albo prawej krawędzi okna wychodziłby poza ekran — CDK robił to
798
+ * Panele narzędzi żyją w nakładce na końcu `<body>` - po zamontowaniu fokus wchodzi do środka,
799
+ * inaczej Tab musiałby przejść przez całą stronę, żeby do nich dojść (WCAG 2.1.1).
800
+ */
801
+ // Refy stabilne (`useCallback`): nowa funkcja przy każdym renderze byłaby wołana od nowa
802
+ // i przy każdej zmianie stanu edytora zabierałaby fokus z powrotem na początek panelu.
803
+ const focusPanelOnMount = useCallback((el) => {
804
+ if (el)
805
+ requestAnimationFrame(() => el.focus());
806
+ }, []);
807
+ const focusSwatchOnMount = useCallback((el) => {
808
+ if (el)
809
+ requestAnimationFrame(() => el.querySelector('.kpt-rich-text__swatch')?.focus());
810
+ }, []);
811
+ const focusGridOnMount = useCallback((el) => {
812
+ if (!el)
813
+ return;
814
+ setGrid((g) => (g.r === 0 ? { r: 1, c: 1 } : g));
815
+ requestAnimationFrame(() => el.focus());
816
+ }, []);
817
+ /** Paleta: strzałki po próbkach, Tab krąży w panelu, Escape zamyka i oddaje fokus narzędziu. */
818
+ const onPaletteKeyDown = (event) => {
819
+ const panel = event.currentTarget;
820
+ if (event.key === 'Escape') {
821
+ event.preventDefault();
822
+ event.stopPropagation();
823
+ setColorOpen(false);
824
+ if (colorKind === 'cellBg')
825
+ focusEditor();
826
+ else
827
+ (colorKind === 'foreColor' ? foreAnchor : backAnchor)?.focus();
828
+ return;
829
+ }
830
+ if (event.key === 'Tab') {
831
+ trapTab(panel, event.nativeEvent);
832
+ return;
833
+ }
834
+ const swatches = [...panel.querySelectorAll('.kpt-rich-text__swatch')];
835
+ const at = swatches.indexOf(document.activeElement);
836
+ if (at === -1)
837
+ return;
838
+ const next = nextRovingIndex(swatches.map(() => false), at, event.key);
839
+ if (next === null || next === at)
840
+ return;
841
+ event.preventDefault();
842
+ swatches[next].focus();
843
+ };
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) })] })] }));
845
+ /**
846
+ * Panel otwarty przy dolnej albo prawej krawędzi okna wychodziłby poza ekran - CDK robił to
744
847
  * w Angularze sam, tutaj przesuwamy go po zamontowaniu, gdy już znać jego rozmiar.
745
848
  */
746
849
  const clampToViewport = (el) => {
@@ -753,13 +856,13 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
753
856
  el.style.top = `${top}px`;
754
857
  };
755
858
  /**
756
- * 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
757
860
  * nie przechodzą przez `KptOverlay`; pozycję niosą współrzędne kliknięcia, tak jak
758
861
  * w `KptContextMenu`.
759
862
  */
760
863
  const pointPanel = (content, onClose) => {
761
864
  if (!container)
762
- return null; // render na serwerze — nie ma czego portalować
865
+ return null; // render na serwerze - nie ma czego portalować
763
866
  const layer = { zIndex: 'var(--kpt-z-overlay, 1100)' };
764
867
  return createPortal(_jsxs(_Fragment, { children: [_jsx("div", { style: { position: 'fixed', inset: 0, ...layer }, onClick: onClose, onContextMenu: (event) => {
765
868
  event.preventDefault();
@@ -767,37 +870,144 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
767
870
  } }), _jsx("div", { ref: clampToViewport, style: { position: 'fixed', left: menuPoint.x, top: menuPoint.y, ...layer }, children: content })] }), container);
768
871
  };
769
872
  /**
770
- * 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 -
771
874
  * pierwsze dostępne. Dzięki temu `Tab` z paska idzie od razu do edytora, a nie przez kilkanaście
772
875
  * przycisków.
773
876
  */
774
877
  const usableTools = sections.flat().filter((item) => !disabled && enabled(item));
775
878
  const toolStop = lastTool && usableTools.includes(lastTool) ? lastTool : (usableTools[0] ?? null);
776
879
  const rovingProps = (item) => ({ 'data-tool': item, tabIndex: item === toolStop ? 0 : -1 });
777
- /** 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. */
778
881
  const onToolbarKeyDown = (event) => {
779
882
  const bar = event.currentTarget;
780
- 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')));
781
886
  const current = buttons.indexOf(event.target);
782
887
  if (current < 0)
783
888
  return;
784
889
  const rtl = getComputedStyle(bar).direction === 'rtl';
785
890
  const key = rtl && event.key === 'ArrowLeft' ? 'ArrowRight' : rtl && event.key === 'ArrowRight' ? 'ArrowLeft' : event.key;
786
- const next = nextRovingIndex(buttons.map((button) => button.disabled), current, key);
891
+ const next = nextRovingIndex(buttons.map((button) => !button || button.disabled), current, key);
787
892
  if (next === null)
788
893
  return;
789
894
  event.preventDefault();
790
- setLastTool(buttons[next].dataset['tool']);
791
- buttons[next].focus();
895
+ setLastTool(tools[next].dataset['tool']);
896
+ buttons[next]?.focus();
792
897
  };
793
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));
794
- const menuItem = (icon, label, onClick, extra) => (_jsxs("button", { type: "button", role: "menuitem", tabIndex: -1, className: cn('kpt-rt-menu__item', extra), "aria-disabled": extra === 'is-disabled' || undefined, onClick: onClick, children: [_jsx(KptIcon, { name: icon }), " ", label] }));
795
- return (_jsxs("div", { className: cn('kpt-rich-text-host', className), ...rest, children: [_jsxs("div", { className: cn('kpt-rich-text', showInvalid && 'kpt-rich-text--invalid', disabled && 'kpt-rich-text--disabled'), children: [_jsx("div", { className: "kpt-rich-text__toolbar", role: "toolbar", "aria-label": t('richText.toolbar'), "aria-controls": field.id, onKeyDown: onToolbarKeyDown, children: sections.map((section, index) => (_jsxs("span", { style: { display: 'contents' }, children: [index > 0 ? _jsx("span", { className: "kpt-rich-text__divider", "aria-hidden": "true" }) : null, section.map((item) => {
899
+ /**
900
+ * Akcje menu tabeli po identyfikatorze. Handlery żyją tutaj, a nie jako domknięcia przekazywane
901
+ * do `menuItem()` - funkcja wołana w renderze nie dostaje wtedy niczego, co czyta refy.
902
+ */
903
+ const runTableAction = (action) => {
904
+ switch (action) {
905
+ case 'insertLeft': {
906
+ if (ctxCell.current)
907
+ insertColumn(ctxCell.current, 'left');
908
+ afterTableOp();
909
+ break;
910
+ }
911
+ case 'insertRight': {
912
+ if (ctxCell.current)
913
+ insertColumn(ctxCell.current, 'right');
914
+ afterTableOp();
915
+ break;
916
+ }
917
+ case 'headerColumn': {
918
+ if (ctxCell.current)
919
+ toggleHeaderColumn(ctxCell.current);
920
+ ctxCell.current = null; // komórki zostały podmienione
921
+ afterTableOp();
922
+ break;
923
+ }
924
+ case 'deleteColumn': {
925
+ if (ctxCell.current)
926
+ deleteColumn(ctxCell.current);
927
+ afterTableOp();
928
+ break;
929
+ }
930
+ case 'insertAbove': {
931
+ if (ctxCell.current)
932
+ insertRow(ctxCell.current, 'above');
933
+ afterTableOp();
934
+ break;
935
+ }
936
+ case 'insertBelow': {
937
+ if (ctxCell.current)
938
+ insertRow(ctxCell.current, 'below');
939
+ afterTableOp();
940
+ break;
941
+ }
942
+ case 'headerRow': {
943
+ if (ctxCell.current)
944
+ toggleHeaderRow(ctxCell.current);
945
+ ctxCell.current = null;
946
+ afterTableOp();
947
+ break;
948
+ }
949
+ case 'deleteRow': {
950
+ if (ctxCell.current)
951
+ deleteRow(ctxCell.current);
952
+ afterTableOp();
953
+ break;
954
+ }
955
+ case 'cellBackground': {
956
+ setMenuOpen(false);
957
+ setColorKind('cellBg');
958
+ setColorOpen(true);
959
+ break;
960
+ }
961
+ case 'alignLeft': {
962
+ for (const cell of activeCells())
963
+ cell.style.textAlign = 'left';
964
+ afterTableOp();
965
+ break;
966
+ }
967
+ case 'alignCenter': {
968
+ for (const cell of activeCells())
969
+ cell.style.textAlign = 'center';
970
+ afterTableOp();
971
+ break;
972
+ }
973
+ case 'alignRight': {
974
+ for (const cell of activeCells())
975
+ cell.style.textAlign = 'right';
976
+ afterTableOp();
977
+ break;
978
+ }
979
+ case 'mergeCells': {
980
+ mergeCells([...selCells.current]);
981
+ clearCellSelection();
982
+ afterTableOp();
983
+ break;
984
+ }
985
+ case 'splitCell': {
986
+ if (ctxCell.current)
987
+ splitCell(ctxCell.current);
988
+ afterTableOp();
989
+ break;
990
+ }
991
+ case 'border': {
992
+ setMenuOpen(false);
993
+ setBorderOpen(true);
994
+ break;
995
+ }
996
+ case 'deleteTable': {
997
+ ctxCell.current?.closest('table')?.remove();
998
+ afterTableOp();
999
+ break;
1000
+ }
1001
+ }
1002
+ };
1003
+ const menuItem = (icon, label, action, extra) => (_jsxs("button", { type: "button", role: "menuitem", tabIndex: -1, className: cn('kpt-rt-menu__item', extra), "aria-disabled": extra === 'is-disabled' || undefined, onClick: () => runTableAction(action), children: [_jsx(KptIcon, { name: icon }), " ", label] }));
1004
+ return (_jsxs("div", { className: cn('kpt-rich-text-host', className), ...rest, children: [_jsxs("div", { className: cn('kpt-rich-text', showInvalid && 'kpt-rich-text--invalid', disabledProp && 'kpt-rich-text--disabled'), children: [_jsx("div", { className: "kpt-rich-text__toolbar", role: "toolbar", "aria-label": t('richText.toolbar'), "aria-controls": field.id, onKeyDown: onToolbarKeyDown, children: sections.map((section, index) => (_jsxs("span", { style: { display: 'contents' }, children: [index > 0 ? _jsx("span", { className: "kpt-rich-text__divider", "aria-hidden": "true" }) : null, section.map((item) => {
796
1005
  if (item === 'heading') {
797
1006
  return (_jsxs("button", { ref: setStyleAnchor, ...rovingProps(item), type: "button", className: cn('kpt-rich-text__style', styleOpen && 'is-open'), disabled: disabled, "aria-label": t('richText.heading'), "aria-expanded": styleOpen, title: t('richText.heading'), onMouseDown: (event) => event.preventDefault(), onClick: () => {
798
1007
  if (disabled)
799
1008
  return;
800
1009
  saveSelection();
1010
+ setStyleActive(Math.max(0, styleOptions.findIndex((o) => o.tag === currentBlock)));
801
1011
  setStyleOpen(true);
802
1012
  }, children: [_jsx("span", { children: styleLabel }), _jsx(KptIcon, { name: "chevron-down", className: "kpt-rich-text__style-caret" })] }, item));
803
1013
  }
@@ -813,8 +1023,15 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
813
1023
  if (item === 'table') {
814
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));
815
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
+ }
816
1031
  return toolButton(item);
817
- })] }, 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, suppressContentEditableWarning: true, "data-placeholder": placeholder, "aria-invalid": showInvalid || undefined, role: "textbox", "aria-multiline": "true", id: field.id, "aria-required": required || undefined, "aria-label": ariaLabel ?? undefined, "aria-labelledby": ariaLabel ? undefined : field.labelledBy, "aria-describedby": field.describedBy, onInput: () => {
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,
1033
+ // Wyłączony edytor (contentEditable=false) wypada z kolejności Tab jak wyłączone pole.
1034
+ tabIndex: disabled ? -1 : 0, "aria-readonly": readOnly || undefined, suppressContentEditableWarning: true, "data-placeholder": placeholder, "aria-invalid": showInvalid || undefined, role: "textbox", "aria-multiline": "true", id: field.id, "aria-required": required || undefined, "aria-label": ariaLabel ?? undefined, "aria-labelledby": ariaLabel ? undefined : field.labelledBy, "aria-describedby": field.describedBy, onInput: () => {
818
1035
  pushValue();
819
1036
  syncMention();
820
1037
  }, onKeyDown: onKeyDown, onPaste: onPaste, onContextMenu: onContextMenu, onMouseDown: onEditorMouseDown, onMouseMove: onEditorMouseMove, onBlur: () => onTouch?.() }))] }), mentionOpen && container
@@ -830,7 +1047,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
830
1047
  insertMention(item);
831
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)
832
1049
  : null, _jsx(KptOverlay, { open: linkOpen, anchor: linkAnchor, onClose: () => closeLink(false), offset: 4, children: _jsxs("form", { className: "kpt-rt-link", role: "dialog",
833
- // 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://.
834
1051
  noValidate: true, "aria-label": t('richText.link'), onSubmit: (event) => {
835
1052
  event.preventDefault();
836
1053
  applyLink();
@@ -841,93 +1058,45 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
841
1058
  event.stopPropagation();
842
1059
  closeLink(true);
843
1060
  }, children: [_jsx("label", { className: "kpt-rt-link__label", htmlFor: `${field.id}-link`, children: t('richText.linkPrompt') }), _jsx("input", { ref: (el) => {
844
- // Panel jest niewidoczny do pierwszego pomiaru — fokus po klatce.
1061
+ // Panel jest niewidoczny do pierwszego pomiaru - fokus po klatce.
845
1062
  if (el)
846
1063
  requestAnimationFrame(() => el.focus());
847
- }, 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", { className: "kpt-rich-text__style-panel", role: "listbox", children: styleOptions.map((option) => (_jsx("li", { className: cn(`kpt-rich-text__style-opt kpt-rich-text__style-opt--${option.tag}`, currentBlock === option.tag && 'is-selected'), role: "option", "aria-selected": currentBlock === option.tag, onClick: () => {
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) => {
1065
+ const count = styleOptions.length;
1066
+ if (event.key === 'ArrowDown')
1067
+ setStyleActive((i) => (i + 1) % count);
1068
+ else if (event.key === 'ArrowUp')
1069
+ setStyleActive((i) => (i - 1 + count) % count);
1070
+ else if (event.key === 'Home')
1071
+ setStyleActive(0);
1072
+ else if (event.key === 'End')
1073
+ setStyleActive(count - 1);
1074
+ else if (event.key === 'Enter' || event.key === ' ')
1075
+ pickStyle(styleOptions[styleActive].tag);
1076
+ else if (event.key === 'Escape' || event.key === 'Tab') {
848
1077
  setStyleOpen(false);
849
- focusEditor();
850
- restoreSelection();
851
- legacyExec('formatBlock', `<${option.tag}>`);
852
- pushValue();
853
- refreshState();
854
- }, children: option.label }, option.tag))) }) }), _jsx(KptOverlay, { open: colorOpen && colorKind === 'foreColor', anchor: foreAnchor, onClose: () => setColorOpen(false), offset: 4, children: palette(applyColor, t('richText.paletteCustom')) }), _jsx(KptOverlay, { open: colorOpen && colorKind === 'backColor', anchor: backAnchor, onClose: () => setColorOpen(false), offset: 4, children: palette(applyColor, t('richText.paletteCustom')) }), _jsx(KptOverlay, { open: tableOpen, anchor: tableAnchor, onClose: () => {
1078
+ styleAnchor?.focus();
1079
+ }
1080
+ else
1081
+ return;
1082
+ event.preventDefault();
1083
+ event.stopPropagation();
1084
+ }, children: styleOptions.map((option, index) => (_jsx("li", { id: `${field.id}-style-${index}`, className: cn(`kpt-rich-text__style-opt kpt-rich-text__style-opt--${option.tag}`, currentBlock === option.tag && 'is-selected', styleActive === index && 'is-active'), role: "option", "aria-selected": currentBlock === option.tag, onClick: () => pickStyle(option.tag), children: option.label }, option.tag))) }) }), _jsx(KptOverlay, { open: colorOpen && colorKind === 'foreColor', anchor: foreAnchor, onClose: () => setColorOpen(false), offset: 4, children: paletteContent }), _jsx(KptOverlay, { open: colorOpen && colorKind === 'backColor', anchor: backAnchor, onClose: () => setColorOpen(false), offset: 4, children: paletteContent }), _jsx(KptOverlay, { open: tableOpen, anchor: tableAnchor, onClose: () => {
855
1085
  setTableOpen(false);
856
1086
  setGrid({ r: 0, c: 0 });
857
- }, offset: 4, children: _jsxs("div", { className: "kpt-rt-grid-pop", onMouseDown: (event) => event.preventDefault(), children: [_jsx("div", { className: "kpt-rt-grid", 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", children: [grid.r, " \u00D7 ", grid.c] })] }) }), menuOpen
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",
1088
+ // Jeden przystanek ze strzałkami (rozmiar tabeli) - grupa z własną obsługą klawiatury.
1089
+ // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex -- patrz wyżej
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
858
1091
  ? pointPanel(_jsxs("div", { ref: (el) => {
859
1092
  if (el && menuFromKeyboard.current) {
860
1093
  menuFromKeyboard.current = false;
861
1094
  requestAnimationFrame(() => focusMenuEdge(el, 'first'));
862
1095
  }
863
- }, className: "kpt-rt-menu", role: "menu", "aria-label": t('richText.table'), onContextMenu: (event) => event.preventDefault(), onKeyDown: onMenuKeyDown, children: [_jsx("div", { className: "kpt-rt-menu__group", "aria-hidden": "true", children: t('richText.menu.column') }), menuItem('plus', t('richText.menu.insertLeft'), () => {
864
- if (ctxCell.current)
865
- insertColumn(ctxCell.current, 'left');
866
- afterTableOp();
867
- }), menuItem('plus', t('richText.menu.insertRight'), () => {
868
- if (ctxCell.current)
869
- insertColumn(ctxCell.current, 'right');
870
- afterTableOp();
871
- }), menuItem('table', t('richText.menu.headerColumn'), () => {
872
- if (ctxCell.current)
873
- toggleHeaderColumn(ctxCell.current);
874
- ctxCell.current = null; // komórki zostały podmienione
875
- afterTableOp();
876
- }), menuItem('trash', t('richText.menu.deleteColumn'), () => {
877
- if (ctxCell.current)
878
- deleteColumn(ctxCell.current);
879
- afterTableOp();
880
- }), _jsx("div", { className: "kpt-rt-menu__sep", role: "separator" }), _jsx("div", { className: "kpt-rt-menu__group", "aria-hidden": "true", children: t('richText.menu.row') }), menuItem('plus', t('richText.menu.insertAbove'), () => {
881
- if (ctxCell.current)
882
- insertRow(ctxCell.current, 'above');
883
- afterTableOp();
884
- }), menuItem('plus', t('richText.menu.insertBelow'), () => {
885
- if (ctxCell.current)
886
- insertRow(ctxCell.current, 'below');
887
- afterTableOp();
888
- }), menuItem('table', t('richText.menu.headerRow'), () => {
889
- if (ctxCell.current)
890
- toggleHeaderRow(ctxCell.current);
891
- ctxCell.current = null;
892
- afterTableOp();
893
- }), menuItem('trash', t('richText.menu.deleteRow'), () => {
894
- if (ctxCell.current)
895
- deleteRow(ctxCell.current);
896
- afterTableOp();
897
- }), _jsx("div", { className: "kpt-rt-menu__sep", role: "separator" }), _jsx("div", { className: "kpt-rt-menu__group", "aria-hidden": "true", children: t('richText.menu.cell') }), menuItem('palette', t('richText.menu.cellBackground'), () => {
898
- setMenuOpen(false);
899
- setColorKind('cellBg');
900
- setColorOpen(true);
901
- }), menuItem('align-left', t('richText.menu.alignLeft'), () => {
902
- for (const cell of activeCells())
903
- cell.style.textAlign = 'left';
904
- afterTableOp();
905
- }), menuItem('align-center', t('richText.menu.alignCenter'), () => {
906
- for (const cell of activeCells())
907
- cell.style.textAlign = 'center';
908
- afterTableOp();
909
- }), menuItem('align-right', t('richText.menu.alignRight'), () => {
910
- for (const cell of activeCells())
911
- cell.style.textAlign = 'right';
912
- afterTableOp();
913
- }), _jsx("div", { className: "kpt-rt-menu__sep", role: "separator" }), menuItem('table', t('richText.menu.mergeCells'), () => {
914
- mergeCells([...selCells.current]);
915
- clearCellSelection();
916
- afterTableOp();
917
- }, selCount < 2 ? 'is-disabled' : undefined), menuItem('grip-vertical', t('richText.menu.splitCell'), () => {
918
- if (ctxCell.current)
919
- splitCell(ctxCell.current);
920
- afterTableOp();
921
- }, canSplitCtx.current ? undefined : 'is-disabled'), _jsx("div", { className: "kpt-rt-menu__sep", role: "separator" }), menuItem('table', t('richText.menu.border'), () => {
922
- setMenuOpen(false);
923
- setBorderOpen(true);
924
- }), _jsx("div", { className: "kpt-rt-menu__sep", role: "separator" }), menuItem('trash', t('richText.menu.deleteTable'), () => {
925
- ctxCell.current?.closest('table')?.remove();
926
- afterTableOp();
927
- }, 'kpt-rt-menu__item--danger')] }), () => setMenuOpen(false))
1096
+ }, className: "kpt-rt-menu", role: "menu", tabIndex: -1, "aria-label": t('richText.table'), onContextMenu: (event) => event.preventDefault(), onKeyDown: onMenuKeyDown, children: [_jsx("div", { className: "kpt-rt-menu__group", "aria-hidden": "true", children: t('richText.menu.column') }), menuItem('plus', t('richText.menu.insertLeft'), 'insertLeft'), menuItem('plus', t('richText.menu.insertRight'), 'insertRight'), menuItem('table', t('richText.menu.headerColumn'), 'headerColumn'), menuItem('trash', t('richText.menu.deleteColumn'), 'deleteColumn'), _jsx("div", { className: "kpt-rt-menu__sep", role: "separator" }), _jsx("div", { className: "kpt-rt-menu__group", "aria-hidden": "true", children: t('richText.menu.row') }), menuItem('plus', t('richText.menu.insertAbove'), 'insertAbove'), menuItem('plus', t('richText.menu.insertBelow'), 'insertBelow'), menuItem('table', t('richText.menu.headerRow'), 'headerRow'), menuItem('trash', t('richText.menu.deleteRow'), 'deleteRow'), _jsx("div", { className: "kpt-rt-menu__sep", role: "separator" }), _jsx("div", { className: "kpt-rt-menu__group", "aria-hidden": "true", children: t('richText.menu.cell') }), menuItem('palette', t('richText.menu.cellBackground'), 'cellBackground'), menuItem('align-left', t('richText.menu.alignLeft'), 'alignLeft'), menuItem('align-center', t('richText.menu.alignCenter'), 'alignCenter'), menuItem('align-right', t('richText.menu.alignRight'), 'alignRight'), _jsx("div", { className: "kpt-rt-menu__sep", role: "separator" }), menuItem('table', t('richText.menu.mergeCells'), 'mergeCells', selCount < 2 ? 'is-disabled' : undefined), menuItem('grip-vertical', t('richText.menu.splitCell'), 'splitCell', canSplitCtx ? undefined : 'is-disabled'), _jsx("div", { className: "kpt-rt-menu__sep", role: "separator" }), menuItem('table', t('richText.menu.border'), 'border'), _jsx("div", { className: "kpt-rt-menu__sep", role: "separator" }), menuItem('trash', t('richText.menu.deleteTable'), 'deleteTable', 'kpt-rt-menu__item--danger')] }), () => setMenuOpen(false))
928
1097
  : null, borderOpen
929
1098
  ? pointPanel(_jsxs("div", { className: "kpt-rt-border", onMouseDown: (event) => event.preventDefault(), children: [_jsxs("div", { className: "kpt-rt-border__seg", children: [_jsx("button", { type: "button", className: cn(borderTarget === 'table' && 'is-active'), onClick: () => setBorderTarget('table'), children: t('richText.border.table') }), _jsx("button", { type: "button", className: cn(borderTarget === 'cell' && 'is-active'), onClick: () => setBorderTarget('cell'), children: t('richText.border.cell') })] }), _jsxs("div", { className: "kpt-rt-border__widths", children: [_jsx("button", { type: "button", onClick: () => setBorderWidth('none'), children: t('richText.border.none') }), _jsx("button", { type: "button", onClick: () => setBorderWidth('thin'), children: t('richText.border.thin') }), _jsx("button", { type: "button", onClick: () => setBorderWidth('medium'), children: t('richText.border.medium') }), _jsx("button", { type: "button", onClick: () => setBorderWidth('thick'), children: t('richText.border.thick') })] }), _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: () => setBorderColor(swatch.color) }, swatch.color))) }), _jsxs("label", { className: "kpt-rich-text__picker", children: [_jsx("span", { children: t('richText.border.customColor') }), _jsx("input", { type: "color", onInput: (event) => setBorderColor(event.target.value) })] })] }), () => setBorderOpen(false))
930
1099
  : null, colorOpen && colorKind === 'cellBg'
931
- ? pointPanel(palette(applyColor, t('richText.paletteCustom')), () => setColorOpen(false))
1100
+ ? pointPanel(paletteContent, () => setColorOpen(false))
932
1101
  : null] }));
933
1102
  }