@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,20 +1,20 @@
1
1
  # KptScrollTop (@konce-pt/react)
2
2
 
3
- A "back to top" button — appears once the scroll passes `threshold` px.
3
+ A "back to top" button - appears once the scroll passes `threshold` px.
4
4
  Import: `import { KptScrollTop } from '@konce-pt/react';`
5
5
 
6
6
  ## DOM
7
7
  <div class="kpt-scroll-top-host">
8
- <button class="kpt-scroll-top is-visible" tabindex="0" aria-label="Scroll to top">…</button>
8
+ <button class="kpt-scroll-top is-visible" tabindex="0" aria-label="Scroll to top">...</button>
9
9
  </div>
10
10
 
11
11
  ## Props
12
12
  - `threshold`: number (px, default 300)
13
- - `target`: string | null — a selector for the scrolled element; the window by default
13
+ - `target`: string | null - a selector for the scrolled element; the window by default
14
14
  - `className`, `ref` and every other `<div>` prop go to the host
15
15
 
16
16
  ## `target` is not optional in a shell
17
- Whenever the app scrolls a container rather than the page — an `KptAppShell` layout, for instance —
17
+ Whenever the app scrolls a container rather than the page - an `KptAppShell` layout, for instance -
18
18
  the window never scrolls and the button would never appear. Point it at the container:
19
19
  `target=".kpt-app-shell__content"`.
20
20
 
@@ -24,7 +24,7 @@ component, and on the server it does not exist at all. The listener is removed o
24
24
  position is read once on mount, because the page may already be scrolled (arriving at an anchor).
25
25
 
26
26
  ## The hidden button stays in the DOM
27
- It fades in and out, so hiding it is `tabindex="-1"` plus the CSS class, not a conditional render —
27
+ It fades in and out, so hiding it is `tabindex="-1"` plus the CSS class, not a conditional render -
28
28
  otherwise the animation would have nothing to run on.
29
29
 
30
30
  ## Examples
@@ -42,4 +42,4 @@ A real `<button>` labelled from `scrollTop.label`; while hidden it is `visibilit
42
42
  out of the tab order and the accessibility tree. It sits in the bottom-end corner
43
43
  (`inset-inline-end`), so it moves to the left in RTL. With `prefers-reduced-motion` the scroll is a
44
44
  jump instead of smooth and the button appears without a transition. Focus stays on the button after
45
- the jump — move it to your page heading if that matters.
45
+ the jump - move it to your page heading if that matters.
@@ -7,7 +7,7 @@ export interface KptScrollTopProps extends ComponentPropsWithRef<'div'>, KptI18n
7
7
  target?: string | null;
8
8
  }
9
9
  /**
10
- * Przycisk „do góry" — pojawia się po przewinięciu ponad `threshold` px.
10
+ * Przycisk "do góry" - pojawia się po przewinięciu ponad `threshold` px.
11
11
  *
12
12
  * @example
13
13
  * <KptScrollTop threshold={300} />
@@ -1,10 +1,11 @@
1
+ 'use client';
1
2
  import { jsx as _jsx } from "react/jsx-runtime";
2
3
  import { useEffect, useState } from 'react';
3
4
  import { cn } from "../internal/cn.js";
4
5
  import { useKptMessages } from "../i18n/i18n-context.js";
5
6
  import { KptIcon } from "../icon/icon.js";
6
7
  /**
7
- * Przycisk „do góry" — pojawia się po przewinięciu ponad `threshold` px.
8
+ * Przycisk "do góry" - pojawia się po przewinięciu ponad `threshold` px.
8
9
  *
9
10
  * @example
10
11
  * <KptScrollTop threshold={300} />
@@ -6,21 +6,21 @@ A select on its own panel. Variants: single, with a search box (`filter`) and mu
6
6
  ## DOM
7
7
  <span class="kpt-select-host">
8
8
  <div class="kpt-select" role="combobox" tabindex="0" aria-expanded="false">
9
- <div class="kpt-select__value"><span class="kpt-select__placeholder">Choose…</span></div>
10
- <span class="kpt-icon-host kpt-select__chevron">…</span>
9
+ <div class="kpt-select__value"><span class="kpt-select__placeholder">Choose...</span></div>
10
+ <span class="kpt-icon-host kpt-select__chevron">...</span>
11
11
  </div>
12
12
  </span>
13
13
  <!-- while open, in a portal on document.body: -->
14
- <div class="kpt-select__panel"><ul class="kpt-select__list" role="listbox">…</ul></div>
14
+ <div class="kpt-select__panel"><ul class="kpt-select__list" role="listbox">...</ul></div>
15
15
 
16
16
  ## Props
17
- - `options`: `KptOption<T>[]` — `{ label, value, disabled? }`
18
- - `value` / `defaultValue` / `onValueChange` — a single `T`, or `T[]` in `multiple` mode
19
- - `open` / `defaultOpen` / `onOpenChange` — the panel state, so the list can be opened from outside
17
+ - `options`: `KptOption<T>[]` - `{ label, value, disabled? }`
18
+ - `value` / `defaultValue` / `onValueChange` - a single `T`, or `T[]` in `multiple` mode
19
+ - `open` / `defaultOpen` / `onOpenChange` - the panel state, so the list can be opened from outside
20
20
  - `placeholder`, `multiple`, `filter`, `disabled`, `invalid`, `touched`
21
- - `onTouch()` — fires when the panel closes or focus leaves the closed trigger
21
+ - `onTouch()` - fires when the panel closes or focus leaves the closed trigger
22
22
  - `aria-label` (through the rest props) names the trigger when no `<label for>` describes it
23
- - `locale`, `dictionary` — i18n overrides for this component only
23
+ - `locale`, `dictionary` - i18n overrides for this component only
24
24
  - `className`, `ref` and every other `<div>` prop go to the trigger
25
25
 
26
26
  ## The panel is a portal
@@ -35,20 +35,20 @@ through `aria-activedescendant` (with `filter`, focus moves to the search field)
35
35
  ArrowUp, Enter, Space, Home and End open the list, typing letters opens it on the first match, and
36
36
  in `multiple` mode Backspace/Delete removes the last chip. Open: ArrowUp/ArrowDown move by one,
37
37
  Home/End to the ends, PageUp/PageDown by ten, skipping disabled options without wrapping; Enter (and
38
- Space on the trigger) picks — a single select closes and returns focus to the trigger; typing jumps
38
+ Space on the trigger) picks - a single select closes and returns focus to the trigger; typing jumps
39
39
  to the next match, diacritics ignored; Escape and Tab close. Hovering an option makes it active, so
40
40
  the mouse and the keyboard share one highlight, and clicking it does not take focus from the trigger.
41
41
 
42
42
  ## Inside KptFormField
43
43
  There is no native field for the wrapper to read, so the select reports its filled state with
44
- `useKptFormFieldFilled` — the float label then floats over the chosen value.
44
+ `useKptFormFieldFilled` - the float label then floats over the chosen value.
45
45
 
46
46
  ## i18n
47
47
  `select.search` (the search placeholder), `select.noResults` (the empty list),
48
48
  `select.removeChip` (the chip's remove button).
49
49
 
50
50
  ## Examples
51
- <KptSelect options={roles} value={role} onValueChange={setRole} placeholder="Choose…" />
51
+ <KptSelect options={roles} value={role} onValueChange={setRole} placeholder="Choose..." />
52
52
  <KptSelect options={roles} value={role} onValueChange={setRole} filter />
53
53
  <KptSelect options={tags} value={tags} onValueChange={setTags} multiple filter />
54
54
 
@@ -56,9 +56,16 @@ There is no native field for the wrapper to read, so the select reports its fill
56
56
  `id` goes to the focusable element, not the host; without one it comes from `KptFormField` or is
57
57
  generated. A `div` with a role is not named by `label[for]`, so inside `KptFormField` it gets
58
58
  `aria-labelledby` pointing at the wrapper's label (unless `aria-label` is set), and the hint or
59
- error is added to `aria-describedby`. New: `id` and `required` (→ `aria-required`); the list gets
59
+ error is added to `aria-describedby`. New: `id` and `required` (-> `aria-required`); the list gets
60
60
  the same name as the trigger.
61
61
 
62
+ ## Motion
63
+ With `multiple`, a chip of a newly picked option pops in at its place (chips follow the order of
64
+ `options`), a removed one - by its "x", by unpicking it in the list or by `Backspace` - fades and shrinks in
65
+ place while the rest slide over on a spring. Several changes at once (e.g. a new value set from code) go
66
+ without motion. Follows `enter` from `KptMotionProvider` (`spring` / `fade` / `none`); `enabled: false` and the
67
+ system "reduce motion" setting always win.
68
+
62
69
  ## Tokens
63
70
  `--kpt-form-field-{bg,text,placeholder,border,border-hover,border-focus,border-error,radius}`,
64
71
  `--kpt-color-surface-raised` and `--kpt-elevation-3` for the panel, `--kpt-z-overlay` for the layer.
@@ -68,3 +75,6 @@ The trigger is `role="combobox"` with `aria-haspopup="listbox"`, `aria-expanded`
68
75
  and `aria-activedescendant`; the list is `role="listbox"` with `aria-multiselectable`, and each
69
76
  option is `role="option"` with `aria-selected` and `aria-disabled`. Chip remove buttons sit outside
70
77
  the Tab order and are named with the option label.
78
+
79
+ ## Read-only and required
80
+ `readOnly` shows the choice, but the panel does not open and the chips have no remove button (`aria-readonly`). The filter ignores case and diacritics ("lodz" finds "Łódź"), as in `listbox`. "No results" sits next to the list, not inside it (a listbox may contain only options).
@@ -4,22 +4,24 @@ import type { KptOption } from '../shared/types.ts';
4
4
  export interface KptSelectProps<T> extends Omit<ComponentPropsWithRef<'div'>, 'children' | 'onChange' | 'defaultValue'>, KptI18nOverrides {
5
5
  options?: readonly KptOption<T>[];
6
6
  placeholder?: string;
7
- /** Wybór wielokrotny — wartością jest tablica, a wybrane opcje pokazują się jako chipy. */
7
+ /** Wybór wielokrotny - wartością jest tablica, a wybrane opcje pokazują się jako chipy. */
8
8
  multiple?: boolean;
9
9
  /** Pole wyszukiwania nad listą. */
10
10
  filter?: boolean;
11
- /** Id wyzwalacza; bez niego generowane, w `KptFormField` — z wrappera. */
11
+ /** Id wyzwalacza; bez niego generowane, w `KptFormField` - z wrappera. */
12
12
  id?: string;
13
13
  /** Oznacza pole jako wymagane atrybutem `aria-required`. */
14
14
  required?: boolean;
15
15
  disabled?: boolean;
16
+ /** Tylko do odczytu - wybór widać, panel się nie otwiera (`aria-readonly`). */
17
+ readOnly?: boolean;
16
18
  invalid?: boolean;
17
19
  touched?: boolean;
18
20
  /** Wartość: pojedyncza (T) albo tablica (T[]) w trybie `multiple`. */
19
21
  value?: T | T[] | null;
20
22
  defaultValue?: T | T[] | null;
21
23
  onValueChange?: (value: T | T[] | null) => void;
22
- /** Stan panelu — pozwala otworzyć listę z zewnątrz i wiedzieć, kiedy user skończył wybierać. */
24
+ /** Stan panelu - pozwala otworzyć listę z zewnątrz i wiedzieć, kiedy user skończył wybierać. */
23
25
  open?: boolean;
24
26
  defaultOpen?: boolean;
25
27
  onOpenChange?: (open: boolean) => void;
@@ -30,19 +32,19 @@ export interface KptSelectProps<T> extends Omit<ComponentPropsWithRef<'div'>, 'c
30
32
  * Lista wyboru (select) na własnym panelu. Warianty: pojedynczy, z wyszukiwarką (`filter`)
31
33
  * i wielokrotny (`multiple`, chipy).
32
34
  *
33
- * Klawiatura według wzorca WAI-ARIA „select-only combobox”: fokus zostaje na polu, a aktywna opcja
34
- * idzie przez `aria-activedescendant`. `↓`/`↑`/`Enter`/`Spacja` otwierają listę na wybranej opcji,
35
+ * Klawiatura według wzorca WAI-ARIA "select-only combobox": fokus zostaje na polu, a aktywna opcja
36
+ * idzie przez `aria-activedescendant`. `ArrowDown`/`ArrowUp`/`Enter`/`Spacja` otwierają listę na wybranej opcji,
35
37
  * strzałki, `Home`/`End`, `PageUp`/`PageDown` chodzą po opcjach (z pominięciem wyłączonych),
36
38
  * wpisywane litery skaczą do pasującej opcji (bez polskich znaków), `Enter` wybiera, `Escape`
37
39
  * zamyka. Z `filter` fokus przechodzi do pola wyszukiwania, które obsługuje te same klawisze.
38
40
  * W trybie `multiple` `Backspace` na zamkniętym polu usuwa ostatni wybór.
39
41
  *
40
42
  * Wewnątrz `KptFormField` zgłasza wypełnienie hookiem `useKptFormFieldFilled`, więc etykieta
41
- * Float Label unosi się nad wybraną wartością — natywnego pola, z którego wrapper mógłby to
43
+ * Float Label unosi się nad wybraną wartością - natywnego pola, z którego wrapper mógłby to
42
44
  * odczytać, tu nie ma.
43
45
  *
44
46
  * @example
45
- * <KptSelect options={roles} value={role} onValueChange={setRole} placeholder="Wybierz…" />
47
+ * <KptSelect options={roles} value={role} onValueChange={setRole} placeholder="Wybierz..." />
46
48
  * <KptSelect options={tags} multiple filter value={tags} onValueChange={setTags} />
47
49
  */
48
- export declare function KptSelect<T>({ options, placeholder, multiple, filter, disabled, invalid, touched, value, defaultValue, onValueChange, open, defaultOpen, onOpenChange, onTouch, locale, dictionary, className, onBlur, id, required, 'aria-label': ariaLabel, 'aria-describedby': ariaDescribedBy, ...rest }: Readonly<KptSelectProps<T>>): import("react").JSX.Element;
50
+ export declare function KptSelect<T>({ options, placeholder, multiple, filter, disabled, readOnly, invalid, touched, value, defaultValue, onValueChange, open, defaultOpen, onOpenChange, onTouch, locale, dictionary, className, onBlur, id, required, 'aria-label': ariaLabel, 'aria-describedby': ariaDescribedBy, ...rest }: Readonly<KptSelectProps<T>>): import("react").JSX.Element;
@@ -1,6 +1,11 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
3
+ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react';
4
+ import { foldMentionText } from '@konce-pt/mention';
5
+ import { flowChangeOf, prepareFlowMotion, resolveEnterMotion } from '@konce-pt/motion';
6
+ import { useKptMotion } from "../motion/motion-context.js";
3
7
  import { cn } from "../internal/cn.js";
8
+ import { optionKey } from "../internal/option-key.js";
4
9
  import { KptOverlay } from "../internal/overlay.js";
5
10
  import { useControllableState } from "../internal/use-controllable-state.js";
6
11
  import { useKptId } from "../internal/use-id.js";
@@ -8,30 +13,37 @@ import { KptTypeahead, OPTION_NAV_KEYS, isTypeaheadKey, nextOptionIndex, typeahe
8
13
  import { useKptMessages } from "../i18n/i18n-context.js";
9
14
  import { joinIds, useKptFieldA11y, useKptFormFieldFilled } from "../form-field/form-field-context.js";
10
15
  import { KptIcon } from "../icon/icon.js";
16
+ /** Filtr opcji bez wielkości liter i diakrytyków, jak w `KptListbox`: "lodz" znajduje "Łódź". */
17
+ function filterSelectOptions(options, text) {
18
+ const q = foldMentionText(text.trim());
19
+ return q ? options.filter((option) => foldMentionText(option.label).includes(q)) : options;
20
+ }
11
21
  /**
12
22
  * Lista wyboru (select) na własnym panelu. Warianty: pojedynczy, z wyszukiwarką (`filter`)
13
23
  * i wielokrotny (`multiple`, chipy).
14
24
  *
15
- * Klawiatura według wzorca WAI-ARIA „select-only combobox”: fokus zostaje na polu, a aktywna opcja
16
- * idzie przez `aria-activedescendant`. `↓`/`↑`/`Enter`/`Spacja` otwierają listę na wybranej opcji,
25
+ * Klawiatura według wzorca WAI-ARIA "select-only combobox": fokus zostaje na polu, a aktywna opcja
26
+ * idzie przez `aria-activedescendant`. `ArrowDown`/`ArrowUp`/`Enter`/`Spacja` otwierają listę na wybranej opcji,
17
27
  * strzałki, `Home`/`End`, `PageUp`/`PageDown` chodzą po opcjach (z pominięciem wyłączonych),
18
28
  * wpisywane litery skaczą do pasującej opcji (bez polskich znaków), `Enter` wybiera, `Escape`
19
29
  * zamyka. Z `filter` fokus przechodzi do pola wyszukiwania, które obsługuje te same klawisze.
20
30
  * W trybie `multiple` `Backspace` na zamkniętym polu usuwa ostatni wybór.
21
31
  *
22
32
  * Wewnątrz `KptFormField` zgłasza wypełnienie hookiem `useKptFormFieldFilled`, więc etykieta
23
- * Float Label unosi się nad wybraną wartością — natywnego pola, z którego wrapper mógłby to
33
+ * Float Label unosi się nad wybraną wartością - natywnego pola, z którego wrapper mógłby to
24
34
  * odczytać, tu nie ma.
25
35
  *
26
36
  * @example
27
- * <KptSelect options={roles} value={role} onValueChange={setRole} placeholder="Wybierz…" />
37
+ * <KptSelect options={roles} value={role} onValueChange={setRole} placeholder="Wybierz..." />
28
38
  * <KptSelect options={tags} multiple filter value={tags} onValueChange={setTags} />
29
39
  */
30
- export function KptSelect({ options = [], placeholder = '', multiple = false, filter = false, disabled = false, invalid = false, touched, value, defaultValue = null, onValueChange, open, defaultOpen = false, onOpenChange, onTouch, locale, dictionary, className, onBlur, id, required = false, 'aria-label': ariaLabel, 'aria-describedby': ariaDescribedBy, ...rest }) {
31
- /** `div` z rolą nie dostaje nazwy z `label[for]` — stąd `aria-labelledby` na etykietę wrappera. */
40
+ export function KptSelect({ options = [], placeholder = '', multiple = false, filter = false, disabled = false, readOnly = false, invalid = false, touched, value, defaultValue = null, onValueChange, open, defaultOpen = false, onOpenChange, onTouch, locale, dictionary, className, onBlur, id, required = false, 'aria-label': ariaLabel, 'aria-describedby': ariaDescribedBy, ...rest }) {
41
+ /** `div` z rolą nie dostaje nazwy z `label[for]` - stąd `aria-labelledby` na etykietę wrappera. */
32
42
  const field = useKptFieldA11y(id, 'kpt-select-trigger');
33
43
  const { t } = useKptMessages({ locale, dictionary });
34
44
  const triggerRef = useRef(null);
45
+ /** Kotwica nakładki w stanie (callback ref), nie `triggerRef.current` czytane w renderze. */
46
+ const [anchor, setAnchor] = useState(null);
35
47
  const searchRef = useRef(null);
36
48
  const typeahead = useRef(new KptTypeahead());
37
49
  const uid = useKptId(undefined, 'kpt-select');
@@ -52,35 +64,41 @@ export function KptSelect({ options = [], placeholder = '', multiple = false, fi
52
64
  const showInvalid = invalid && (touched ?? true);
53
65
  const selectedValues = useMemo(() => {
54
66
  if (multiple)
55
- return Array.isArray(current) ? current : [];
67
+ return Array.isArray(current) ? (current) : [];
56
68
  return current == null ? [] : [current];
57
69
  }, [current, multiple]);
58
70
  const selectedOptions = useMemo(() => options.filter((option) => selectedValues.includes(option.value)), [options, selectedValues]);
59
- const filterOptions = (text) => {
60
- const q = text.trim().toLowerCase();
61
- return q ? options.filter((option) => option.label.toLowerCase().includes(q)) : options;
62
- };
63
- const filteredOptions = useMemo(() => filterOptions(filterText), [options, filterText]); // eslint-disable-line react-hooks/exhaustive-deps
71
+ const filteredOptions = useMemo(() => filterSelectOptions(options, filterText), [options, filterText]);
64
72
  const disabledMap = filteredOptions.map((option) => !!option.disabled);
65
73
  const filled = selectedOptions.length > 0;
66
74
  useKptFormFieldFilled(filled);
67
75
  const isSelected = (option) => selectedValues.includes(option.value);
68
- // Panel może zostać otwarty także z zewnątrz (`open`), nie tylko z pola — filtr i aktywną
69
- // pozycję ustawiamy więc przy każdym otwarciu. Lista otwiera się na wybranej opcji; z `filter`
70
- // fokus idzie do wyszukiwarki (klatkę później: `KptOverlay` mierzy panel w `visibility: hidden`).
71
- // Tylko na otwarcie — zmiana wyboru w trybie `multiple` nie może przesuwać aktywnej opcji.
76
+ /** Skrajna opcja, na której ma się otworzyć lista (`Home`/`End`/`ArrowUp` na zamkniętym polu). */
77
+ const [openEdge, setOpenEdge] = useState(null);
78
+ // Panel może zostać otwarty także z zewnątrz (`open`), nie tylko z pola - filtr i aktywną
79
+ // pozycję ustawiamy więc przy każdym przejściu w stan otwarty, wzorcem "poprzednia wartość
80
+ // w stanie" (w tym samym renderze, bez efektu). Lista otwiera się na wybranej opcji; zmiana
81
+ // wyboru w trybie `multiple` przy otwartym panelu nie przesuwa aktywnej opcji.
82
+ const [wasOpen, setWasOpen] = useState(isOpen);
83
+ if (wasOpen !== isOpen) {
84
+ setWasOpen(isOpen);
85
+ if (isOpen) {
86
+ setFilterText('');
87
+ const disabledAll = options.map((o) => !!o.disabled);
88
+ const selected = options.findIndex((option) => selectedValues.includes(option.value));
89
+ const firstEnabled = nextOptionIndex(disabledAll, -1, 'ArrowDown') ?? -1;
90
+ setActiveIndex(openEdge ? (nextOptionIndex(disabledAll, -1, openEdge) ?? -1) : selected >= 0 ? selected : firstEnabled);
91
+ setOpenEdge(null);
92
+ }
93
+ }
94
+ // Z `filter` fokus idzie do wyszukiwarki - klatkę później, bo `KptOverlay` mierzy panel
95
+ // w `visibility: hidden`.
72
96
  useEffect(() => {
73
- if (!isOpen)
74
- return;
75
- setFilterText('');
76
- const selected = options.findIndex((option) => selectedValues.includes(option.value));
77
- const firstEnabled = nextOptionIndex(options.map((o) => !!o.disabled), -1, 'ArrowDown') ?? -1;
78
- setActiveIndex(selected >= 0 ? selected : firstEnabled);
79
- if (!filter)
97
+ if (!isOpen || !filter)
80
98
  return;
81
99
  const frame = requestAnimationFrame(() => searchRef.current?.focus());
82
100
  return () => cancelAnimationFrame(frame);
83
- }, [isOpen]); // eslint-disable-line react-hooks/exhaustive-deps
101
+ }, [isOpen, filter]);
84
102
  // Aktywna opcja zawsze w polu widzenia listy.
85
103
  useEffect(() => {
86
104
  if (isOpen && activeIndex >= 0)
@@ -92,16 +110,40 @@ export function KptSelect({ options = [], placeholder = '', multiple = false, fi
92
110
  setOpen(false);
93
111
  onTouch?.();
94
112
  }, [isOpen, setOpen, onTouch]);
95
- /** Zamknięcie z powrotem fokusu na pole — po wyborze, `Escape`, `Tab` w wyszukiwarce. */
113
+ /** Zamknięcie z powrotem fokusu na pole - po wyborze, `Escape`, `Tab` w wyszukiwarce. */
96
114
  const closeAndFocus = () => {
97
115
  close();
98
116
  triggerRef.current?.focus();
99
117
  };
118
+ const enterMotion = resolveEnterMotion(undefined, useKptMotion());
119
+ const valueRef = useRef(null);
120
+ /** Ruch chipów przygotowany przed zmianą wyboru, odtwarzany po jej wyrenderowaniu. */
121
+ const pendingFlow = useRef(null);
122
+ const chipElements = () => Array.from(valueRef.current?.querySelectorAll('.kpt-select__chip') ?? []);
123
+ // Przed malowaniem - inaczej mignęłaby klatka z nowym układem, zanim FLIP cofnie chipy.
124
+ useLayoutEffect(() => {
125
+ const play = pendingFlow.current;
126
+ pendingFlow.current = null;
127
+ play?.(chipElements());
128
+ });
129
+ /**
130
+ * Nowy wybór w trybie `multiple` z ruchem chipów: dodany wskakuje, usunięty gaśnie, reszta się
131
+ * dosuwa (`prepareFlowMotion`, za `enter` z `KptMotionProvider`). Chipy stoją w kolejności opcji,
132
+ * więc pozycję zmiany liczymy z list przed i po; kilka zmian naraz idzie bez ruchu.
133
+ */
134
+ const setSelected = (next) => {
135
+ const box = valueRef.current;
136
+ const order = (values) => options.filter((o) => values.includes(o.value)).map((o) => o.value);
137
+ const change = flowChangeOf(order(selectedValues), order(next));
138
+ if (enterMotion !== 'none' && box && change)
139
+ pendingFlow.current = prepareFlowMotion(box, chipElements(), change, enterMotion);
140
+ setCurrent(next);
141
+ };
100
142
  const pick = (option) => {
101
143
  if (option.disabled)
102
144
  return;
103
145
  if (multiple) {
104
- setCurrent(isSelected(option) ? selectedValues.filter((v) => v !== option.value) : [...selectedValues, option.value]);
146
+ setSelected(isSelected(option) ? selectedValues.filter((v) => v !== option.value) : [...selectedValues, option.value]);
105
147
  }
106
148
  else {
107
149
  setCurrent(option.value);
@@ -114,27 +156,22 @@ export function KptSelect({ options = [], placeholder = '', multiple = false, fi
114
156
  if (found !== null)
115
157
  setActiveIndex(found);
116
158
  };
117
- /** Jeden obsługiwacz dla pola i dla wyszukiwarki w panelu — różnią się tylko `Spacją` i `Tab`. */
159
+ /** Jeden obsługiwacz dla pola i dla wyszukiwarki w panelu - różnią się tylko `Spacją` i `Tab`. */
118
160
  const onKeyDown = (event, source) => {
119
- if (disabled)
161
+ if (disabled || readOnly)
120
162
  return;
121
163
  const key = event.key;
122
164
  if (!isOpen) {
123
165
  if (['ArrowDown', 'ArrowUp', 'Enter', ' ', 'Home', 'End'].includes(key)) {
124
166
  event.preventDefault();
167
+ // Skrajne klawisze otwierają listę na pierwszej albo ostatniej opcji zamiast na wybranej.
168
+ if (key === 'ArrowUp' || key === 'End' || key === 'Home')
169
+ setOpenEdge(key === 'Home' ? 'Home' : 'End');
125
170
  setOpen(true);
126
- if (key === 'ArrowUp' || key === 'End' || key === 'Home') {
127
- // Efekt otwarcia ustawia wybraną opcję; skrajne klawisze nadpisują to klatkę później.
128
- const target = nextOptionIndex(disabledMap, -1, key === 'Home' ? 'Home' : 'End');
129
- requestAnimationFrame(() => {
130
- if (target !== null)
131
- setActiveIndex(target);
132
- });
133
- }
134
171
  }
135
172
  else if ((key === 'Backspace' || key === 'Delete') && multiple && selectedValues.length) {
136
173
  event.preventDefault();
137
- setCurrent(selectedValues.slice(0, -1));
174
+ setSelected(selectedValues.slice(0, -1));
138
175
  }
139
176
  else if (isTypeaheadKey(event.nativeEvent) && !filter) {
140
177
  setOpen(true);
@@ -147,7 +184,7 @@ export function KptSelect({ options = [], placeholder = '', multiple = false, fi
147
184
  closeAndFocus();
148
185
  }
149
186
  else if (key === 'Tab') {
150
- // Z wyszukiwarki (w portalu na końcu strony) Tab uciekłby w próżnię — wracamy na pole.
187
+ // Z wyszukiwarki (w portalu na końcu strony) Tab uciekłby w próżnię - wracamy na pole.
151
188
  if (source === 'search') {
152
189
  event.preventDefault();
153
190
  closeAndFocus();
@@ -172,18 +209,21 @@ export function KptSelect({ options = [], placeholder = '', multiple = false, fi
172
209
  jumpTo(key);
173
210
  }
174
211
  };
175
- return (_jsxs("span", { className: "kpt-select-host", children: [_jsxs("div", { ref: triggerRef, className: cn('kpt-select', isOpen && 'kpt-select--open', disabled && 'kpt-select--disabled', showInvalid && 'kpt-select--invalid', className), role: "combobox", id: field.id, tabIndex: 0, "aria-haspopup": "listbox", "aria-label": ariaLabel, "aria-labelledby": ariaLabel ? undefined : field.labelledBy, "aria-describedby": joinIds(ariaDescribedBy, field.describedBy), "aria-required": required || undefined, "aria-expanded": isOpen, "aria-controls": isOpen ? listId : undefined, "aria-activedescendant": isOpen && !filter && activeIndex >= 0 ? optionId(activeIndex) : undefined, "aria-disabled": disabled || undefined, "aria-invalid": showInvalid || undefined, onClick: () => !disabled && setOpen(!isOpen), onKeyDown: (event) => onKeyDown(event, 'trigger'), onBlur: (event) => {
212
+ return (_jsxs("span", { className: "kpt-select-host", children: [_jsxs("div", { ref: (el) => {
213
+ triggerRef.current = el;
214
+ setAnchor(el);
215
+ }, className: cn('kpt-select', isOpen && 'kpt-select--open', disabled && 'kpt-select--disabled', readOnly && 'kpt-select--readonly', showInvalid && 'kpt-select--invalid', className), role: "combobox", id: field.id, tabIndex: 0, "aria-haspopup": "listbox", "aria-label": ariaLabel, "aria-labelledby": ariaLabel ? undefined : field.labelledBy, "aria-describedby": joinIds(ariaDescribedBy, field.describedBy), "aria-required": required || undefined, "aria-readonly": readOnly || undefined, "aria-expanded": isOpen, "aria-controls": isOpen ? listId : undefined, "aria-activedescendant": isOpen && !filter && activeIndex >= 0 ? optionId(activeIndex) : undefined, "aria-disabled": disabled || undefined, "aria-invalid": showInvalid || undefined, onClick: () => !disabled && !readOnly && setOpen(!isOpen), onKeyDown: (event) => onKeyDown(event, 'trigger'), onBlur: (event) => {
176
216
  onBlur?.(event);
177
- // Przy otwartym panelu fokus idzie do pola szukania — „dotknięcie” wyśle dopiero zamknięcie.
217
+ // Przy otwartym panelu fokus idzie do pola szukania - "dotknięcie" wyśle dopiero zamknięcie.
178
218
  if (!isOpen)
179
219
  onTouch?.();
180
- }, ...rest, children: [_jsx("div", { className: "kpt-select__value", children: multiple && selectedOptions.length ? (selectedOptions.map((option) => (_jsxs("span", { className: "kpt-select__chip", children: [option.label, _jsx("button", { type: "button", className: "kpt-select__chip-remove", tabIndex: -1, "aria-label": `${t('select.removeChip')}: ${option.label}`, onClick: (event) => {
220
+ }, ...rest, children: [_jsx("div", { className: "kpt-select__value", ref: valueRef, children: multiple && selectedOptions.length ? (selectedOptions.map((option, index) => (_jsxs("span", { className: "kpt-select__chip", children: [option.label, readOnly ? null : (_jsx("button", { type: "button", className: "kpt-select__chip-remove", tabIndex: -1, "aria-label": `${t('select.removeChip')}: ${option.label}`, onClick: (event) => {
181
221
  event.stopPropagation();
182
- setCurrent(selectedValues.filter((v) => v !== option.value));
183
- }, children: _jsx(KptIcon, { name: "x" }) })] }, String(option.value))))) : !multiple && filled ? (_jsx("span", { className: "kpt-select__single", children: selectedOptions[0]?.label })) : (_jsx("span", { className: "kpt-select__placeholder", children: placeholder })) }), _jsx(KptIcon, { className: "kpt-select__chevron", name: "chevron-down" })] }), _jsx(KptOverlay, { open: isOpen, anchor: triggerRef.current, onClose: close, minAnchorWidth: true, children: _jsxs("div", { className: "kpt-select__panel", children: [filter ? (_jsxs("div", { className: "kpt-select__search", children: [_jsx(KptIcon, { name: "search" }), _jsx("input", { ref: searchRef, type: "text", className: "kpt-select__search-input", role: "combobox", "aria-autocomplete": "list", "aria-expanded": true, "aria-controls": listId, "aria-activedescendant": activeIndex >= 0 ? optionId(activeIndex) : undefined, "aria-label": t('select.search'), value: filterText, placeholder: t('select.search'), onKeyDown: (event) => onKeyDown(event, 'search'), onChange: (event) => {
222
+ setSelected(selectedValues.filter((v) => v !== option.value));
223
+ }, children: _jsx(KptIcon, { name: "x" }) }))] }, optionKey(option.value, index))))) : !multiple && filled ? (_jsx("span", { className: "kpt-select__single", children: selectedOptions[0]?.label })) : (_jsx("span", { className: "kpt-select__placeholder", children: placeholder })) }), _jsx(KptIcon, { className: "kpt-select__chevron", name: "chevron-down" })] }), _jsx(KptOverlay, { open: isOpen, anchor: anchor, onClose: close, minAnchorWidth: true, children: _jsxs("div", { className: "kpt-select__panel", children: [filter ? (_jsxs("div", { className: "kpt-select__search", children: [_jsx(KptIcon, { name: "search" }), _jsx("input", { ref: searchRef, type: "text", className: "kpt-select__search-input", role: "combobox", "aria-autocomplete": "list", "aria-expanded": true, "aria-controls": listId, "aria-activedescendant": activeIndex >= 0 ? optionId(activeIndex) : undefined, "aria-label": t('select.search'), value: filterText, placeholder: t('select.search'), onKeyDown: (event) => onKeyDown(event, 'search'), onChange: (event) => {
184
224
  setFilterText(event.target.value);
185
- // Po zawężeniu aktywna jest pierwsza pasująca opcja — `Enter` wybiera ją od razu.
186
- const narrowed = filterOptions(event.target.value).map((o) => !!o.disabled);
225
+ // Po zawężeniu aktywna jest pierwsza pasująca opcja - `Enter` wybiera ją od razu.
226
+ const narrowed = filterSelectOptions(options, event.target.value).map((o) => !!o.disabled);
187
227
  setActiveIndex(nextOptionIndex(narrowed, -1, 'ArrowDown') ?? -1);
188
- } })] })) : null, _jsx("ul", { className: "kpt-select__list", role: "listbox", id: listId, "aria-multiselectable": multiple, "aria-label": ariaLabel, "aria-labelledby": ariaLabel ? undefined : field.labelledBy, children: filteredOptions.length ? (filteredOptions.map((option, index) => (_jsxs("li", { id: optionId(index), className: cn('kpt-select__option', isSelected(option) && 'kpt-select__option--selected', index === activeIndex && 'kpt-select__option--active', option.disabled && 'kpt-select__option--disabled'), role: "option", "aria-selected": isSelected(option), "aria-disabled": option.disabled || undefined, onMouseDown: (event) => event.preventDefault(), onClick: () => pick(option), onMouseEnter: () => setActiveIndex(index), children: [multiple ? (_jsx("span", { className: cn('kpt-select__check', isSelected(option) && 'is-on'), children: _jsx(KptIcon, { name: "check" }) })) : null, _jsx("span", { className: "kpt-select__option-label", children: option.label }), !multiple && isSelected(option) ? (_jsx(KptIcon, { className: "kpt-select__option-tick", name: "check" })) : null] }, String(option.value))))) : (_jsx("li", { className: "kpt-select__empty", role: "status", children: t('select.noResults') })) })] }) })] }));
228
+ } })] })) : null, _jsx("ul", { className: "kpt-select__list", role: "listbox", id: listId, "aria-multiselectable": multiple, "aria-label": ariaLabel, "aria-labelledby": ariaLabel ? undefined : field.labelledBy, children: filteredOptions.map((option, index) => (_jsxs("li", { id: optionId(index), className: cn('kpt-select__option', isSelected(option) && 'kpt-select__option--selected', index === activeIndex && 'kpt-select__option--active', option.disabled && 'kpt-select__option--disabled'), role: "option", "aria-selected": isSelected(option), "aria-disabled": option.disabled || undefined, onMouseDown: (event) => event.preventDefault(), onClick: () => pick(option), onMouseEnter: () => setActiveIndex(index), children: [multiple ? (_jsx("span", { className: cn('kpt-select__check', isSelected(option) && 'is-on'), children: _jsx(KptIcon, { name: "check" }) })) : null, _jsx("span", { className: "kpt-select__option-label", children: option.label }), !multiple && isSelected(option) ? (_jsx(KptIcon, { className: "kpt-select__option-tick", name: "check" })) : null] }, optionKey(option.value, index)))) }), filteredOptions.length === 0 ? (_jsx("div", { className: "kpt-select__empty", role: "status", children: t('select.noResults') })) : null] }) })] }));
189
229
  }
@@ -6,5 +6,26 @@ export interface KptOption<T = unknown> {
6
6
  /** Nazwa ikony z rejestru. Renderuje ją dziś wyłącznie `kpt-button-group`. */
7
7
  icon?: string;
8
8
  }
9
- /** Wariant komunikatu — wspólny dla alertu i toastów. */
9
+ /** Wariant komunikatu - wspólny dla alertu i toastów. */
10
10
  export type KptAlertVariant = 'info' | 'success' | 'warning' | 'danger';
11
+ /**
12
+ * Klik w odnośnik komponentu nawigacji (`kpt-breadcrumb`, `kpt-menubar`, `kpt-megamenu`). Linki
13
+ * są zwykłym `<a href>`, więc bez routera klik przeładowuje stronę. Aplikacja z routerem łapie to
14
+ * zdarzenie, woła `event.preventDefault()` i nawiguje sama - biblioteka nie zależy od żadnego routera.
15
+ * Zdarzenie leci tylko dla zwykłego kliku: Ctrl/Cmd/Shift/środkowy przycisk i `target="_blank"`
16
+ * zostają natywne (nowa karta, nowe okno).
17
+ */
18
+ export interface KptNavigateEvent<T = unknown> {
19
+ /** Pozycja, w którą kliknięto. */
20
+ item: T;
21
+ /** Adres z `href`. */
22
+ url: string;
23
+ /** Oryginalne zdarzenie - `preventDefault()` zatrzymuje przeładowanie strony. */
24
+ event: MouseEvent;
25
+ }
26
+ /**
27
+ * Czy klik to zwykła nawigacja w tej samej karcie: lewy przycisk, bez modyfikatorów, a odnośnik
28
+ * nie ma `target` innego niż `_self`. `target` podaje wołający - w React `currentTarget`
29
+ * zdarzenia natywnego to korzeń aplikacji, nie kliknięty odnośnik.
30
+ */
31
+ export declare function kptIsPlainClick(event: Pick<MouseEvent, 'button' | 'ctrlKey' | 'metaKey' | 'shiftKey' | 'altKey'>, target?: string): boolean;
@@ -1 +1,9 @@
1
- export {};
1
+ /**
2
+ * Czy klik to zwykła nawigacja w tej samej karcie: lewy przycisk, bez modyfikatorów, a odnośnik
3
+ * nie ma `target` innego niż `_self`. `target` podaje wołający - w React `currentTarget`
4
+ * zdarzenia natywnego to korzeń aplikacji, nie kliknięty odnośnik.
5
+ */
6
+ export function kptIsPlainClick(event, target = '') {
7
+ const modified = event.ctrlKey || event.metaKey || event.shiftKey || event.altKey;
8
+ return event.button === 0 && !modified && (target === '' || target === '_self');
9
+ }
@@ -1,24 +1,24 @@
1
1
  # KptSidenav (@konce-pt/react)
2
2
 
3
- The side panel — a styled navigation container, usually handed to the `sidenav` slot of
3
+ The side panel - a styled navigation container, usually handed to the `sidenav` slot of
4
4
  `<KptAppShell>`. Import: `import { KptSidenav } from '@konce-pt/react';`
5
5
 
6
6
  ## DOM
7
7
  <div class="kpt-sidenav">
8
- <div class="kpt-sidenav__scroll">…children…</div>
9
- <div kptsidenavfooter="">…footer…</div>
8
+ <div class="kpt-sidenav__scroll">...children...</div>
9
+ <div kptsidenavfooter="">...footer...</div>
10
10
  </div>
11
11
 
12
12
  ## Props
13
13
  - `className`, `ref` and every other `<div>` prop go to the panel root
14
14
 
15
15
  ## Sub-components
16
- - `KptSidenav.Footer` — lifted OUT of the scrolling area, so it stays pinned to the bottom of the
16
+ - `KptSidenav.Footer` - lifted OUT of the scrolling area, so it stays pinned to the bottom of the
17
17
  panel however long the navigation gets (a user block, an app version, a theme switch). It carries
18
18
  the `kptsidenavfooter` attribute the shared SCSS targets.
19
19
 
20
20
  Default children land in the scrolling area. Plain `<a>` elements inside it are styled by the
21
- stylesheet already — padding, radius, a hover background — so a simple link list needs no classes.
21
+ stylesheet already - padding, radius, a hover background - so a simple link list needs no classes.
22
22
 
23
23
  ## Examples
24
24
  <KptSidenav>
@@ -27,7 +27,7 @@ stylesheet already — padding, radius, a hover background — so a simple link
27
27
  <KptSidenav.Footer>v0.7.12</KptSidenav.Footer>
28
28
  </KptSidenav>
29
29
 
30
- <KptAppShell sidenav={<KptSidenav>…</KptSidenav>} toolbar={…}>…</KptAppShell>
30
+ <KptAppShell sidenav={<KptSidenav>...</KptSidenav>} toolbar={...}>...</KptAppShell>
31
31
 
32
32
  ## Rail mode
33
33
  When `<KptAppShell sidenavRailBreakpoint>` is on, a closed panel narrows instead of hiding. Mark the
@@ -41,13 +41,13 @@ the shell will clip them the accessible way (visually hidden, still in the acces
41
41
 
42
42
  Both render the lowercase attribute the shared SCSS targets (`kptnavlabel` / `kptnavsection`);
43
43
  attribute names in CSS selectors are case-insensitive in HTML, so one rule serves both ports. Use
44
- the components rather than writing the attribute by hand — JSX rejects an unknown camelCase
44
+ the components rather than writing the attribute by hand - JSX rejects an unknown camelCase
45
45
  attribute, and a hand-written one is easy to misspell.
46
46
 
47
47
  ## Links
48
48
  Links in the scroll area are `display: flex` with a gap, so an icon and a label never glue together
49
49
  even when the template compiler drops the whitespace between them. A link with
50
- `aria-current="page"` gets the muted background and a medium weight — mark the current page
50
+ `aria-current="page"` gets the muted background and a medium weight - mark the current page
51
51
  yourself, the component does not know which one it is.
52
52
 
53
53
  ## Tokens
@@ -55,5 +55,5 @@ yourself, the component does not know which one it is.
55
55
  `--kpt-sidenav-rail-width`, which are read by `KptAppShell`.
56
56
 
57
57
  ## Accessibility
58
- The panel itself is a plain container — the `role="navigation"` and the label live on the shell's
58
+ The panel itself is a plain container - the `role="navigation"` and the label live on the shell's
59
59
  `<aside>` around it, so a sidenav used outside `KptAppShell` needs its own landmark.
@@ -1,8 +1,7 @@
1
1
  import { type ComponentPropsWithRef } from 'react';
2
- export interface KptSidenavProps extends ComponentPropsWithRef<'div'> {
3
- }
2
+ export type KptSidenavProps = ComponentPropsWithRef<'div'>;
4
3
  /**
5
- * Panel boczny (sidenav) — stylowany kontener nawigacji, zwykle podawany do slotu
4
+ * Panel boczny (sidenav) - stylowany kontener nawigacji, zwykle podawany do slotu
6
5
  * `sidenav` komponentu `<KptAppShell>`.
7
6
  *
8
7
  * @example
@@ -1,13 +1,14 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Children, isValidElement } from 'react';
2
+ import { isValidElement } from 'react';
3
3
  import { cn } from "../internal/cn.js";
4
+ import { slotChildren } from "../internal/slot-children.js";
4
5
  /*
5
6
  * Panel odtwarza projekcję z Angulara: treść domyślna ląduje w obszarze przewijanym,
6
7
  * a `<KptSidenav.Footer>` jest wynoszona POZA ten obszar. Dzięki temu stopka zostaje
7
8
  * przyklejona do dołu panelu niezależnie od długości nawigacji.
8
9
  */
9
10
  /**
10
- * Panel boczny (sidenav) — stylowany kontener nawigacji, zwykle podawany do slotu
11
+ * Panel boczny (sidenav) - stylowany kontener nawigacji, zwykle podawany do slotu
11
12
  * `sidenav` komponentu `<KptAppShell>`.
12
13
  *
13
14
  * @example
@@ -20,14 +21,14 @@ import { cn } from "../internal/cn.js";
20
21
  export function KptSidenav({ className, children, ...rest }) {
21
22
  const scroll = [];
22
23
  const footers = [];
23
- Children.forEach(children, (child) => {
24
+ for (const child of slotChildren(children)) {
24
25
  if (isValidElement(child) && child.type?.kptSidenavSlot === 'footer') {
25
26
  footers.push(child);
26
27
  }
27
28
  else {
28
29
  scroll.push(child);
29
30
  }
30
- });
31
+ }
31
32
  return (_jsxs("div", { className: cn('kpt-sidenav', className), ...rest, children: [_jsx("div", { className: "kpt-sidenav__scroll", children: scroll }), footers] }));
32
33
  }
33
34
  /**
@@ -12,13 +12,13 @@ Import: `import { KptSkeleton } from '@konce-pt/react';`
12
12
  ## Props
13
13
  - `variant`: 'text' | 'circle' | 'rect' (default 'rect')
14
14
  - `width`: string (default '100%'), `height`: string (default '1rem')
15
- - `lines`: number (default 3) — the number of lines in the `text` variant
15
+ - `lines`: number (default 3) - the number of lines in the `text` variant
16
16
  - `className`, `ref` and every other `<span>` prop go to the host
17
17
 
18
18
  ## The last line is shorter
19
19
  In the `text` variant the last line is 60% wide. A stack of full-width rectangles does not read as
20
20
  a paragraph, and a paragraph is exactly what it stands in for. A single line (`lines={1}`) keeps
21
- the full `width` — there is no paragraph to imitate.
21
+ the full `width` - there is no paragraph to imitate.
22
22
 
23
23
  ## Examples
24
24
  <KptSkeleton variant="text" lines={3} />
@@ -29,6 +29,6 @@ the full `width` — there is no paragraph to imitate.
29
29
  The base `--kpt-color-muted`; the shimmer is a lighter band mixed with `--kpt-color-surface`; corners `--kpt-radius-md` (blocks) / `--kpt-radius-sm` (text lines).
30
30
 
31
31
  ## Accessibility
32
- The host is `aria-hidden="true"` — a placeholder has nothing to announce, and reading out empty
32
+ The host is `aria-hidden="true"` - a placeholder has nothing to announce, and reading out empty
33
33
  boxes only adds noise. Announce the wait with `KptSpinner` or `KptProgress` next to it when it
34
34
  matters.