@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,11 +1,26 @@
1
+ 'use client';
1
2
  import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
3
  import { cloneElement, isValidElement, useCallback, useEffect, useId, useRef } from 'react';
3
4
  import { composeRefs } from "../internal/compose-refs.js";
4
- /** Elementy, które same przyjmują fokus — na nie trafia `aria-describedby`. */
5
+ import { isDevBuild } from "../internal/dev.js";
6
+ /** Elementy, które same przyjmują fokus - na nie trafia `aria-describedby`. */
5
7
  const FOCUSABLE = 'a[href], button, input, select, textarea, [tabindex]:not([tabindex="-1"])';
6
8
  /** Ile czasu ma wskaźnik na przejście z elementu na dymek, zanim ten zniknie (WCAG 1.4.13). */
7
9
  const HIDE_DELAY_MS = 120;
8
- /** Strona przeciwna i dwie boczne — kolejność prób, gdy zadana strona się nie nadaje. */
10
+ /**
11
+ * Zwłoka pokazania pod wskaźnikiem - przejazd myszą po pasku ikon nie zapala po kolei wszystkich
12
+ * dymków. Fokus z klawiatury pokazuje dymek od razu.
13
+ */
14
+ const SHOW_DELAY_MS = 300;
15
+ /**
16
+ * Gdzie dokleić dymek: do najbliższego otwartego `<dialog>` albo popovera hosta, inaczej do `body`.
17
+ * Modalny `<dialog>` leży w warstwie szczytowej - dymek z `body` byłby pod nim niewidoczny.
18
+ * Bliźniak w porcie Angulara.
19
+ */
20
+ function tooltipContainer(host) {
21
+ return host.closest('dialog[open], [popover]') ?? host.ownerDocument.body;
22
+ }
23
+ /** Strona przeciwna i dwie boczne - kolejność prób, gdy zadana strona się nie nadaje. */
9
24
  const FALLBACKS = {
10
25
  top: ['top', 'bottom', 'right', 'left'],
11
26
  bottom: ['bottom', 'top', 'right', 'left'],
@@ -15,10 +30,10 @@ const FALLBACKS = {
15
30
  /**
16
31
  * Ustawia dymek przy hoście, na pierwszej stronie z `FALLBACKS`, która się nadaje: dymek mieści się
17
32
  * w oknie, a krawędź hosta od tej strony nie jest niczym przykryta (przyklejony nagłówek, menu, panel
18
- * — sprawdzane `elementFromPoint` tuż przy krawędzi). Bez tego dymek przycisku wsuniętego pod
33
+ * - sprawdzane `elementFromPoint` tuż przy krawędzi). Bez tego dymek przycisku wsuniętego pod
19
34
  * nagłówek wyskakiwał nad nagłówkiem, odcięty od tego, co opisuje. Gdy żadna strona się nie nadaje,
20
35
  * zostaje zadana. Na koniec dymek jest dociśnięty do okna z marginesem 4 px.
21
- * Bliźniak tej funkcji żyje w drugim porcie — zmiany w obu naraz.
36
+ * Bliźniak tej funkcji żyje w drugim porcie - zmiany w obu naraz.
22
37
  */
23
38
  function placeTooltip(host, tip, preferred) {
24
39
  const r = host.getBoundingClientRect();
@@ -41,7 +56,7 @@ function placeTooltip(host, tip, preferred) {
41
56
  };
42
57
  const fits = (side) => {
43
58
  const { top, left } = at(side);
44
- // Po osi poprzecznej dymek i tak zostanie dociśnięty do okna — liczy się tylko oś strony.
59
+ // Po osi poprzecznej dymek i tak zostanie dociśnięty do okna - liczy się tylko oś strony.
45
60
  return side === 'top' || side === 'bottom'
46
61
  ? top >= margin && top + t.height <= vh - margin
47
62
  : left >= margin && left + t.width <= vw - margin;
@@ -63,14 +78,15 @@ function placeTooltip(host, tip, preferred) {
63
78
  tip.style.left = `${Math.min(Math.max(margin, left), Math.max(margin, vw - t.width - margin))}px`;
64
79
  }
65
80
  /**
66
- * Dymek podpowiedzi pokazywany na hover i focus. Lekki — element dodawany do `body`
67
- * i pozycjonowany względem dziecka.
81
+ * Dymek podpowiedzi pokazywany na hover i focus. Lekki - element dodawany do `body` (albo do
82
+ * otwartego `<dialog>`/popovera dziecka, żeby był widoczny w warstwie szczytowej) i pozycjonowany
83
+ * względem dziecka. Wygląd daje klasa `.kpt-tooltip` z `@konce-pt/styles` w warstwie `kpt.components`.
68
84
  *
69
85
  * Port dyrektywy `[kptTooltip]`: nie renderuje własnego opakowania, tylko dokleja handlery
70
86
  * i ref do przekazanego elementu, więc drzewo DOM zostaje takie samo jak bez tooltipa.
71
87
  *
72
88
  * Dostępność (jak w porcie Angulara): tekst trafia do czytników przez trwały, ukryty opis
73
- * powiązany `aria-describedby` z elementem przyjmującym fokus — pomijany, gdy powtarzałby
89
+ * powiązany `aria-describedby` z elementem przyjmującym fokus - pomijany, gdy powtarzałby
74
90
  * jego nazwę albo przy `onlyWhenTruncated`. `Escape` zamyka dymek, a wskaźnik może na niego
75
91
  * przejść bez zniknięcia (WCAG 1.4.13).
76
92
  *
@@ -80,9 +96,18 @@ function placeTooltip(host, tip, preferred) {
80
96
  */
81
97
  export function KptTooltip({ text, position = 'top', onlyWhenTruncated = false, children }) {
82
98
  const hostRef = useRef(null);
99
+ // Dymek przypina się do elementu przez `ref` dziecka. Własny komponent, który nie przekazuje
100
+ // `ref` do elementu DOM, zostawia hosta pustym - dymek wtedy po cichu nie działa.
101
+ useEffect(() => {
102
+ if (isDevBuild() && isValidElement(children) && !hostRef.current) {
103
+ console.warn('[KptTooltip] Dziecko nie przekazało `ref` do elementu DOM - dymek nie ma się do czego przypiąć.');
104
+ }
105
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- sprawdzenie raz, po montażu
106
+ }, []);
83
107
  const tipRef = useRef(null);
84
108
  const descRef = useRef(null);
85
109
  const hideTimer = useRef(undefined);
110
+ const showTimer = useRef(undefined);
86
111
  const descId = `kpt-tooltip-desc-${useId().replace(/[^a-zA-Z0-9]/g, '')}`;
87
112
  // Trwały opis dla czytników: dymek powstaje dopiero przy pokazaniu, więc nie da się go
88
113
  // wskazać wcześniej. Opis żyje razem z komponentem i aktualizuje się ze zmianą tekstu.
@@ -119,67 +144,98 @@ export function KptTooltip({ text, position = 'top', onlyWhenTruncated = false,
119
144
  return;
120
145
  placeTooltip(host, tip, position);
121
146
  }, [position]);
122
- const onKeyDown = useCallback((event) => {
123
- if (event.key === 'Escape')
124
- hideRef.current();
125
- }, []);
147
+ /** Sprzątanie bieżącego dymka (nasłuchy i element) - ustawiane przy pokazaniu, wołane przy ukryciu. */
148
+ const cleanupRef = useRef(null);
126
149
  const cancelHide = useCallback(() => {
127
150
  if (hideTimer.current !== undefined)
128
151
  clearTimeout(hideTimer.current);
129
152
  hideTimer.current = undefined;
130
153
  }, []);
154
+ const cancelShow = useCallback(() => {
155
+ if (showTimer.current !== undefined)
156
+ clearTimeout(showTimer.current);
157
+ showTimer.current = undefined;
158
+ }, []);
131
159
  const hide = useCallback(() => {
132
160
  cancelHide();
133
- window.removeEventListener('scroll', reposition, true);
134
- window.removeEventListener('resize', reposition);
135
- document.removeEventListener('keydown', onKeyDown);
136
- tipRef.current?.remove();
137
- tipRef.current = null;
138
- }, [reposition, onKeyDown, cancelHide]);
139
- // `onKeyDown` potrzebuje aktualnego `hide`, a `hide` zdejmuje `onKeyDown` — ref przerywa krąg.
140
- const hideRef = useRef(hide);
141
- hideRef.current = hide;
161
+ cancelShow();
162
+ cleanupRef.current?.();
163
+ cleanupRef.current = null;
164
+ }, [cancelHide, cancelShow]);
142
165
  const scheduleHide = useCallback(() => {
166
+ cancelShow();
143
167
  cancelHide();
144
- hideTimer.current = setTimeout(() => hideRef.current(), HIDE_DELAY_MS);
145
- }, [cancelHide]);
146
- useEffect(() => () => hideRef.current(), []);
168
+ hideTimer.current = setTimeout(hide, HIDE_DELAY_MS);
169
+ }, [cancelHide, cancelShow, hide]);
170
+ useEffect(() => hide, [hide]);
147
171
  const show = useCallback(() => {
148
172
  cancelHide();
149
- if (tipRef.current || !text)
173
+ cancelShow();
174
+ const host = hostRef.current;
175
+ if (tipRef.current || !text || !host)
150
176
  return;
151
177
  // Sprawdzamy dopiero teraz, a nie przy renderze: dzięki temu wynik jest zawsze aktualny
152
178
  // po zmianie szerokości czy powiększenia, bez żadnych obserwatorów.
153
- if (onlyWhenTruncated && !isTruncated(hostRef.current))
179
+ if (onlyWhenTruncated && !isTruncated(host))
154
180
  return;
155
181
  const tip = document.createElement('div');
156
182
  tip.className = 'kpt-tooltip';
157
183
  tip.setAttribute('role', 'tooltip');
158
- // Treść czytnik dostaje z trwałego opisu — dymek by ją zdublował.
184
+ // Treść czytnik dostaje z trwałego opisu - dymek by ją zdublował.
159
185
  if (descRef.current)
160
186
  tip.setAttribute('aria-hidden', 'true');
161
187
  tip.textContent = text;
162
- tip.style.cssText =
163
- 'position:fixed;z-index:1400;max-width:16rem;padding:0.35rem 0.6rem;' +
164
- 'border-radius:var(--kpt-radius-sm,0.25rem);font-size:var(--kpt-font-size-xs,0.75rem);' +
165
- 'line-height:1.4;background:var(--kpt-color-on-surface);color:var(--kpt-color-surface);' +
166
- 'box-shadow:var(--kpt-elevation-2);opacity:0;transition:opacity 120ms;';
188
+ // Inline tylko to, bez czego dymek nie stanie przy hoście; wygląd daje klasa w `@layer`.
189
+ tip.style.position = 'fixed';
167
190
  tip.addEventListener('mouseenter', cancelHide);
168
191
  tip.addEventListener('mouseleave', scheduleHide);
169
- document.body.appendChild(tip);
192
+ tooltipContainer(host).appendChild(tip);
170
193
  tipRef.current = tip;
171
194
  reposition();
195
+ const onKeyDown = (event) => {
196
+ if (event.key === 'Escape')
197
+ hide();
198
+ };
172
199
  // Faza przechwytywania łapie także przewijanie zagnieżdżonych kontenerów, bo zdarzenie
173
200
  // `scroll` elementu się nie propaguje.
174
201
  window.addEventListener('scroll', reposition, true);
175
202
  window.addEventListener('resize', reposition);
176
203
  document.addEventListener('keydown', onKeyDown);
177
- requestAnimationFrame(() => {
178
- if (tipRef.current)
179
- tipRef.current.style.opacity = '1';
204
+ const frame = requestAnimationFrame(() => {
205
+ tip.dataset['visible'] = '';
180
206
  });
181
- }, [text, onlyWhenTruncated, reposition, onKeyDown, cancelHide, scheduleHide]);
182
- /** Fokus przeszedł do innego elementu w obrębie hosta — dymek zostaje. */
207
+ cleanupRef.current = () => {
208
+ cancelAnimationFrame(frame);
209
+ window.removeEventListener('scroll', reposition, true);
210
+ window.removeEventListener('resize', reposition);
211
+ document.removeEventListener('keydown', onKeyDown);
212
+ tip.remove();
213
+ tipRef.current = null;
214
+ };
215
+ }, [text, onlyWhenTruncated, reposition, cancelHide, cancelShow, scheduleHide, hide]);
216
+ /** Najechanie wskaźnikiem pokazuje dymek po chwili; fokus - od razu. */
217
+ const scheduleShow = useCallback(() => {
218
+ cancelHide();
219
+ if (tipRef.current || showTimer.current !== undefined)
220
+ return;
221
+ showTimer.current = setTimeout(() => {
222
+ showTimer.current = undefined;
223
+ show();
224
+ }, SHOW_DELAY_MS);
225
+ }, [cancelHide, show]);
226
+ // Zmiana tekstu przy widocznym dymku - bez tego dymek pokazywałby starą treść do zamknięcia.
227
+ useEffect(() => {
228
+ const tip = tipRef.current;
229
+ if (!tip)
230
+ return;
231
+ if (!text)
232
+ hide();
233
+ else if (tip.textContent !== text) {
234
+ tip.textContent = text;
235
+ reposition();
236
+ }
237
+ }, [text, hide, reposition]);
238
+ /** Fokus przeszedł do innego elementu w obrębie hosta - dymek zostaje. */
183
239
  const onBlur = (event) => {
184
240
  const next = event.relatedTarget;
185
241
  if (next && hostRef.current?.contains(next))
@@ -190,15 +246,18 @@ export function KptTooltip({ text, position = 'top', onlyWhenTruncated = false,
190
246
  return _jsx(_Fragment, { children: children });
191
247
  const child = children;
192
248
  const props = child.props;
249
+ /* eslint-disable react-hooks/refs -- composeRefs i chain tylko składają funkcje; refy czytają one
250
+ dopiero przy podpięciu elementu i w zdarzeniach, nigdy w renderze */
193
251
  return cloneElement(child, {
194
252
  ref: composeRefs(hostRef, props['ref']),
195
- onMouseEnter: chain(props['onMouseEnter'], show),
253
+ onMouseEnter: chain(props['onMouseEnter'], scheduleShow),
196
254
  onMouseLeave: chain(props['onMouseLeave'], scheduleHide),
197
255
  onFocus: chain(props['onFocus'], show),
198
256
  onBlur: chain(props['onBlur'], onBlur),
199
257
  });
258
+ /* eslint-enable react-hooks/refs */
200
259
  }
201
- /** Handler dziecka najpierw, nasz po nim — jak w scalaniu propsów slotu. */
260
+ /** Handler dziecka najpierw, nasz po nim - jak w scalaniu propsów slotu. */
202
261
  function chain(theirs, ours) {
203
262
  return (...args) => {
204
263
  if (typeof theirs === 'function')
@@ -208,7 +267,7 @@ function chain(theirs, ours) {
208
267
  }
209
268
  /**
210
269
  * Czy treść jest przycięta? Sprawdzamy hosta oraz jego bezpośrednie dzieci, bo
211
- * `text-overflow: ellipsis` siedzi zwykle na wewnętrznym elemencie — a taki element kurczy się
270
+ * `text-overflow: ellipsis` siedzi zwykle na wewnętrznym elemencie - a taki element kurczy się
212
271
  * do dostępnej szerokości i nie rozpycha rodzica, więc pomiar samego hosta nie wykryłby niczego.
213
272
  */
214
273
  function isTruncated(root) {
@@ -8,25 +8,26 @@ Import: `import { KptTree, type KptTreeNode } from '@konce-pt/react';`
8
8
  <ul class="kpt-tree" role="tree">
9
9
  <li class="kpt-tree__node" role="treeitem" aria-expanded="true">
10
10
  <div class="kpt-tree__row is-selected">
11
- <span class="kpt-tree__toggle" aria-hidden="true">…chevron…</span>
12
- <span class="kpt-tree__icon">…</span>
11
+ <span class="kpt-tree__toggle" aria-hidden="true">...chevron...</span>
12
+ <span class="kpt-tree__icon">...</span>
13
13
  <span class="kpt-tree__label">src</span>
14
14
  </div>
15
- <ul role="group">…</ul>
15
+ <ul role="group">...</ul>
16
16
  </li>
17
17
  </ul>
18
18
  </div>
19
19
 
20
20
  ## Props
21
- - `nodes`: `KptTreeNode<T>[]` — `{ label, value?, icon?, children?, expanded?, disabled? }`
22
- - `selectable`: boolean — a row click selects instead of expanding
23
- - `selected` / `defaultSelected` / `onSelectedChange` — the node object, or `null`
21
+ - `nodes`: `KptTreeNode<T>[]` - `{ label, value?, icon?, children?, expanded?, disabled? }`
22
+ - `selectable`: boolean - a row click selects instead of expanding
23
+ - `collapseMotion` - `'spring' | 'fade' | 'none'` (default from `KptMotionProvider`, else `spring`) - see below
24
+ - `selected` / `defaultSelected` / `onSelectedChange` - the node object, or `null`
24
25
  - `onNodeClick(node)`
25
26
  - `className`, `ref` and every other `<div>` prop go to the host
26
27
 
27
28
  ## Expansion is component state, not data
28
29
  `expanded` in a node is the initial state only. The component keeps what the user opened or closed
29
- itself and never writes to the node objects — so two trees fed the same nodes open and close
30
+ itself and never writes to the node objects - so two trees fed the same nodes open and close
30
31
  independently, and replacing `nodes` with new objects brings their own initial state.
31
32
 
32
33
  ## Examples
@@ -36,16 +37,28 @@ independently, and replacing `nodes` with new objects brings their own initial s
36
37
  ## Keyboard
37
38
  The tree is one Tab stop (roving tabindex on the `treeitem` elements), following the WAI-ARIA tree view
38
39
  pattern:
39
- - `↓` / `↑` — the next / previous visible row, `Home` / `End` — the first / last;
40
- - `→` — expands a closed branch, on an open one moves to its first child;
41
- - `←` — collapses an open branch, otherwise moves to the parent;
42
- - `Enter` / `Space` — the same as a click: selects (with `selectable`) or toggles the branch.
40
+ - `ArrowDown` / `ArrowUp` - the next / previous visible row, `Home` / `End` - the first / last;
41
+ - `ArrowRight` - expands a closed branch, on an open one moves to its first child;
42
+ - `ArrowLeft` - collapses an open branch, otherwise moves to the parent;
43
+ - `Enter` / `Space` - the same as a click: selects (with `selectable`) or toggles the branch.
43
44
  The logic is the pure function `treeKeyAction` in `tree-keys.ts`; the other port has a twin file.
44
45
 
45
46
  ## i18n
46
47
  No labels of its own any more: the chevron is mouse-only and the keyboard expands with the arrows.
47
48
  `locale` and `dictionary` are still accepted, so existing call sites keep compiling.
48
49
 
50
+ ## Branch motion
51
+ `collapseMotion` animates expanding and collapsing a branch:
52
+ - `spring` - the height opens on a soft spring, children slide in as a cascade and sharpen out of a
53
+ blur; when collapsing, the children fade first;
54
+ - `fade` - children cross-fade;
55
+ - `none` - no motion.
56
+
57
+ A collapsing branch stays in the DOM (`inert`) until its animation ends, so it does not cut off
58
+ mid-way; keyboard navigation already skips it. Entry rules apply only after the first render, so
59
+ branches expanded in the data do not animate on page load. With no value it takes `collapse` from
60
+ `KptMotionProvider` (default `spring`); `enabled: false` and the system "reduce motion" setting always win.
61
+
49
62
  ## Tokens
50
63
  The selected row `--kpt-color-primary`, the hover `--kpt-color-surface-variant`.
51
64
 
@@ -55,3 +68,4 @@ selectable trees `aria-selected`, disabled rows `aria-disabled`. The chevron is
55
68
  (`aria-hidden`): a button inside a `treeitem` would be a nested control and a second Tab stop per
56
69
  row, and the keyboard expands with the arrows anyway. The focus ring is drawn on the row, not on the
57
70
  `<li>`, which would outline the whole subtree.
71
+ `ariaLabel` names the `role="tree"` element (e.g. "Project files") - a separate prop because `aria-label` from the rest props lands on the wrapper, not on the tree.
@@ -18,10 +18,10 @@ export type KptTreeKeyAction = {
18
18
  index: number;
19
19
  };
20
20
  /**
21
- * ↓ / ↑ — następny / poprzedni widoczny wiersz (bez zawijania), `Home` / `End` — pierwszy / ostatni.
22
- * → — zamkniętą gałąź rozwija, w otwartej przechodzi do pierwszego dziecka.
23
- * ← — otwartą gałąź zwija, w pozostałych przypadkach przechodzi do rodzica.
24
- * `Enter` / spacja — aktywuje wiersz (zaznaczenie albo przełączenie, jak kliknięcie).
25
- * Zwraca `null` dla klawisza, który nic nie robi — wtedy nie blokujemy jego domyślnej akcji.
21
+ * ArrowDown / ArrowUp - następny / poprzedni widoczny wiersz (bez zawijania), `Home` / `End` - pierwszy / ostatni.
22
+ * -> - zamkniętą gałąź rozwija, w otwartej przechodzi do pierwszego dziecka.
23
+ * <- - otwartą gałąź zwija, w pozostałych przypadkach przechodzi do rodzica.
24
+ * `Enter` / spacja - aktywuje wiersz (zaznaczenie albo przełączenie, jak kliknięcie).
25
+ * Zwraca `null` dla klawisza, który nic nie robi - wtedy nie blokujemy jego domyślnej akcji.
26
26
  */
27
27
  export declare function treeKeyAction(rows: readonly KptTreeRow[], current: number, key: string): KptTreeKeyAction | null;
@@ -1,13 +1,13 @@
1
1
  /*
2
- * Klawiatura drzewa (wzorzec WAI-ARIA „tree view”) — czysta funkcja na spłaszczonej liście
3
- * widocznych wierszy. Port Angulara ma bliźniaczy plik `lib/tree/tree-keys.ts` — zmiana zachowania w obu.
2
+ * Klawiatura drzewa (wzorzec WAI-ARIA "tree view") - czysta funkcja na spłaszczonej liście
3
+ * widocznych wierszy. Port Angulara ma bliźniaczy plik `lib/tree/tree-keys.ts` - zmiana zachowania w obu.
4
4
  */
5
5
  /**
6
- * ↓ / ↑ — następny / poprzedni widoczny wiersz (bez zawijania), `Home` / `End` — pierwszy / ostatni.
7
- * → — zamkniętą gałąź rozwija, w otwartej przechodzi do pierwszego dziecka.
8
- * ← — otwartą gałąź zwija, w pozostałych przypadkach przechodzi do rodzica.
9
- * `Enter` / spacja — aktywuje wiersz (zaznaczenie albo przełączenie, jak kliknięcie).
10
- * Zwraca `null` dla klawisza, który nic nie robi — wtedy nie blokujemy jego domyślnej akcji.
6
+ * ArrowDown / ArrowUp - następny / poprzedni widoczny wiersz (bez zawijania), `Home` / `End` - pierwszy / ostatni.
7
+ * -> - zamkniętą gałąź rozwija, w otwartej przechodzi do pierwszego dziecka.
8
+ * <- - otwartą gałąź zwija, w pozostałych przypadkach przechodzi do rodzica.
9
+ * `Enter` / spacja - aktywuje wiersz (zaznaczenie albo przełączenie, jak kliknięcie).
10
+ * Zwraca `null` dla klawisza, który nic nie robi - wtedy nie blokujemy jego domyślnej akcji.
11
11
  */
12
12
  export function treeKeyAction(rows, current, key) {
13
13
  const row = rows[current];
@@ -1,5 +1,6 @@
1
1
  import { type ComponentPropsWithRef } from 'react';
2
2
  import type { KptI18nOverrides } from '../i18n/i18n-context.tsx';
3
+ import { type KptCollapseMotion } from '@konce-pt/motion';
3
4
  export interface KptTreeNode<T = unknown> {
4
5
  label: string;
5
6
  value?: T;
@@ -17,22 +18,33 @@ export interface KptTreeProps<T> extends Omit<ComponentPropsWithRef<'div'>, 'onS
17
18
  defaultSelected?: KptTreeNode<T> | null;
18
19
  onSelectedChange?: (node: KptTreeNode<T> | null) => void;
19
20
  onNodeClick?: (node: KptTreeNode<T>) => void;
21
+ /**
22
+ * Nazwa drzewa dla czytnika (`role="tree"` bez nazwy to samo "drzewo"), np. "Pliki projektu".
23
+ * Trafia na element `role="tree"`, nie na opakowanie - dlatego osobny prop, a nie `aria-label`.
24
+ */
25
+ ariaLabel?: string;
26
+ /**
27
+ * Ruch rozwijania gałęzi: `spring` - wysokość na sprężynie, dzieci wjeżdżają kaskadą i wyostrzają
28
+ * się z rozmycia; `fade` - dzieci się przenikają; `none` - bez ruchu. Bez wartości bierze `collapse`
29
+ * z `KptMotionProvider` (domyślnie `spring`); `enabled={false}` i systemowe "ogranicz ruch" wyłączają go zawsze.
30
+ */
31
+ collapseMotion?: KptCollapseMotion;
20
32
  }
21
33
  /**
22
34
  * Drzewo hierarchiczne: rozwijanie, zaznaczanie, ikony.
23
35
  *
24
36
  * Stan rozwinięcia trzyma komponent, a nie pole `expanded` w danych: mutowanie cudzej tablicy
25
37
  * przy każdym kliknięciu byłoby w Reakcie efektem ubocznym renderu, a `expanded` z danych zostaje
26
- * tym, czym powinno być — stanem początkowym.
38
+ * tym, czym powinno być - stanem początkowym.
27
39
  *
28
- * Klawiatura według wzorca WAI-ARIA „tree view”: jeden przystanek Tab na całe drzewo (roving
29
- * tabindex), strzałki po widocznych wierszach, → / ← rozwijają i zwijają, `Enter` / spacja
40
+ * Klawiatura według wzorca WAI-ARIA "tree view": jeden przystanek Tab na całe drzewo (roving
41
+ * tabindex), strzałki po widocznych wierszach, -> / <- rozwijają i zwijają, `Enter` / spacja
30
42
  * działają jak kliknięcie. Logika klawiszy siedzi w czystej funkcji `treeKeyAction`.
31
43
  *
32
- * `locale` i `dictionary` są przyjmowane dla zgodności — drzewo nie ma już własnych etykiet:
44
+ * `locale` i `dictionary` są przyjmowane dla zgodności - drzewo nie ma już własnych etykiet:
33
45
  * przełącznik gałęzi jest tylko dla myszy, a z klawiatury rozwija się strzałkami.
34
46
  *
35
47
  * @example
36
48
  * <KptTree nodes={tree} selectable selected={node} onSelectedChange={setNode} />
37
49
  */
38
- export declare function KptTree<T>({ nodes, selectable, selected, defaultSelected, onSelectedChange, onNodeClick, locale: _locale, dictionary: _dictionary, className, ...rest }: Readonly<KptTreeProps<T>>): import("react").JSX.Element;
50
+ export declare function KptTree<T>({ nodes, selectable, selected, defaultSelected, onSelectedChange, onNodeClick, ariaLabel, collapseMotion, locale: _locale, dictionary: _dictionary, className, ...rest }: Readonly<KptTreeProps<T>>): import("react").JSX.Element;
package/dist/tree/tree.js CHANGED
@@ -1,43 +1,59 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  import { useEffect, useMemo, useRef, useState, } from 'react';
3
4
  import { cn } from "../internal/cn.js";
4
5
  import { useControllableState } from "../internal/use-controllable-state.js";
5
6
  import { KptIcon } from "../icon/icon.js";
6
7
  import { treeKeyAction } from "./tree-keys.js";
8
+ import { layoutMotionAllowed, resolveCollapseMotion } from '@konce-pt/motion';
9
+ import { useKptMotion } from "../motion/motion-context.js";
10
+ /** Ile gałąź w trakcie zwijania zostaje w DOM - z zapasem nad `--kpt-motion-duration-slow`. */
11
+ const CLOSE_MS = 420;
12
+ /** Wiersze drzewa z pominięciem tych w zwijanej gałęzi - ich kolejność to kolejność `rows`. */
13
+ const VISIBLE_ITEMS = '[role="treeitem"]:not(.kpt-tree__collapse:not(.is-open) *)';
7
14
  /**
8
15
  * Drzewo hierarchiczne: rozwijanie, zaznaczanie, ikony.
9
16
  *
10
17
  * Stan rozwinięcia trzyma komponent, a nie pole `expanded` w danych: mutowanie cudzej tablicy
11
18
  * przy każdym kliknięciu byłoby w Reakcie efektem ubocznym renderu, a `expanded` z danych zostaje
12
- * tym, czym powinno być — stanem początkowym.
19
+ * tym, czym powinno być - stanem początkowym.
13
20
  *
14
- * Klawiatura według wzorca WAI-ARIA „tree view”: jeden przystanek Tab na całe drzewo (roving
15
- * tabindex), strzałki po widocznych wierszach, → / ← rozwijają i zwijają, `Enter` / spacja
21
+ * Klawiatura według wzorca WAI-ARIA "tree view": jeden przystanek Tab na całe drzewo (roving
22
+ * tabindex), strzałki po widocznych wierszach, -> / <- rozwijają i zwijają, `Enter` / spacja
16
23
  * działają jak kliknięcie. Logika klawiszy siedzi w czystej funkcji `treeKeyAction`.
17
24
  *
18
- * `locale` i `dictionary` są przyjmowane dla zgodności — drzewo nie ma już własnych etykiet:
25
+ * `locale` i `dictionary` są przyjmowane dla zgodności - drzewo nie ma już własnych etykiet:
19
26
  * przełącznik gałęzi jest tylko dla myszy, a z klawiatury rozwija się strzałkami.
20
27
  *
21
28
  * @example
22
29
  * <KptTree nodes={tree} selectable selected={node} onSelectedChange={setNode} />
23
30
  */
24
- export function KptTree({ nodes = [], selectable = false, selected, defaultSelected = null, onSelectedChange, onNodeClick, locale: _locale, dictionary: _dictionary, className, ...rest }) {
31
+ export function KptTree({ nodes = [], selectable = false, selected, defaultSelected = null, onSelectedChange, onNodeClick, ariaLabel, collapseMotion, locale: _locale, dictionary: _dictionary, className, ...rest }) {
25
32
  const [active, setActive] = useControllableState({
26
33
  value: selected,
27
34
  defaultValue: defaultSelected,
28
35
  onChange: onSelectedChange,
29
36
  });
30
- // Gałęzie przełączone przez użytkownika (węzeł → otwarta?). Węzeł spoza mapy ma stan z `expanded`
31
- // w danych — dzięki temu nowa tablica `nodes` przychodzi z własnym stanem początkowym.
37
+ // Gałęzie przełączone przez użytkownika (węzeł -> otwarta?). Węzeł spoza mapy ma stan z `expanded`
38
+ // w danych - dzięki temu nowa tablica `nodes` przychodzi z własnym stanem początkowym.
32
39
  const [toggled, setToggled] = useState(() => new Map());
33
- /** Wiersz, który ostatnio miał fokus — on jest przystankiem Tab. */
40
+ /** Wiersz, który ostatnio miał fokus - on jest przystankiem Tab. */
34
41
  const [focused, setFocused] = useState(null);
35
42
  /** Węzeł, na który po renderze trzeba przenieść fokus DOM (klawiatura). */
36
43
  const [focusRequest, setFocusRequest] = useState(null);
44
+ const handledFocus = useRef(null);
37
45
  const treeRef = useRef(null);
46
+ const motion = resolveCollapseMotion(collapseMotion, useKptMotion());
47
+ /** Gałęzie w trakcie zwijania - zostają w DOM, dopóki gra animacja. */
48
+ const [closing, setClosing] = useState(() => new Set());
49
+ // Reguły wejścia (`@starting-style`) dopiero po pierwszym renderze - start strony się nie rusza.
50
+ // Klasa idzie wprost na element: osobny stan dałby drugi przebieg renderu tylko po to.
51
+ useEffect(() => {
52
+ treeRef.current?.classList.add('is-live');
53
+ }, []);
38
54
  const hasChildren = (node) => !!node.children?.length;
39
55
  const isOpen = (node) => toggled.get(node) ?? !!node.expanded;
40
- // Widoczne wiersze w kolejności DOM — tyle widzi klawiatura.
56
+ // Widoczne wiersze w kolejności DOM - tyle widzi klawiatura.
41
57
  const rows = useMemo(() => {
42
58
  const out = [];
43
59
  const walk = (list, level, parent) => {
@@ -53,20 +69,46 @@ export function KptTree({ nodes = [], selectable = false, selected, defaultSelec
53
69
  walk(nodes, 0, -1);
54
70
  return out;
55
71
  }, [nodes, toggled]);
56
- // Jedyny wiersz z `tabIndex=0`: ostatnio skupiony, a gdy go nie widać — zaznaczony albo pierwszy.
72
+ // Jedyny wiersz z `tabIndex=0`: ostatnio skupiony, a gdy go nie widać - zaznaczony albo pierwszy.
57
73
  const visible = rows.map((r) => r.node);
58
74
  const tabStop = focused && visible.includes(focused) ? focused : active && visible.includes(active) ? active : (visible[0] ?? null);
75
+ // Żądanie nie jest zerowane w efekcie (setState w efekcie = dodatkowy przebieg renderu): każde jest
76
+ // nowym obiektem, a obsłużone zapamiętuje ref - kolejne przebiegi efektu go pomijają.
59
77
  useEffect(() => {
60
- if (!focusRequest)
78
+ if (!focusRequest || handledFocus.current === focusRequest)
61
79
  return;
62
- const index = rows.findIndex((r) => r.node === focusRequest);
63
- treeRef.current?.querySelectorAll('[role="treeitem"]')[index]?.focus();
64
- setFocusRequest(null);
80
+ const index = rows.findIndex((r) => r.node === focusRequest.node);
81
+ const element = treeRef.current?.querySelectorAll(VISIBLE_ITEMS)[index];
82
+ if (!element)
83
+ return; // wiersz jeszcze nie wyrenderowany (rozwijanie) - spróbujemy po zmianie `rows`
84
+ handledFocus.current = focusRequest;
85
+ element.focus();
65
86
  }, [focusRequest, rows]);
66
87
  const toggle = (node) => {
88
+ const willOpen = !isOpen(node);
67
89
  const next = new Map(toggled);
68
- next.set(node, !isOpen(node));
90
+ next.set(node, willOpen);
69
91
  setToggled(next);
92
+ // Zwijana gałąź zostaje w DOM na czas animacji; ponowne otwarcie w trakcie ją po prostu zatrzymuje.
93
+ const animate = motion !== 'none' && layoutMotionAllowed();
94
+ setClosing((current) => {
95
+ const set = new Set(current);
96
+ set.delete(node);
97
+ if (!willOpen && animate)
98
+ set.add(node);
99
+ return set;
100
+ });
101
+ if (!willOpen && animate) {
102
+ setTimeout(() => {
103
+ setClosing((current) => {
104
+ if (!current.has(node))
105
+ return current;
106
+ const set = new Set(current);
107
+ set.delete(node);
108
+ return set;
109
+ });
110
+ }, CLOSE_MS);
111
+ }
70
112
  };
71
113
  const onRow = (node) => {
72
114
  setFocused(node);
@@ -79,7 +121,7 @@ export function KptTree({ nodes = [], selectable = false, selected, defaultSelec
79
121
  onNodeClick?.(node);
80
122
  };
81
123
  const onKeyDown = (node, event) => {
82
- // Zagnieżdżone `<li>` przepuszczają zdarzenie w górę — obsługuje je tylko wiersz z fokusem.
124
+ // Zagnieżdżone `<li>` przepuszczają zdarzenie w górę - obsługuje je tylko wiersz z fokusem.
83
125
  if (event.target !== event.currentTarget)
84
126
  return;
85
127
  const current = rows.findIndex((r) => r.node === node);
@@ -94,15 +136,15 @@ export function KptTree({ nodes = [], selectable = false, selected, defaultSelec
94
136
  onRow(target);
95
137
  else {
96
138
  setFocused(target);
97
- setFocusRequest(target);
139
+ setFocusRequest({ node: target });
98
140
  }
99
141
  };
100
- const renderList = (list, level) => list.map((node, index) => (_jsxs("li", { className: "kpt-tree__node", role: "treeitem", "aria-level": level + 1, "aria-expanded": hasChildren(node) ? isOpen(node) : undefined, "aria-selected": selectable ? active === node : undefined, "aria-disabled": node.disabled ? true : undefined, tabIndex: node === tabStop ? 0 : -1, onFocus: (event) => {
142
+ const renderList = (list, level) => list.map((node, index) => (_jsxs("li", { className: "kpt-tree__node", style: { '--_i': index }, role: "treeitem", "aria-level": level + 1, "aria-expanded": hasChildren(node) ? isOpen(node) : undefined, "aria-selected": selectable ? active === node : undefined, "aria-disabled": node.disabled ? true : undefined, tabIndex: node === tabStop ? 0 : -1, onFocus: (event) => {
101
143
  if (event.target === event.currentTarget)
102
144
  setFocused(node);
103
145
  }, onKeyDown: (event) => onKeyDown(node, event), children: [_jsxs("div", { className: cn('kpt-tree__row', active === node && 'is-selected', node.disabled && 'is-disabled'), style: { paddingInlineStart: `${0.5 + level * 1.1}rem` }, onClick: () => onRow(node), children: [hasChildren(node) ? (_jsx("span", { className: "kpt-tree__toggle", "aria-hidden": "true", onClick: (event) => {
104
146
  event.stopPropagation();
105
147
  toggle(node);
106
- }, children: _jsx(KptIcon, { name: "chevron-right", className: cn('kpt-tree__chevron', isOpen(node) && 'is-open') }) })) : (_jsx("span", { className: "kpt-tree__spacer" })), node.icon ? _jsx(KptIcon, { name: node.icon, className: "kpt-tree__icon" }) : null, _jsx("span", { className: "kpt-tree__label", children: node.label })] }), hasChildren(node) && isOpen(node) ? _jsx("ul", { role: "group", children: renderList(node.children, level + 1) }) : null] }, index)));
107
- return (_jsx("div", { className: cn('kpt-tree-host', className), ...rest, children: _jsx("ul", { ref: treeRef, className: "kpt-tree", role: "tree", children: renderList(nodes, 0) }) }));
148
+ }, children: _jsx(KptIcon, { name: "chevron-right", className: cn('kpt-tree__chevron', isOpen(node) && 'is-open') }) })) : (_jsx("span", { className: "kpt-tree__spacer" })), node.icon ? _jsx(KptIcon, { name: node.icon, className: "kpt-tree__icon" }) : null, _jsx("span", { className: "kpt-tree__label", children: node.label })] }), hasChildren(node) && (isOpen(node) || closing.has(node)) ? (_jsx("div", { className: cn('kpt-tree__collapse', isOpen(node) && 'is-open'), inert: !isOpen(node), children: _jsx("ul", { role: "group", className: "kpt-tree__group", children: renderList(node.children, level + 1) }) })) : null] }, index)));
149
+ return (_jsx("div", { className: cn('kpt-tree-host', className), ...rest, children: _jsx("ul", { ref: treeRef, className: "kpt-tree", role: "tree", "aria-label": ariaLabel || undefined, "data-motion": motion, children: renderList(nodes, 0) }) }));
108
150
  }
@@ -0,0 +1,54 @@
1
+ # KptUpdate (@konce-pt/react)
2
+
3
+ An app update row: the icon in `KptLoader variant="ring"`, the name, a status line
4
+ ("12.3 MB of 24 MB") and a button whose label follows the state; texts cross-fade on a state change.
5
+ Same classes as `kpt-update` in Angular. Import: `import { KptUpdate } from '@konce-pt/react';`
6
+
7
+ The component downloads nothing - the app owns the state, the byte counts and what an action does.
8
+
9
+ ## DOM
10
+ <div class="kpt-update-host">
11
+ <div class="kpt-update" data-status="downloading">
12
+ <span class="kpt-loader-host kpt-update__ring">...<span class="kpt-update__icon">...</span>...</span>
13
+ <div class="kpt-update__meta">
14
+ <span class="kpt-update__name">Kompas</span>
15
+ <span class="kpt-update__line kpt-update__swap" aria-hidden="true"><span>0.4 MB of 24 MB</span></span>
16
+ <span class="kpt-button-host kpt-update__action"><button class="kpt-button">...</button></span>
17
+ </div>
18
+ <span class="kpt-update__live" role="status">Kompas: Downloading...</span>
19
+ </div>
20
+ </div>
21
+
22
+ ## States (`status`)
23
+ `pending` (default, ring loops, button "Update") -> `downloading` (with `total` the ring draws
24
+ progress and the line shows both sizes in the unit of the total; button "Cancel") -> `success`
25
+ (the ring releases the icon; "Open") or `error` (a badge on the icon; "Retry").
26
+
27
+ ## Props
28
+ - `name`: string (required)
29
+ - `status`: KptUpdateStatus (default 'pending')
30
+ - `loaded`, `total`: number | null (bytes) - formatted with `Intl` in the interface language
31
+ - `size`: string (default '5.5rem'), `color`: KptLoaderColor (default 'info')
32
+ - `showAction`: boolean (default true)
33
+ - `pendingText`, `downloadingText`, `successText`, `errorText`, `updateLabel`, `cancelLabel`,
34
+ `openLabel`, `retryLabel` - override the `update.*` texts
35
+ - `onAction`: (action: KptUpdateAction) => void - 'update' | 'cancel' | 'open' | 'retry' by state
36
+ - `children` - the icon (`<img>`, SVG, `KptIcon`); omitted = `download`
37
+ - `locale`, `dictionary` - i18n overrides for this component only
38
+ - `className`, `style`, `ref` and other `<div>` props go to `.kpt-update`; `hostProps` to the wrapper
39
+
40
+ ## Example
41
+ <KptUpdate name="Kompas" status={status} loaded={loaded} total={total} onAction={handle}>
42
+ <img src="/kompas.svg" alt="" />
43
+ </KptUpdate>
44
+
45
+ ## Tokens
46
+ The tile: a gradient from the `color` role to `--kpt-color-success`, the icon in
47
+ `--kpt-color-<role>-contrast`, `--kpt-shadow-sm`. Text: `--kpt-font-weight-semibold`,
48
+ `--kpt-font-size-sm`, `--kpt-color-text-muted`.
49
+
50
+ ## Accessibility
51
+ A visually hidden `role="status"` region holds "name: state" and announces state changes, not
52
+ bytes (the line is `aria-hidden`). While downloading with a known total the ring is a
53
+ `role="progressbar"`. During the cross-fade the leaving label is `aria-hidden`; with motion off
54
+ texts change at once.