@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,10 +1,19 @@
1
+ /**
2
+ * Czyści HTML i mówi, czy coś usunął. `changed: false` znaczy, że treść była czysta - wtedy
3
+ * wołający nie musi nadpisywać wartości modelu (serializacja DOM potrafi zmienić zapis, np.
4
+ * cudzysłowy, bez zmiany treści, a taka różnica nie może brudzić formularza).
5
+ */
6
+ export declare function sanitizeHtmlReport(html: string): {
7
+ html: string;
8
+ changed: boolean;
9
+ };
1
10
  /**
2
11
  * Czyści HTML wchodzący do edytora. Dotyczy **każdego** zapisu do DOM edytora, nie tylko
3
12
  * wklejania: `value` z aplikacji też bywa niezaufane (baza, CMS, inny użytkownik).
4
13
  */
5
14
  export declare function sanitizeHtml(html: string): string;
6
15
  /**
7
- * Waliduje URL z narzędzia „link". Brak schematu → dopisujemy `https://`; `#` i `/` przechodzą
8
- * 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.
9
18
  */
10
19
  export declare function sanitizeUrl(url: string): string | null;
@@ -1,7 +1,6 @@
1
1
  /*
2
- * Sanityzacja treści edytora. Kod nie zna Reacta — operuje wyłącznie na DOM, więc gdyby port
3
- * Angulara kiedyś sięgnął po wspólny rdzeń, wystarczy przenieść ten plik do osobnej paczki
4
- * (jak `@konce-pt/datetime`).
2
+ * Sanityzacja treści edytora. Kod nie zna frameworka - operuje wyłącznie na DOM. Bliźniak
3
+ * w porcie Angulara: `rich-text/sanitize.ts` - zmiana w obu (docelowo wspólna paczka pure-TS).
5
4
  */
6
5
  /** Znaczniki dopuszczone w treści; pozostałe rozwijamy do dzieci, nie usuwając tekstu. */
7
6
  const ALLOWED_TAGS = new Set([
@@ -16,16 +15,30 @@ const ALLOWED_STYLES = new Set([
16
15
  /*
17
16
  * `data-mention` i `contenteditable` są tu dla wzmianek: identyfikator musi przeżyć zapis treści,
18
17
  * a `contenteditable="false"` trzyma wzmiankę jako jeden kawałek, w którego środku nie da się
19
- * pisać. Oba atrybuty są bierne — nie wykonują niczego i nie wskazują zasobów, więc wpuszczenie
20
- * ich nie otwiera nowej furtki.
18
+ * pisać. Oba atrybuty są bierne - nie wykonują niczego i nie wskazują zasobów, więc wpuszczenie
19
+ * ich nie otwiera nowej furtki. `class` przechodzi osobno, tylko z klasami samego edytora.
21
20
  */
22
21
  const ALLOWED_ATTRS = new Set([
23
- 'colspan', 'rowspan', 'span', 'width', 'class', 'target', 'rel', 'title',
22
+ 'colspan', 'rowspan', 'span', 'width', 'target', 'rel', 'title', 'color',
24
23
  'data-mention', 'contenteditable',
25
24
  ]);
26
- // Schematy dopuszczone w `href` narzędzia „link"; blocklista byłaby obchodzalna.
25
+ /**
26
+ * Klasy, które zostawia sam edytor (`kpt-rt-table`, `kpt-mention`). Obce klasy z wklejki - aplikacji,
27
+ * biblioteki (`kpt-dialog`, `kpt-visually-hidden`) czy Tailwinda - przejęłyby ich wygląd tam,
28
+ * gdzie aplikacja wyświetla treść.
29
+ */
30
+ const EDITOR_CLASS = /^kpt-(rt-[a-z0-9-]+|mention)$/;
31
+ // Schematy dopuszczone w `href` - ta sama allowlista dla narzędzia "link" i dla wklejonego HTML.
27
32
  const SAFE_URL_SCHEME = /^(https?:|mailto:|tel:)/i;
28
33
  const HAS_URL_SCHEME = /^[a-z][a-z0-9+.-]*:/i;
34
+ /** Znaki sterujące, które przeglądarka pomija przy parsowaniu URL-a (`jav\tascript:`). */
35
+ // eslint-disable-next-line no-control-regex -- celowo: właśnie te znaki trzeba wyciąć przed testem schematu
36
+ const URL_CONTROL = /[\x00-\x1f]/g;
37
+ /** Czy adres z treści może zostać: względny (bez schematu) albo ze schematu z allowlisty. */
38
+ function isSafeHref(value) {
39
+ const compact = value.replace(URL_CONTROL, '').trim();
40
+ return !HAS_URL_SCHEME.test(compact) || SAFE_URL_SCHEME.test(compact);
41
+ }
29
42
  /** Deklaracje `style` z allowlisty; `null`, gdy nie zostaje nic. */
30
43
  function sanitizeStyleAttr(value) {
31
44
  const kept = value
@@ -34,56 +47,91 @@ function sanitizeStyleAttr(value) {
34
47
  .filter((decl) => ALLOWED_STYLES.has(decl.split(':')[0]?.trim().toLowerCase() ?? ''));
35
48
  return kept.length ? kept.join('; ') : null;
36
49
  }
37
- function sanitizeAttribute(el, attr) {
50
+ function sanitizeAttribute(el, attr, report) {
38
51
  const name = attr.name.toLowerCase();
39
- if (name.startsWith('on')) {
52
+ const remove = () => {
40
53
  el.removeAttribute(attr.name);
41
- }
42
- else if (name === 'href' || name === 'src') {
43
- // Znaki kontrolne usuwamy przed testem schematu — przeglądarka i tak je pomija przy
44
- // parsowaniu URL-a, więc `jav\tascript:` obszedłby samo porównanie napisów.
45
- // eslint-disable-next-line no-control-regex
46
- if (/^\s*javascript:/i.test(attr.value.replace(/[\x00-\x1f]/g, '')))
47
- el.removeAttribute(attr.name);
54
+ report.changed = true;
55
+ };
56
+ if (name === 'href') {
57
+ if (!isSafeHref(attr.value))
58
+ remove();
48
59
  }
49
60
  else if (name === 'style') {
50
61
  const kept = sanitizeStyleAttr(attr.value);
51
- if (kept)
62
+ const declared = attr.value.split(';').filter((decl) => decl.trim()).length;
63
+ if (kept === null)
64
+ remove();
65
+ else if (kept.split('; ').length !== declared) {
66
+ // Zmiana tylko wtedy, gdy wypadła deklaracja - samo przeformatowanie zapisu się nie liczy.
52
67
  el.setAttribute('style', kept);
53
- else
54
- el.removeAttribute('style');
68
+ report.changed = true;
69
+ }
55
70
  }
56
- else if (!ALLOWED_ATTRS.has(name) && name !== 'color') {
57
- el.removeAttribute(attr.name);
71
+ else if (name === 'class') {
72
+ const tokens = attr.value.split(/\s+/).filter(Boolean);
73
+ const kept = tokens.filter((token) => EDITOR_CLASS.test(token));
74
+ if (kept.length === 0)
75
+ remove();
76
+ else if (kept.length !== tokens.length) {
77
+ el.setAttribute('class', kept.join(' '));
78
+ report.changed = true;
79
+ }
58
80
  }
81
+ else if (!ALLOWED_ATTRS.has(name)) {
82
+ // Także `on*` i `src` - żaden dopuszczony znacznik nie ładuje zasobów.
83
+ remove();
84
+ }
85
+ }
86
+ /** Link otwierany w nowej karcie dostaje `noopener noreferrer` - bez tego karta sięga do `opener`. */
87
+ function secureTarget(el, report) {
88
+ if (el.tagName !== 'A' || !el.hasAttribute('target'))
89
+ return;
90
+ const rel = new Set((el.getAttribute('rel') ?? '').split(/\s+/).filter(Boolean));
91
+ if (rel.has('noopener') && rel.has('noreferrer'))
92
+ return;
93
+ rel.add('noopener');
94
+ rel.add('noreferrer');
95
+ el.setAttribute('rel', [...rel].join(' '));
96
+ report.changed = true;
59
97
  }
60
98
  /** Tag spoza allowlisty rozwijamy do dzieci; atrybuty czyścimy po kolei. */
61
- function sanitizeElement(el) {
99
+ function sanitizeElement(el, report) {
62
100
  if (!ALLOWED_TAGS.has(el.tagName)) {
63
101
  el.replaceWith(...Array.from(el.childNodes));
102
+ report.changed = true;
64
103
  return;
65
104
  }
66
105
  for (const attr of Array.from(el.attributes))
67
- sanitizeAttribute(el, attr);
106
+ sanitizeAttribute(el, attr, report);
107
+ secureTarget(el, report);
68
108
  }
69
109
  /**
70
- * Czyści HTML wchodzący do edytora. Dotyczy **każdego** zapisu do DOM edytora, nie tylko
71
- * wklejania: `value` z aplikacji też bywa niezaufane (baza, CMS, inny użytkownik).
110
+ * Czyści HTML i mówi, czy coś usunął. `changed: false` znaczy, że treść była czysta - wtedy
111
+ * wołający nie musi nadpisywać wartości modelu (serializacja DOM potrafi zmienić zapis, np.
112
+ * cudzysłowy, bez zmiany treści, a taka różnica nie może brudzić formularza).
72
113
  */
73
- export function sanitizeHtml(html) {
114
+ export function sanitizeHtmlReport(html) {
74
115
  const template = document.createElement('template');
75
116
  template.innerHTML = html;
117
+ const report = { changed: false };
76
118
  for (const el of Array.from(template.content.querySelectorAll('*')))
77
- sanitizeElement(el);
78
- return template.innerHTML;
119
+ sanitizeElement(el, report);
120
+ return { html: template.innerHTML, changed: report.changed };
121
+ }
122
+ /**
123
+ * Czyści HTML wchodzący do edytora. Dotyczy **każdego** zapisu do DOM edytora, nie tylko
124
+ * wklejania: `value` z aplikacji też bywa niezaufane (baza, CMS, inny użytkownik).
125
+ */
126
+ export function sanitizeHtml(html) {
127
+ return sanitizeHtmlReport(html).html;
79
128
  }
80
129
  /**
81
- * Waliduje URL z narzędzia „link". Brak schematu → dopisujemy `https://`; `#` i `/` przechodzą
82
- * bez zmian; schemat spoza allowlisty → `null`, czyli link nie zostaje wstawiony.
130
+ * Waliduje URL z narzędzia "link". Brak schematu -> dopisujemy `https://`; `#` i `/` przechodzą
131
+ * bez zmian; schemat spoza allowlisty -> `null`, czyli link nie zostaje wstawiony.
83
132
  */
84
133
  export function sanitizeUrl(url) {
85
- // eslint-disable-next-line no-control-regex
86
- const trimmed = url.trim().replace(/[\x00-\x1f]/g, '');
134
+ const trimmed = url.trim().replace(URL_CONTROL, '');
87
135
  if (!trimmed)
88
136
  return null;
89
137
  if (trimmed.startsWith('#') || trimmed.startsWith('/'))
@@ -14,7 +14,7 @@ export interface Bounds {
14
14
  }
15
15
  /** Klasa znacząca komórki objęte zaznaczeniem prostokątnym. */
16
16
  export declare const CELL_SELECTED = "kpt-rt-cell-sel";
17
- /** Macierz komórek: pozycja w siatce, a nie w DOM — scalona komórka zajmuje kilka pól. */
17
+ /** Macierz komórek: pozycja w siatce, a nie w DOM - scalona komórka zajmuje kilka pól. */
18
18
  export declare function buildGrid(table: HTMLTableElement): {
19
19
  grid: HTMLTableCellElement[][];
20
20
  pos: GridPos;
@@ -23,7 +23,7 @@ export declare function buildGrid(table: HTMLTableElement): {
23
23
  export declare function cellsInRegion(grid: HTMLTableCellElement[][], b: Bounds): Set<HTMLTableCellElement>;
24
24
  /**
25
25
  * Prostokątne zaznaczenie od komórki-kotwicy do komórki pod kursorem. Granice rosną, aż region
26
- * w pełni obejmie scalone komórki na brzegach — inaczej scalanie działałoby na pół komórki.
26
+ * w pełni obejmie scalone komórki na brzegach - inaczej scalanie działałoby na pół komórki.
27
27
  */
28
28
  export declare function regionBetween(anchor: HTMLTableCellElement, focus: HTMLTableCellElement): Set<HTMLTableCellElement>;
29
29
  /** Równe szerokości kolumn po wstawieniu albo usunięciu kolumny. */
@@ -34,7 +34,7 @@ export declare function insertColumn(cell: HTMLTableCellElement, dir: 'left' | '
34
34
  export declare function deleteColumn(cell: HTMLTableCellElement): void;
35
35
  export declare function insertRow(cell: HTMLTableCellElement, dir: 'above' | 'below'): void;
36
36
  export declare function deleteRow(cell: HTMLTableCellElement): void;
37
- /** Podmienia znacznik komórki `td` ↔ `th`, zachowując atrybuty i treść. */
37
+ /** Podmienia znacznik komórki `td` <-> `th`, zachowując atrybuty i treść. */
38
38
  export declare function swapCellTag(cell: HTMLTableCellElement, tag: 'td' | 'th'): void;
39
39
  export declare function toggleHeaderRow(cell: HTMLTableCellElement): void;
40
40
  export declare function toggleHeaderColumn(cell: HTMLTableCellElement): void;
@@ -4,7 +4,7 @@
4
4
  */
5
5
  /** Klasa znacząca komórki objęte zaznaczeniem prostokątnym. */
6
6
  export const CELL_SELECTED = 'kpt-rt-cell-sel';
7
- /** Macierz komórek: pozycja w siatce, a nie w DOM — scalona komórka zajmuje kilka pól. */
7
+ /** Macierz komórek: pozycja w siatce, a nie w DOM - scalona komórka zajmuje kilka pól. */
8
8
  export function buildGrid(table) {
9
9
  const grid = [];
10
10
  const pos = new Map();
@@ -65,7 +65,7 @@ function growBounds(grid, pos, b) {
65
65
  }
66
66
  /**
67
67
  * Prostokątne zaznaczenie od komórki-kotwicy do komórki pod kursorem. Granice rosną, aż region
68
- * w pełni obejmie scalone komórki na brzegach — inaczej scalanie działałoby na pół komórki.
68
+ * w pełni obejmie scalone komórki na brzegach - inaczej scalanie działałoby na pół komórki.
69
69
  */
70
70
  export function regionBetween(anchor, focus) {
71
71
  const table = anchor.closest('table');
@@ -153,7 +153,7 @@ export function deleteRow(cell) {
153
153
  }
154
154
  tr.remove();
155
155
  }
156
- /** Podmienia znacznik komórki `td` ↔ `th`, zachowując atrybuty i treść. */
156
+ /** Podmienia znacznik komórki `td` <-> `th`, zachowując atrybuty i treść. */
157
157
  export function swapCellTag(cell, tag) {
158
158
  if (cell.tagName.toLowerCase() === tag)
159
159
  return;
@@ -9,40 +9,40 @@ A separate subpath, because the geometry, the styles and the data model are heav
9
9
  library core and not every app needs them.
10
10
 
11
11
  ## Props
12
- - `config`: `KptRoadmapConfig` (required) — the data and every option
13
- - `scrollHeight`: string | null — a fixed height, e.g. '420px'; enables the sticky header
14
- - `labelWidth`: string — the width of the label column (default '14rem')
15
- - `cellWidthPx`: number | null — the width of an axis column; without it the value depends on the zoom
16
- - `showToday`: boolean — the "today" marker (default true)
17
- - `virtual`: boolean — renders only the visible rows; worth it from roughly 100 rows up
18
- - `collapsedGroups` / `defaultCollapsedGroups` / `onCollapsedGroupsChange` — the ids of collapsed
12
+ - `config`: `KptRoadmapConfig` (required) - the data and every option
13
+ - `scrollHeight`: string | null - a fixed height, e.g. '420px'; enables the sticky header
14
+ - `labelWidth`: string - the width of the label column (default '14rem')
15
+ - `cellWidthPx`: number | null - the width of an axis column; without it the value depends on the zoom
16
+ - `showToday`: boolean - the "today" marker (default true)
17
+ - `virtual`: boolean - renders only the visible rows; worth it from roughly 100 rows up
18
+ - `collapsedGroups` / `defaultCollapsedGroups` / `onCollapsedGroupsChange` - the ids of collapsed
19
19
  groups, controlled or uncontrolled
20
- - `onItemClick(item)`, `onRangeChange(range)` — the click on a bar and the computed axis range
20
+ - `onItemClick(item)`, `onRangeChange(range)` - the click on a bar and the computed axis range
21
21
  - `className`, `ref` and every other `<div>` prop go to the host
22
- - `locale`, `dictionary` — the per-component i18n overrides
22
+ - `locale`, `dictionary` - the per-component i18n overrides
23
23
 
24
24
  ## The config is the API
25
25
  `{ items, groups?, links?, zoom?, range?, locale?, today?, timeline?, rows?, dependencies?,
26
- interaction? }` — the same shape the Angular port takes, because both read it from
26
+ interaction? }` - the same shape the Angular port takes, because both read it from
27
27
  `@konce-pt/roadmap`. An item is `{ id, label, start, end?, sublabel?, variant?, color?, progress?,
28
28
  groupId?, dependsOn?, milestone?, meta? }`; no `end` means a milestone diamond, and a bare string in
29
29
  `dependsOn` is an 'FS' dependency.
30
30
 
31
31
  ## The geometry lives in a package
32
32
  The calendar arithmetic, the timeline bands and cells, the row layout, lane packing and the
33
- dependency polylines come from `@konce-pt/roadmap` — pure functions with `node --test` coverage,
33
+ dependency polylines come from `@konce-pt/roadmap` - pure functions with `node --test` coverage,
34
34
  shared with the Angular port. The component only turns them into DOM and SVG.
35
35
 
36
36
  ## Collapsing has two sources
37
37
  `group.collapsed` in the data and the `collapsedGroups` prop are kept apart, so an app that controls
38
38
  the state keeps control while a group collapsed declaratively still works. Expanding removes the
39
- group from both — otherwise `group.collapsed` would immediately collapse it again. The declarative
39
+ group from both - otherwise `group.collapsed` would immediately collapse it again. The declarative
40
40
  set is recomputed during render whenever the `groups` array identity changes; an effect would do it
41
41
  one render too late, that is after the group flashed open.
42
42
 
43
43
  ## Measuring, not guessing
44
44
  The SVG dependency layer works in pixels, so the lane width and the scroll viewport are measured
45
- with a `ResizeObserver` in a layout effect. Before the first measurement no link is drawn — half a
45
+ with a `ResizeObserver` in a layout effect. Before the first measurement no link is drawn - half a
46
46
  frame without arrows beats arrows in the wrong place.
47
47
 
48
48
  ## Examples
@@ -63,13 +63,13 @@ frame without arrows beats arrows in the wrong place.
63
63
  different lengths; inside a column the position is interpolated linearly.
64
64
  - An item extending past the axis range is cut off on the side where it is clipped.
65
65
  - Tooltips use `KptTooltip`, mostly with `onlyWhenTruncated`: a tooltip shows what cannot be seen.
66
- Milestones and header cells carry one unconditionally — a diamond shows no text at all.
66
+ Milestones and header cells carry one unconditionally - a diamond shows no text at all.
67
67
  - Dependencies: 'FS' (the default), 'SS', 'FF', 'SF'; violated ones are drawn as a dashed line in
68
68
  the danger color.
69
69
  - Entries with an invalid date or a duplicate `id` are skipped, with a warning in a development build.
70
70
 
71
71
  ## i18n
72
- The `roadmap.*` keys: `empty`, `week`, `today`, `milestone`, `expandGroup`, `collapseGroup`.
72
+ The `roadmap.*` keys: `empty`, `week`, `today`, `milestone`, `expandGroup`, `collapseGroup`, `progress` (`{percent}`).
73
73
  Month and weekday names come from `Intl` per `config.locale` or the locale from `KptI18nProvider`.
74
74
 
75
75
  ## Tokens
@@ -78,5 +78,11 @@ bars per variant `--kpt-color-{primary,success,danger,warning,info,muted}`.
78
78
  Sizes: `--kpt-roadmap-label-w`, `--kpt-roadmap-cell-w` (both can be overridden from outside).
79
79
 
80
80
  ## Accessibility
81
- Group toggles are real buttons with `aria-expanded`; bars and milestones are focusable and answer
82
- Enter and Space like a click. The background grid and the dependency layer are `aria-hidden`.
81
+ Bars and milestones are `role="button"` with an accessible name built from the label, sublabel, date range
82
+ and progress (`Design, 3-14 Mar 2026, 40% complete`) - the end is exclusive, so the last occupied day is
83
+ announced. The whole bar grid is one Tab stop (roving tabindex): Left/Right move within a row, Up/Down go to
84
+ the bar with the nearest start in the neighbouring row, Home/End jump to the row's edge, Ctrl+Home/End to the
85
+ first/last bar; Enter and Space act like a click. With `virtual` on, moving to a row outside the window
86
+ scrolls it in first. Disabled items get `aria-disabled`. A group toggle is named by the group label and
87
+ carries `aria-expanded`; "Expand/Collapse group" is only its tooltip. The background grid and the dependency
88
+ layer are `aria-hidden`.
@@ -8,15 +8,15 @@ export interface KptRoadmapProps<TItem = unknown, TGroup = unknown> extends Omit
8
8
  scrollHeight?: string | null;
9
9
  /** Szerokość kolumny etykiet. */
10
10
  labelWidth?: string;
11
- /** Szerokość kolumny osi czasu; brak ⇒ wartość zależna od zoomu. */
11
+ /** Szerokość kolumny osi czasu; brak => wartość zależna od zoomu. */
12
12
  cellWidthPx?: number | null;
13
- /** Znacznik „dziś". */
13
+ /** Znacznik "dziś". */
14
14
  showToday?: boolean;
15
15
  /** Renderowanie tylko widocznych wierszy. Warto włączyć od ~100 wierszy. */
16
16
  virtual?: boolean;
17
- /** Zwinięte grupy — tryb kontrolowany. */
17
+ /** Zwinięte grupy - tryb kontrolowany. */
18
18
  collapsedGroups?: string[];
19
- /** Zwinięte grupy na starcie — tryb niekontrolowany. */
19
+ /** Zwinięte grupy na starcie - tryb niekontrolowany. */
20
20
  defaultCollapsedGroups?: string[];
21
21
  onCollapsedGroupsChange?: (ids: string[]) => void;
22
22
  onItemClick?: (item: KptRoadmapItem<TItem>) => void;
@@ -24,11 +24,11 @@ export interface KptRoadmapProps<TItem = unknown, TGroup = unknown> extends Omit
24
24
  onRangeChange?: (range: KptTimeRange) => void;
25
25
  }
26
26
  /**
27
- * Roadmapa — wiersze zadań na poziomej osi czasu (widok typu Gantt).
27
+ * Roadmapa - wiersze zadań na poziomej osi czasu (widok typu Gantt).
28
28
  *
29
29
  * Układ opiera się na DOM i `position: sticky` (kolumna etykiet oraz nagłówek),
30
30
  * paski są pozycjonowane procentowo w torze, a zależności rysuje warstwa SVG
31
- * rozpięta nad torami. Cała geometria pochodzi z `@konce-pt/roadmap` — tej samej
31
+ * rozpięta nad torami. Cała geometria pochodzi z `@konce-pt/roadmap` - tej samej
32
32
  * paczki, z której korzysta port Angulara.
33
33
  *
34
34
  * @example
@@ -1,6 +1,7 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
3
  import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react';
3
- import { buildRows, buildTimeline, linkGeometry, resolveLinks, resolveRange, sliceRows, } from '@konce-pt/roadmap';
4
+ import { buildRows, buildTimeline, linkGeometry, resolveLinks, resolveRange, roadmapItemDates, roadmapNavTarget, sliceRows, } from '@konce-pt/roadmap';
4
5
  import { cn } from "../internal/cn.js";
5
6
  import { isDevBuild } from "../internal/dev.js";
6
7
  import { useControllableState } from "../internal/use-controllable-state.js";
@@ -15,16 +16,16 @@ const CELL_WIDTH = {
15
16
  day: 44,
16
17
  hour: 46,
17
18
  };
18
- /** Wysokość paska w trybie „jeden item na wiersz" (px). */
19
+ /** Wysokość paska w trybie "jeden item na wiersz" (px). */
19
20
  const BAR_HEIGHT = 22;
20
- /** Odstęp między pasami w trybie „pack" (px). */
21
+ /** Odstęp między pasami w trybie "pack" (px). */
21
22
  const LANE_GAP = 4;
22
23
  /**
23
- * Roadmapa — wiersze zadań na poziomej osi czasu (widok typu Gantt).
24
+ * Roadmapa - wiersze zadań na poziomej osi czasu (widok typu Gantt).
24
25
  *
25
26
  * Układ opiera się na DOM i `position: sticky` (kolumna etykiet oraz nagłówek),
26
27
  * paski są pozycjonowane procentowo w torze, a zależności rysuje warstwa SVG
27
- * rozpięta nad torami. Cała geometria pochodzi z `@konce-pt/roadmap` — tej samej
28
+ * rozpięta nad torami. Cała geometria pochodzi z `@konce-pt/roadmap` - tej samej
28
29
  * paczki, z której korzysta port Angulara.
29
30
  *
30
31
  * @example
@@ -40,22 +41,22 @@ export function KptRoadmap({ config, scrollHeight = null, labelWidth = '14rem',
40
41
  /**
41
42
  * Grupy zwinięte na starcie przez `group.collapsed`. Trzymamy je osobno od stanu aplikacji,
42
43
  * żeby sterowanie z zewnątrz pozostało w jej rękach, a zwinięcie zadeklarowane w danych
43
- * nadal działało. Wyliczamy w trakcie renderu i resetujemy, gdy zmieni się lista grup —
44
+ * nadal działało. Wyliczamy w trakcie renderu i resetujemy, gdy zmieni się lista grup -
44
45
  * efekt zrobiłby to samo o jeden render za późno, czyli po mignięciu rozwiniętej grupy.
45
46
  */
46
47
  const [fromConfig, setFromConfig] = useState(() => declaredCollapsed(config.groups));
47
- const groupsRef = useRef(config.groups);
48
- if (groupsRef.current !== config.groups) {
49
- groupsRef.current = config.groups;
48
+ const [prevGroups, setPrevGroups] = useState(config.groups);
49
+ if (prevGroups !== config.groups) {
50
+ setPrevGroups(config.groups);
50
51
  setFromConfig(declaredCollapsed(config.groups));
51
52
  }
52
- /** Suma obu źródeł — stan faktycznie użyty do budowy wierszy. */
53
+ /** Suma obu źródeł - stan faktycznie użyty do budowy wierszy. */
53
54
  const collapsedIds = useMemo(() => new Set([...fromConfig, ...collapsed]), [fromConfig, collapsed]);
54
55
  const scrollRef = useRef(null);
55
56
  const laneRef = useRef(null);
56
57
  const [scrollTop, setScrollTop] = useState(0);
57
58
  const [viewportHeight, setViewportHeight] = useState(0);
58
- /** Szerokość toru w px — potrzebna warstwie SVG, która pracuje w pikselach. */
59
+ /** Szerokość toru w px - potrzebna warstwie SVG, która pracuje w pikselach. */
59
60
  const [laneWidth, setLaneWidth] = useState(0);
60
61
  const zoom = config.zoom ?? 'week';
61
62
  const cellWidth = cellWidthPx ?? CELL_WIDTH[zoom];
@@ -68,13 +69,35 @@ export function KptRoadmap({ config, scrollHeight = null, labelWidth = '14rem',
68
69
  weekPrefix: config.timeline?.weekPrefix ?? t('roadmap.week'),
69
70
  }), [range, zoom, config, locale, t]);
70
71
  const layout = useMemo(() => buildRows(config.items, config.groups, collapsedIds, config.rows), [config.items, config.groups, config.rows, collapsedIds]);
71
- /** Wiersze faktycznie renderowane — całość albo okno wirtualizacji. */
72
+ const itemLocale = config.locale ?? config.timeline?.locale ?? locale;
73
+ /**
74
+ * Paski widoczne na osi, wiersz po wierszu - mapa dla nawigacji strzałkami. Liczona ze wszystkich
75
+ * wierszy, nie z okna wirtualizacji: strzałka w dół z ostatniego wyrenderowanego wiersza ma dokąd iść.
76
+ */
77
+ const navGrid = useMemo(() => layout.rows.map((row) => row.kind === 'group'
78
+ ? []
79
+ : row.items
80
+ .filter((item) => timeline.span(item.start, item.end).visible)
81
+ .map((item) => ({ id: item.id, start: item.start }))
82
+ .sort((a, b) => a.start - b.start)), [layout, timeline]);
83
+ /** Pasek, na którym ostatnio był fokus. */
84
+ const [activeId, setActiveId] = useState(null);
85
+ /**
86
+ * Jedyny przystanek Tab w siatce pasków (roving tabindex) - ostatnio aktywny pasek, a gdy zniknął
87
+ * (zwinięta grupa, inne dane), pierwszy. Bez tego roadmapa ze stu zadaniami to sto przystanków Tab.
88
+ */
89
+ const tabStopId = useMemo(() => {
90
+ if (activeId && navGrid.some((row) => row.some((item) => item.id === activeId)))
91
+ return activeId;
92
+ return navGrid.find((row) => row.length > 0)?.[0].id ?? null;
93
+ }, [navGrid, activeId]);
94
+ /** Wiersze faktycznie renderowane - całość albo okno wirtualizacji. */
72
95
  const visibleRows = useMemo(() => {
73
96
  if (!virtual || viewportHeight <= 0)
74
97
  return layout.rows;
75
98
  return sliceRows(layout, scrollTop, viewportHeight).rows;
76
99
  }, [virtual, viewportHeight, layout, scrollTop]);
77
- // Zakres jest wielkością wyliczaną — informujemy o nim po każdym przeliczeniu.
100
+ // Zakres jest wielkością wyliczaną - informujemy o nim po każdym przeliczeniu.
78
101
  useEffect(() => {
79
102
  onRangeChange?.(range);
80
103
  }, [range, onRangeChange]);
@@ -103,13 +126,28 @@ export function KptRoadmap({ config, scrollHeight = null, labelWidth = '14rem',
103
126
  console.warn('[kpt-roadmap] Pominięto elementy:', invalid.map((entry) => `${entry.id} (${entry.reason})`).join(', '));
104
127
  }
105
128
  }, [layout]);
106
- /** Wysokość paska wraz z odstępem — wspólna dla widoku i geometrii zależności. */
129
+ /** Wysokość paska wraz z odstępem - wspólna dla widoku i geometrii zależności. */
107
130
  const laneStep = barHeight + LANE_GAP;
108
131
  /** Offset pionowy paska wewnątrz wiersza (px). */
109
132
  const barTop = useCallback((row, lane) => {
110
133
  const total = row.laneCount * barHeight + (row.laneCount - 1) * LANE_GAP;
111
134
  return Math.max(0, (row.height - total) / 2) + lane * laneStep;
112
135
  }, [barHeight, laneStep]);
136
+ /** Nazwa dostępna paska: "Etykieta, 3-14 mar 2026, ukończono 40%". */
137
+ const itemAriaLabel = useCallback((item) => {
138
+ const source = item.source;
139
+ const dates = roadmapItemDates(item.start, item.end, itemLocale, {
140
+ isMilestone: item.isMilestone,
141
+ withTime: zoom === 'hour',
142
+ });
143
+ const parts = [item.isMilestone ? `${t('roadmap.milestone')}: ${source.label}` : source.label, dates];
144
+ if (source.sublabel)
145
+ parts.splice(1, 0, source.sublabel);
146
+ if (source.progress != null && !item.isMilestone) {
147
+ parts.push(t('roadmap.progress', { percent: Math.round(source.progress * 100) }));
148
+ }
149
+ return parts.join(', ');
150
+ }, [itemLocale, zoom, t]);
113
151
  /** Paski danego wiersza gotowe do renderu. */
114
152
  const bars = useCallback((row) => {
115
153
  const result = [];
@@ -125,17 +163,18 @@ export function KptRoadmap({ config, scrollHeight = null, labelWidth = '14rem',
125
163
  top: barTop(row, item.lane),
126
164
  height: barHeight,
127
165
  label: source.label,
128
- title: source.sublabel ? `${source.label} — ${source.sublabel}` : source.label,
166
+ title: source.sublabel ? `${source.label} - ${source.sublabel}` : source.label,
129
167
  variant: source.variant ?? 'primary',
130
168
  color: source.color,
131
169
  progress: source.progress ?? null,
132
170
  isMilestone: item.isMilestone,
133
171
  clippedStart: geom.clippedStart,
134
172
  clippedEnd: geom.clippedEnd,
173
+ ariaLabel: itemAriaLabel(item),
135
174
  });
136
175
  }
137
176
  return result;
138
- }, [timeline, barTop, barHeight]);
177
+ }, [timeline, barTop, barHeight, itemAriaLabel]);
139
178
  /** Pasek zbiorczy wiersza grupy. */
140
179
  const summary = useCallback((row) => {
141
180
  const span = row.groupState?.span;
@@ -208,25 +247,63 @@ export function KptRoadmap({ config, scrollHeight = null, labelWidth = '14rem',
208
247
  return;
209
248
  onItemClick?.(item.source);
210
249
  };
250
+ /** Przy wirtualizacji przewija tak, żeby wiersz z paskiem wszedł do okna i się wyrenderował. */
251
+ const revealRow = (scroll, id) => {
252
+ if (!virtual)
253
+ return;
254
+ const row = layout.rows.find((candidate) => candidate.items.some((item) => item.id === id));
255
+ if (!row)
256
+ return;
257
+ const header = scroll.querySelector('.kpt-roadmap__head')?.offsetHeight ?? 0;
258
+ const viewport = scroll.clientHeight - header;
259
+ let top = scroll.scrollTop;
260
+ if (row.top < top)
261
+ top = row.top;
262
+ else if (row.top + row.height > top + viewport)
263
+ top = row.top + row.height - viewport;
264
+ if (top === scroll.scrollTop)
265
+ return;
266
+ scroll.scrollTop = top;
267
+ setScrollTop(top);
268
+ };
269
+ /**
270
+ * Klawiatura na pasku: Enter/Spacja działają jak kliknięcie, strzałki przenoszą fokus po siatce
271
+ * (w poziomie po wierszu, w pionie między wierszami), Home/End na skraj wiersza, z Ctrl - roadmapy.
272
+ */
211
273
  const handleItemKeydown = (event, item) => {
212
- if (event.key !== 'Enter' && event.key !== ' ')
274
+ if (event.key === 'Enter' || event.key === ' ') {
275
+ event.preventDefault();
276
+ handleItemClick(item);
277
+ return;
278
+ }
279
+ const target = roadmapNavTarget(navGrid, item.id, event.key, event.ctrlKey || event.metaKey);
280
+ if (!target)
213
281
  return;
214
282
  event.preventDefault();
215
- handleItemClick(item);
283
+ setActiveId(target);
284
+ const scroll = event.currentTarget.closest('.kpt-roadmap__scroll');
285
+ if (!scroll)
286
+ return;
287
+ revealRow(scroll, target);
288
+ // Pasek z innego wiersza może jeszcze nie istnieć w DOM (wirtualizacja). Aktualizacja ze zdarzenia
289
+ // klawiatury jest zatwierdzana synchronicznie, więc w następnej klatce pasek już jest.
290
+ requestAnimationFrame(() => {
291
+ scroll.querySelector(`[data-kpt-item="${CSS.escape(target)}"]`)?.focus();
292
+ });
216
293
  };
217
294
  /**
218
295
  * Treść dymka dla kolumny etykiet. Pusta tam, gdzie dymek nie miałby czego
219
- * uzupełnić — komponent pomija wtedy podpowiedź.
296
+ * uzupełnić - komponent pomija wtedy podpowiedź.
220
297
  */
221
298
  const labelTooltip = (row) => {
222
299
  // Wiersz grupy ma własny dymek, osadzony głębiej (na stosie etykiet).
223
300
  if (row.kind === 'group')
224
301
  return '';
225
- // Tryb 'pack' mieści wiele zadań w jednym wierszu — nie ma jednej etykiety.
302
+ // Tryb 'pack' mieści wiele zadań w jednym wierszu - nie ma jednej etykiety.
226
303
  if (row.items.length !== 1)
227
304
  return '';
228
305
  const source = row.items[0].source;
229
- return source.sublabel ? `${source.label} — ${source.sublabel}` : source.label;
306
+ return source.sublabel ? `${source.label} - ${source.sublabel}` : source.label;
230
307
  };
231
308
  const onScroll = (event) => {
232
309
  setScrollTop(event.currentTarget.scrollTop);
@@ -242,25 +319,25 @@ export function KptRoadmap({ config, scrollHeight = null, labelWidth = '14rem',
242
319
  const group = row.group;
243
320
  const first = row.items[0];
244
321
  const sum = row.kind === 'group' ? summary(row) : null;
245
- return (_jsxs("div", { className: cn('kpt-roadmap__row', row.kind === 'group' && 'kpt-roadmap__row--group'), style: { insetBlockStart: `${row.top}px`, blockSize: `${row.height}px` }, children: [_jsx(KptTooltip, { text: labelTooltip(row), onlyWhenTruncated: true, children: _jsx("div", { className: cn('kpt-roadmap__label', row.kind === 'group' && 'kpt-roadmap__label--group'), style: { paddingInlineStart: `${0.75 + row.depth * 1}rem` }, children: group ? (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "kpt-roadmap__toggle", "aria-expanded": !isCollapsed(group), "aria-label": isCollapsed(group) ? t('roadmap.expandGroup') : t('roadmap.collapseGroup'), onClick: () => toggleGroup(group), children: _jsx("span", { className: cn('kpt-roadmap__toggle-icon', !isCollapsed(group) && 'kpt-roadmap__toggle-icon--open') }) }), _jsx(KptTooltip, { text: group.sublabel ? `${group.label} — ${group.sublabel}` : group.label, onlyWhenTruncated: true, children: _jsxs("span", { className: "kpt-roadmap__label-stack", children: [_jsx("span", { className: "kpt-roadmap__label-text kpt-roadmap__label-text--group", children: group.label }), group.sublabel && _jsx("span", { className: "kpt-roadmap__label-sub", children: group.sublabel })] }) }), row.groupState?.itemCount ? (_jsx("span", { className: "kpt-roadmap__label-count", children: row.groupState.itemCount })) : null] })) : (first && (_jsxs(_Fragment, { children: [_jsx("span", { className: "kpt-roadmap__label-text", children: first.source.label }), row.items.length === 1 && first.source.sublabel && (_jsx("span", { className: "kpt-roadmap__label-sub", children: first.source.sublabel }))] }))) }) }), _jsx("div", { className: "kpt-roadmap__lane", children: row.kind === 'group'
322
+ return (_jsxs("div", { className: cn('kpt-roadmap__row', row.kind === 'group' && 'kpt-roadmap__row--group'), style: { insetBlockStart: `${row.top}px`, blockSize: `${row.height}px` }, children: [_jsx(KptTooltip, { text: labelTooltip(row), onlyWhenTruncated: true, children: _jsx("div", { className: cn('kpt-roadmap__label', row.kind === 'group' && 'kpt-roadmap__label--group'), style: { paddingInlineStart: `${0.75 + row.depth * 1}rem` }, children: group ? (_jsxs(_Fragment, { children: [_jsx(KptTooltip, { text: isCollapsed(group) ? t('roadmap.expandGroup') : t('roadmap.collapseGroup'), children: _jsx("button", { type: "button", className: "kpt-roadmap__toggle", "aria-expanded": !isCollapsed(group), "aria-label": group.label, onClick: () => toggleGroup(group), children: _jsx("span", { className: cn('kpt-roadmap__toggle-icon', !isCollapsed(group) && 'kpt-roadmap__toggle-icon--open') }) }) }), _jsx(KptTooltip, { text: group.sublabel ? `${group.label} - ${group.sublabel}` : group.label, onlyWhenTruncated: true, children: _jsxs("span", { className: "kpt-roadmap__label-stack", children: [_jsx("span", { className: "kpt-roadmap__label-text kpt-roadmap__label-text--group", children: group.label }), group.sublabel && _jsx("span", { className: "kpt-roadmap__label-sub", children: group.sublabel })] }) }), row.groupState?.itemCount ? (_jsx("span", { className: "kpt-roadmap__label-count", children: row.groupState.itemCount })) : null] })) : (first && (_jsxs(_Fragment, { children: [_jsx("span", { className: "kpt-roadmap__label-text", children: first.source.label }), row.items.length === 1 && first.source.sublabel && (_jsx("span", { className: "kpt-roadmap__label-sub", children: first.source.sublabel }))] }))) }) }), _jsx("div", { className: "kpt-roadmap__lane", children: row.kind === 'group'
246
323
  ? sum && (_jsx("div", { className: "kpt-roadmap__summary", "data-variant": sum.variant, style: {
247
324
  insetInlineStart: `${sum.left}%`,
248
325
  inlineSize: `${sum.width}%`,
249
326
  backgroundColor: sum.color,
250
327
  } }))
251
- : bars(row).map((bar) => bar.isMilestone ? (_jsx(KptTooltip, { text: bar.title, children: _jsx("div", { className: "kpt-roadmap__milestone", "data-variant": bar.variant, "aria-label": `${t('roadmap.milestone')}: ${bar.label}`, style: {
328
+ : bars(row).map((bar) => bar.isMilestone ? (_jsx(KptTooltip, { text: bar.title, children: _jsx("div", { className: "kpt-roadmap__milestone", "data-variant": bar.variant, style: {
252
329
  insetInlineStart: `${bar.left}%`,
253
330
  insetBlockStart: `${bar.top}px`,
254
331
  inlineSize: `${bar.height}px`,
255
332
  blockSize: `${bar.height}px`,
256
333
  backgroundColor: bar.color,
257
- }, tabIndex: 0, onClick: () => handleItemClick(bar.item), onKeyDown: (event) => handleItemKeydown(event, bar.item) }) }, bar.item.id)) : (_jsx(KptTooltip, { text: bar.title, onlyWhenTruncated: true, children: _jsxs("div", { className: cn('kpt-roadmap__bar', bar.clippedStart && 'kpt-roadmap__bar--clipped-start', bar.clippedEnd && 'kpt-roadmap__bar--clipped-end', bar.item.source.disabled && 'kpt-roadmap__bar--disabled'), "data-variant": bar.variant, style: {
334
+ }, role: "button", "aria-label": bar.ariaLabel, "aria-disabled": bar.item.source.disabled || undefined, "data-kpt-item": bar.item.id, tabIndex: bar.item.id === tabStopId ? 0 : -1, onClick: () => handleItemClick(bar.item), onFocus: () => setActiveId(bar.item.id), onKeyDown: (event) => handleItemKeydown(event, bar.item) }) }, bar.item.id)) : (_jsx(KptTooltip, { text: bar.title, onlyWhenTruncated: true, children: _jsxs("div", { className: cn('kpt-roadmap__bar', bar.clippedStart && 'kpt-roadmap__bar--clipped-start', bar.clippedEnd && 'kpt-roadmap__bar--clipped-end', bar.item.source.disabled && 'kpt-roadmap__bar--disabled'), "data-variant": bar.variant, style: {
258
335
  insetInlineStart: `${bar.left}%`,
259
336
  inlineSize: `${bar.width}%`,
260
337
  insetBlockStart: `${bar.top}px`,
261
338
  blockSize: `${bar.height}px`,
262
339
  backgroundColor: bar.color,
263
- }, tabIndex: 0, onClick: () => handleItemClick(bar.item), onKeyDown: (event) => handleItemKeydown(event, bar.item), children: [bar.progress !== null && (_jsx("span", { className: "kpt-roadmap__bar-progress", style: { inlineSize: `${bar.progress * 100}%` } })), _jsx("span", { className: "kpt-roadmap__bar-label", children: bar.label })] }) }, bar.item.id))) })] }, row.key));
340
+ }, role: "button", "aria-label": bar.ariaLabel, "aria-disabled": bar.item.source.disabled || undefined, "data-kpt-item": bar.item.id, tabIndex: bar.item.id === tabStopId ? 0 : -1, onClick: () => handleItemClick(bar.item), onFocus: () => setActiveId(bar.item.id), onKeyDown: (event) => handleItemKeydown(event, bar.item), children: [bar.progress !== null && (_jsx("span", { className: "kpt-roadmap__bar-progress", style: { inlineSize: `${bar.progress * 100}%` } })), _jsx("span", { className: "kpt-roadmap__bar-label", children: bar.label })] }) }, bar.item.id))) })] }, row.key));
264
341
  })] }))] }) }));
265
342
  }
266
343
  /** Identyfikatory grup zwiniętych deklaratywnie w danych. */
@@ -1,7 +1,7 @@
1
1
  /*
2
- * @konce-pt/react/roadmap — oś czasu typu Gantt. Osobny subpath, bo geometria, style i dane
2
+ * @konce-pt/react/roadmap - oś czasu typu Gantt. Osobny subpath, bo geometria, style i dane
3
3
  * roadmapy są ciężkie względem rdzenia biblioteki i nie każda aplikacja ich potrzebuje.
4
4
  */
5
5
  export { KptRoadmap } from "../roadmap/roadmap.js";
6
- // Rdzeń obliczeniowy (pure TS) — przydatny do własnych rozszerzeń, SSR i testów.
6
+ // Rdzeń obliczeniowy (pure TS) - przydatny do własnych rozszerzeń, SSR i testów.
7
7
  export { buildTimeline, buildRows, normalizeItems, packLanes, sliceRows, spanOf, rollupProgress, resolveLinks, linkGeometry, roundedPolyline, arrowHead, autoRange, resolveRange, isoWeekNumber, isoWeekYear, isoWeekday, isoWeeksInYear, startOfIsoWeek, startOfDay, addDays, addHours, addMonths, addUnits, floorToUnit, ceilToUnit, countUnits, diffDays, daysInMonth, isSameDay, toTimestamp, } from '@konce-pt/roadmap';