@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
package/dist/knob/knob.js CHANGED
@@ -1,37 +1,42 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  import { useEffect, useRef, useState } from 'react';
3
4
  import { cn } from "../internal/cn.js";
5
+ import { kptSnapToStep } from "../internal/step.js";
4
6
  import { kptStepDecimals, kptTween } from "../internal/tween.js";
5
7
  import { useControllableState } from "../internal/use-controllable-state.js";
6
8
  import { useKptFieldA11y } from "../form-field/form-field-context.js";
7
9
  const TAU = Math.PI * 2;
8
- /** Przerwa u dołu — kąt „otwarcia" łuku, żeby początek i koniec były rozróżnialne. */
10
+ /** Przerwa u dołu - kąt "otwarcia" łuku, żeby początek i koniec były rozróżnialne. */
9
11
  const GAP = Math.PI / 4;
10
12
  const START_ANGLE = Math.PI / 2 + GAP;
11
13
  const SWEEP = TAU - GAP * 2;
12
14
  /** Czas dojścia łuku i licznika po skoku wartości (klawisz, klik, zmiana z zewnątrz). */
13
15
  const TWEEN_MS = 220;
14
16
  /**
15
- * Pokrętło — wybór wartości po łuku, przeciąganiem albo strzałkami.
17
+ * Pokrętło - wybór wartości po łuku, przeciąganiem albo strzałkami.
16
18
  *
17
19
  * Skok wartości (strzałka, `Home`/`End`, klik w łuk, zmiana z zewnątrz) łuk i licznik pokonują
18
- * płynnie; przy przeciąganiu idą za kursorem bez opóźnienia. `prefers-reduced-motion` wyłącza
20
+ * płynnie; przy przeciąganiu idą za kursorem bez opóźnienia. `PageUp`/`PageDown` skaczą o dziesięć
21
+ * kroków, a wartości leżą na siatce `min + k,step` dla myszy i klawiatury. `prefers-reduced-motion` wyłącza
19
22
  * animację, a `aria-valuenow` zawsze niesie wartość docelową, nie pośrednią.
20
23
  *
21
24
  * @example
22
25
  * <KptKnob value={level} onValueChange={setLevel} min={0} max={100} step={5} suffix="%" />
23
26
  */
24
- export function KptKnob({ value, defaultValue = 0, onValueChange, min = 0, max = 100, step = 1, size = 96, stroke = 10, suffix = '', showValue = true, id, ariaLabel = null, disabled = false, invalid = false, touched, onTouch, className, ...rest }) {
27
+ export function KptKnob({ value, defaultValue = 0, onValueChange, min = 0, max = 100, step = 1, size = 96, stroke = 10, suffix = '', showValue = true, id, ariaLabel = null, disabled = false, readOnly = false, invalid = false, touched, onTouch, className, ...rest }) {
25
28
  const field = useKptFieldA11y(id, 'kpt-knob');
26
29
  const svgRef = useRef(null);
27
30
  const [current, setCurrent] = useControllableState({ value, defaultValue, onChange: onValueChange });
28
31
  const showInvalid = invalid && (touched ?? true);
29
32
  const range = max - min;
30
- /** Wartość na rysunku — w trakcie animacji pośrednia, poza nią równa `current`. */
33
+ /** Wartość na rysunku - w trakcie animacji pośrednia, poza nią równa `current`. */
31
34
  const [shown, setShown] = useState(current);
32
35
  const shownRef = useRef(current);
33
- /** Przeciąganie trwa — wtedy rysunek idzie za kursorem bez animacji. */
36
+ /** Przeciąganie trwa - wtedy rysunek idzie za kursorem bez animacji. */
34
37
  const dragging = useRef(false);
38
+ /** Pokrętło trzymane wskaźnikiem (od wciśnięcia do puszczenia) - steruje reakcją w CSS. */
39
+ const [grabbing, setGrabbing] = useState(false);
35
40
  useEffect(() => {
36
41
  const paint = (v) => {
37
42
  shownRef.current = v;
@@ -58,7 +63,6 @@ export function KptKnob({ value, defaultValue = 0, onValueChange, min = 0, max =
58
63
  return `M ${x0} ${y0}`;
59
64
  return `M ${x0} ${y0} A ${r} ${r} 0 ${a1 - a0 > Math.PI ? 1 : 0} 1 ${x1} ${y1}`;
60
65
  };
61
- const clamp = (raw) => Math.min(max, Math.max(min, Math.round(raw / step) * step));
62
66
  const setFromPoint = (clientX, clientY) => {
63
67
  const svg = svgRef.current;
64
68
  if (!svg)
@@ -68,23 +72,24 @@ export function KptKnob({ value, defaultValue = 0, onValueChange, min = 0, max =
68
72
  const cy = rect.top + rect.height / 2;
69
73
  let angle = Math.atan2(clientY - cy, clientX - cx) - START_ANGLE;
70
74
  angle = ((angle % TAU) + TAU) % TAU;
71
- // Kursor w przerwie u dołu nie ma swojej wartości — przyciągamy go do bliższego końca łuku,
75
+ // Kursor w przerwie u dołu nie ma swojej wartości - przyciągamy go do bliższego końca łuku,
72
76
  // inaczej przeciągnięcie przez dół przeskakiwałoby z maksimum na minimum.
73
77
  if (angle > SWEEP + GAP)
74
78
  angle = angle > SWEEP + GAP / 2 ? 0 : SWEEP;
75
- setCurrent(clamp(min + Math.min(1, Math.max(0, angle / SWEEP)) * range));
79
+ setCurrent(kptSnapToStep(min + Math.min(1, Math.max(0, angle / SWEEP)) * range, min, max, step));
76
80
  };
77
81
  const onPointerDown = (event) => {
78
- if (disabled)
82
+ if (disabled || readOnly)
79
83
  return;
80
- // Bez tego przeciąganie zaznacza tekst: licznik w środku i — przez SVG — całe koło, jak po
84
+ // Bez tego przeciąganie zaznacza tekst: licznik w środku i - przez SVG - całe koło, jak po
81
85
  // `Ctrl+A`. Zablokowanie domyślnej akcji zabiera też fokus, więc oddajemy go ręcznie.
82
86
  event.preventDefault();
83
87
  event.currentTarget.focus();
84
- // Klik w łuk to skok — animowany. Dopiero ruch po nim jest przeciąganiem.
88
+ setGrabbing(true);
89
+ // Klik w łuk to skok - animowany. Dopiero ruch po nim jest przeciąganiem.
85
90
  setFromPoint(event.clientX, event.clientY);
86
91
  // Nasłuch wisi na oknie, a nie na pokrętle: przeciąganie ma nie gubić wartości,
87
- // gdy kursor wyjedzie poza koło — a wyjeżdża praktycznie zawsze.
92
+ // gdy kursor wyjedzie poza koło - a wyjeżdża praktycznie zawsze.
88
93
  const move = (e) => {
89
94
  dragging.current = true;
90
95
  setFromPoint(e.clientX, e.clientY);
@@ -94,6 +99,7 @@ export function KptKnob({ value, defaultValue = 0, onValueChange, min = 0, max =
94
99
  window.removeEventListener('pointerup', up);
95
100
  window.removeEventListener('pointercancel', up);
96
101
  dragging.current = false;
102
+ setGrabbing(false);
97
103
  onTouch?.();
98
104
  };
99
105
  window.addEventListener('pointermove', move);
@@ -101,22 +107,21 @@ export function KptKnob({ value, defaultValue = 0, onValueChange, min = 0, max =
101
107
  window.addEventListener('pointercancel', up);
102
108
  };
103
109
  const onKeyDown = (event) => {
104
- if (disabled)
110
+ if (disabled || readOnly)
105
111
  return;
106
- let next = current;
107
- if (event.key === 'ArrowUp' || event.key === 'ArrowRight')
108
- next += step;
109
- else if (event.key === 'ArrowDown' || event.key === 'ArrowLeft')
110
- next -= step;
111
- else if (event.key === 'Home')
112
+ const steps = { ArrowUp: 1, ArrowRight: 1, ArrowDown: -1, ArrowLeft: -1, PageUp: 10, PageDown: -10 };
113
+ let next;
114
+ if (event.key === 'Home')
112
115
  next = min;
113
116
  else if (event.key === 'End')
114
117
  next = max;
118
+ else if (steps[event.key] !== undefined)
119
+ next = current + steps[event.key] * step;
115
120
  else
116
121
  return;
117
122
  event.preventDefault();
118
- setCurrent(Math.min(max, Math.max(min, next)));
123
+ setCurrent(kptSnapToStep(next, min, max, step));
119
124
  onTouch?.();
120
125
  };
121
- return (_jsx("div", { className: cn('kpt-knob-host', className), ...rest, children: _jsx("div", { className: cn('kpt-knob', showInvalid && 'kpt-knob--invalid'), role: "slider", id: field.id, tabIndex: disabled ? -1 : 0, "aria-valuemin": min, "aria-valuemax": max, "aria-valuenow": current, "aria-valuetext": suffix ? `${current}${suffix}` : undefined, "aria-disabled": disabled || undefined, "aria-invalid": showInvalid || undefined, "aria-label": ariaLabel ?? undefined, "aria-labelledby": ariaLabel ? undefined : field.labelledBy, "aria-describedby": field.describedBy, onPointerDown: onPointerDown, onKeyDown: onKeyDown, children: _jsxs("svg", { ref: svgRef, viewBox: `0 0 ${size} ${size}`, style: { width: size, height: size }, children: [_jsx("path", { className: "kpt-knob__range", d: arc(0, 1), fill: "none", strokeWidth: stroke, strokeLinecap: "round" }), _jsx("path", { className: "kpt-knob__value", d: arc(0, ratio), fill: "none", strokeWidth: stroke, strokeLinecap: "round" }), showValue ? (_jsxs("text", { className: "kpt-knob__text", x: size / 2, y: size / 2, textAnchor: "middle", dominantBaseline: "central", children: [shown.toFixed(kptStepDecimals(step)), suffix] })) : null] }) }) }));
126
+ return (_jsx("div", { className: cn('kpt-knob-host', className), ...rest, children: _jsx("div", { className: cn('kpt-knob', showInvalid && 'kpt-knob--invalid', grabbing && 'is-grabbing'), role: "slider", id: field.id, tabIndex: disabled ? -1 : 0, "aria-valuemin": min, "aria-valuemax": max, "aria-valuenow": current, "aria-valuetext": suffix ? `${current}${suffix}` : undefined, "aria-disabled": disabled || undefined, "aria-readonly": readOnly || undefined, "aria-invalid": showInvalid || undefined, "aria-label": ariaLabel ?? undefined, "aria-labelledby": ariaLabel ? undefined : field.labelledBy, "aria-describedby": field.describedBy, onPointerDown: onPointerDown, onKeyDown: onKeyDown, children: _jsxs("svg", { ref: svgRef, viewBox: `0 0 ${size} ${size}`, style: { width: size, height: size }, children: [_jsx("path", { className: "kpt-knob__range", d: arc(0, 1), fill: "none", strokeWidth: stroke, strokeLinecap: "round" }), _jsx("path", { className: "kpt-knob__value", d: arc(0, ratio), fill: "none", strokeWidth: stroke, strokeLinecap: "round" }), showValue ? (_jsxs("text", { className: "kpt-knob__text", x: size / 2, y: size / 2, textAnchor: "middle", dominantBaseline: "central", children: [shown.toFixed(kptStepDecimals(step)), suffix] })) : null] }) }) }));
122
127
  }
@@ -1,36 +1,37 @@
1
1
  # KptKnob (@konce-pt/react)
2
2
 
3
- A knob — pick a value along an SVG arc, by dragging or with the arrow keys.
3
+ A knob - pick a value along an SVG arc, by dragging or with the arrow keys.
4
4
  Import: `import { KptKnob } from '@konce-pt/react';`
5
5
 
6
6
  ## DOM
7
7
  <div class="kpt-knob-host">
8
8
  <div class="kpt-knob" role="slider" tabindex="0" aria-valuenow="40">
9
9
  <svg viewBox="0 0 96 96">
10
- <path class="kpt-knob__range" d="M …" />
11
- <path class="kpt-knob__value" d="M …" />
10
+ <path class="kpt-knob__range" d="M ..." />
11
+ <path class="kpt-knob__value" d="M ..." />
12
12
  <text class="kpt-knob__text">40%</text>
13
13
  </svg>
14
14
  </div>
15
15
  </div>
16
16
 
17
17
  ## Props
18
- - `value` / `defaultValue` / `onValueChange` — the number
18
+ - `value` / `defaultValue` / `onValueChange` - the number
19
19
  - `min` (0), `max` (100), `step` (1)
20
- - `size`: px (96) — the side of the SVG square; `stroke`: px (10) — arc thickness
20
+ - `size`: px (96) - the side of the SVG square; `stroke`: px (10) - arc thickness
21
21
  - `suffix`: string appended to the value in the middle; `showValue`: boolean (default true)
22
- - `disabled`, `invalid`, `touched`, `onTouch()`
22
+ - `disabled`, `readOnly`, `invalid`, `touched`, `onTouch()` - `readOnly` keeps the knob focusable but ignores
23
+ the pointer and the keys (`aria-readonly`)
23
24
  - `className`, `ref` and every other `<div>` prop go to the host
24
25
 
25
26
  ## The gap at the bottom
26
27
  The arc is a full circle minus a 45° gap at the bottom, so its start and end are distinguishable.
27
- A pointer inside that gap has no value of its own — it is snapped to the nearer end, otherwise
28
+ A pointer inside that gap has no value of its own - it is snapped to the nearer end, otherwise
28
29
  dragging across the bottom would jump between minimum and maximum.
29
30
 
30
31
  ## Dragging listens on the window
31
32
  `pointermove` / `pointerup` are attached to `window`, not to the knob: the pointer leaves the
32
33
  circle during almost every drag, and the value must keep following it. Both listeners are removed
33
- on `pointerup`, which is also where `onTouch` fires — one touch per drag, not per pixel.
34
+ on `pointerup`, which is also where `onTouch` fires - one touch per drag, not per pixel.
34
35
 
35
36
  ## Examples
36
37
  <KptKnob value={level} onValueChange={setLevel} min={0} max={100} step={5} suffix="%" />
@@ -41,15 +42,20 @@ on `pointerup`, which is also where `onTouch` fires — one touch per drag, not
41
42
  generated. A `div` with a role is not named by `label[for]`, so inside `KptFormField` it gets
42
43
  `aria-labelledby` pointing at the wrapper's label (unless `ariaLabel` is set), and the hint or error
43
44
  is added to `aria-describedby`. New: `id`, `ariaLabel`; `aria-valuetext` includes `suffix` ("40%"),
44
- and a disabled knob leaves the Tab order. Use `ariaLabel`, not `aria-label` on the host — that one
45
+ and a disabled knob leaves the Tab order. Use `ariaLabel`, not `aria-label` on the host - that one
45
46
  lands on the wrapper.
46
47
 
48
+ ## Pointer
49
+ While held (from press to release) the knob reacts: the cursor becomes `grabbing`, the dial grows
50
+ slightly, the value arc glows stronger and the number in the middle scales up. On hover the arc glows
51
+ softly. All of it is CSS on the `is-grabbing` class and drops out under "reduce motion".
52
+
47
53
  ## Tokens
48
54
  The background arc `--kpt-color-border`, the value `--kpt-color-primary`, the text
49
55
  `--kpt-color-on-surface`.
50
56
 
51
57
  ## Motion
52
- A jump in value — an arrow key, Home/End, a click on the arc, a change from outside — moves the arc
58
+ A jump in value - an arrow key, Home/End, a click on the arc, a change from outside - moves the arc
53
59
  and counts the number up or down over ~220 ms (ease-out). Dragging follows the pointer with no
54
60
  delay, so it never feels laggy. `prefers-reduced-motion: reduce` turns the animation off.
55
61
  `aria-valuenow` always carries the target value, never an in-between frame. Dragging does not
@@ -57,5 +63,6 @@ select the number or the arc.
57
63
 
58
64
  ## Accessibility
59
65
  `role="slider"` with `aria-valuemin` / `aria-valuemax` / `aria-valuenow`; arrows step by `step`,
60
- Home and End jump to the bounds. `disabled` sets `aria-disabled` and drops the knob out of the
66
+ PageUp/PageDown by ten steps, Home and End jump to the bounds. Values sit on the grid `min + k,step` for
67
+ both the pointer and the keys - with `min={1} step={2}` that is 1, 3, 5.... `disabled` sets `aria-disabled` and drops the knob out of the
61
68
  tab order.
@@ -9,13 +9,15 @@ export interface KptListboxProps<T> extends Omit<ComponentPropsWithRef<'div'>, '
9
9
  multiple?: boolean;
10
10
  /** Pole filtrowania nad listą. */
11
11
  filter?: boolean;
12
- /** Id listy (nie hosta); bez niego generowane, w `KptFormField` — z wrappera. */
12
+ /** Id listy (nie hosta); bez niego generowane, w `KptFormField` - z wrappera. */
13
13
  id?: string;
14
14
  /** Nazwa dostępna listy, gdy nie opisuje jej etykieta (`KptFormField`). */
15
15
  ariaLabel?: string | null;
16
16
  /** Oznacza listę jako wymaganą atrybutem `aria-required`. */
17
17
  required?: boolean;
18
18
  disabled?: boolean;
19
+ /** Tylko do odczytu - po liście da się chodzić i czytać wybór, ale nie da się go zmienić. */
20
+ readOnly?: boolean;
19
21
  invalid?: boolean;
20
22
  touched?: boolean;
21
23
  onTouch?: () => void;
@@ -24,17 +26,17 @@ export interface KptListboxProps<T> extends Omit<ComponentPropsWithRef<'div'>, '
24
26
  * Lista wyboru widoczna w całości, bez rozwijania. Pojedynczy albo wielokrotny wybór,
25
27
  * opcjonalny filtr.
26
28
  *
27
- * W trybie pojedynczym ponowny klik w zaznaczoną opcję odznacza ją — lista bywa filtrem,
29
+ * W trybie pojedynczym ponowny klik w zaznaczoną opcję odznacza ją - lista bywa filtrem,
28
30
  * a filtr trzeba dać się wyczyścić bez sięgania po osobny przycisk.
29
31
  *
30
- * Klawiatura według wzorca WAI-ARIA „listbox”: lista to jeden przystanek Tab, a aktywną opcję
32
+ * Klawiatura według wzorca WAI-ARIA "listbox": lista to jeden przystanek Tab, a aktywną opcję
31
33
  * ogłasza `aria-activedescendant`. Strzałki, `Home`/`End`, `PageUp`/`PageDown` przesuwają aktywną
32
34
  * opcję (wyłączone są pomijane), pisanie liter skacze do kolejnej pasującej (bez względu na
33
- * diakrytyki), `Spacja`/`Enter` wybiera, a w trybie wielokrotnym `Ctrl`/`⌘`+`A` zaznacza wszystkie
34
- * widoczne. Pole filtra stoi przed listą; `↓` przenosi z niego fokus do listy.
35
+ * diakrytyki), `Spacja`/`Enter` wybiera, a w trybie wielokrotnym `Ctrl`/`Cmd`+`A` zaznacza wszystkie
36
+ * widoczne. Pole filtra stoi przed listą; `ArrowDown` przenosi z niego fokus do listy.
35
37
  *
36
38
  * @example
37
39
  * <KptListbox options={roles} value={role} onValueChange={setRole} filter />
38
40
  * <KptListbox options={tags} value={tags} onValueChange={setTags} multiple />
39
41
  */
40
- export declare function KptListbox<T>({ options, value, defaultValue, onValueChange, multiple, filter, id, ariaLabel, required, disabled, invalid, touched, onTouch, locale, dictionary, className, ...rest }: Readonly<KptListboxProps<T>>): import("react").JSX.Element;
42
+ export declare function KptListbox<T>({ options, value, defaultValue, onValueChange, multiple, filter, id, ariaLabel, required, disabled, readOnly, invalid, touched, onTouch, locale, dictionary, className, ...rest }: Readonly<KptListboxProps<T>>): import("react").JSX.Element;
@@ -1,3 +1,4 @@
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 { foldMentionText } from '@konce-pt/mention';
@@ -11,22 +12,22 @@ import { KptIcon } from "../icon/icon.js";
11
12
  * Lista wyboru widoczna w całości, bez rozwijania. Pojedynczy albo wielokrotny wybór,
12
13
  * opcjonalny filtr.
13
14
  *
14
- * W trybie pojedynczym ponowny klik w zaznaczoną opcję odznacza ją — lista bywa filtrem,
15
+ * W trybie pojedynczym ponowny klik w zaznaczoną opcję odznacza ją - lista bywa filtrem,
15
16
  * a filtr trzeba dać się wyczyścić bez sięgania po osobny przycisk.
16
17
  *
17
- * Klawiatura według wzorca WAI-ARIA „listbox”: lista to jeden przystanek Tab, a aktywną opcję
18
+ * Klawiatura według wzorca WAI-ARIA "listbox": lista to jeden przystanek Tab, a aktywną opcję
18
19
  * ogłasza `aria-activedescendant`. Strzałki, `Home`/`End`, `PageUp`/`PageDown` przesuwają aktywną
19
20
  * opcję (wyłączone są pomijane), pisanie liter skacze do kolejnej pasującej (bez względu na
20
- * diakrytyki), `Spacja`/`Enter` wybiera, a w trybie wielokrotnym `Ctrl`/`⌘`+`A` zaznacza wszystkie
21
- * widoczne. Pole filtra stoi przed listą; `↓` przenosi z niego fokus do listy.
21
+ * diakrytyki), `Spacja`/`Enter` wybiera, a w trybie wielokrotnym `Ctrl`/`Cmd`+`A` zaznacza wszystkie
22
+ * widoczne. Pole filtra stoi przed listą; `ArrowDown` przenosi z niego fokus do listy.
22
23
  *
23
24
  * @example
24
25
  * <KptListbox options={roles} value={role} onValueChange={setRole} filter />
25
26
  * <KptListbox options={tags} value={tags} onValueChange={setTags} multiple />
26
27
  */
27
- export function KptListbox({ options = [], value, defaultValue = null, onValueChange, multiple = false, filter = false, id, ariaLabel = null, required = false, disabled = false, invalid = false, touched, onTouch, locale, dictionary, className, ...rest }) {
28
+ export function KptListbox({ options = [], value, defaultValue = null, onValueChange, multiple = false, filter = false, id, ariaLabel = null, required = false, disabled = false, readOnly = false, invalid = false, touched, onTouch, locale, dictionary, className, ...rest }) {
28
29
  const { t } = useKptMessages({ locale, dictionary });
29
- /** `div` z rolą nie dostaje nazwy z `label[for]` — stąd `aria-labelledby` na etykietę wrappera. */
30
+ /** `div` z rolą nie dostaje nazwy z `label[for]` - stąd `aria-labelledby` na etykietę wrappera. */
30
31
  const field = useKptFieldA11y(id, 'kpt-listbox');
31
32
  const [current, setCurrent] = useControllableState({
32
33
  value,
@@ -39,7 +40,7 @@ export function KptListbox({ options = [], value, defaultValue = null, onValueCh
39
40
  const listRef = useRef(null);
40
41
  const typeahead = useRef(new KptTypeahead()).current;
41
42
  const showInvalid = invalid && (touched ?? true);
42
- /** Filtr bez względu na wielkość liter i diakrytyki („lodz” znajduje „Łódź”). */
43
+ /** Filtr bez względu na wielkość liter i diakrytyki ("lodz" znajduje "Łódź"). */
43
44
  const visible = useMemo(() => {
44
45
  const q = foldMentionText(query.trim());
45
46
  return q ? options.filter((option) => foldMentionText(option.label).includes(q)) : options;
@@ -55,18 +56,33 @@ export function KptListbox({ options = [], value, defaultValue = null, onValueCh
55
56
  const isSelected = (candidate) => multiple ? Array.isArray(current) && current.includes(candidate) : current === candidate;
56
57
  const toggle = (candidate) => {
57
58
  if (multiple) {
58
- const list = Array.isArray(current) ? current : [];
59
+ const list = Array.isArray(current) ? (current) : [];
59
60
  setCurrent(list.includes(candidate) ? list.filter((item) => item !== candidate) : [...list, candidate]);
60
61
  }
61
62
  else {
62
63
  setCurrent(current === candidate ? null : candidate);
63
64
  }
64
65
  };
65
- const pick = (index) => {
66
+ /** Ostatnio kliknięta opcja - początek zakresu dla `Shift+klik` w trybie `multiple`. */
67
+ const anchorIndex = useRef(-1);
68
+ const pick = (index, extend = false) => {
66
69
  const option = visible[index];
67
- if (!option || disabledMap[index])
70
+ if (!option || disabledMap[index] || readOnly)
68
71
  return;
69
72
  setActiveIndex(index);
73
+ if (extend && multiple && anchorIndex.current >= 0) {
74
+ // Zaznacza (dokłada) dozwolone opcje między kotwicą a klikniętą - `Shift+klik` (APG).
75
+ const [lo, hi] = anchorIndex.current <= index ? [anchorIndex.current, index] : [index, anchorIndex.current];
76
+ const list = Array.isArray(current) ? current : [];
77
+ const add = visible
78
+ .slice(lo, hi + 1)
79
+ .filter((_, offset) => !disabledMap[lo + offset])
80
+ .map((item) => item.value)
81
+ .filter((item) => !list.includes(item));
82
+ setCurrent([...list, ...add]);
83
+ return;
84
+ }
85
+ anchorIndex.current = index;
70
86
  toggle(option.value);
71
87
  };
72
88
  /** Wejście fokusem: aktywna zostaje wybrana opcja, a bez niej pierwsza dozwolona. */
@@ -88,8 +104,12 @@ export function KptListbox({ options = [], value, defaultValue = null, onValueCh
88
104
  if (OPTION_NAV_KEYS.has(event.key)) {
89
105
  event.preventDefault();
90
106
  const next = nextOptionIndex(disabledMap, activeIndex, event.key);
91
- if (next !== null)
92
- setActiveIndex(next);
107
+ if (next === null)
108
+ return;
109
+ setActiveIndex(next);
110
+ // `Shift+strzałka` w wyborze wielokrotnym zaznacza opcję, na którą przeszedł fokus (APG listbox).
111
+ if (event.shiftKey && multiple && !readOnly && !isSelected(visible[next].value))
112
+ toggle(visible[next].value);
93
113
  return;
94
114
  }
95
115
  if (event.key === ' ' || event.key === 'Enter') {
@@ -99,11 +119,11 @@ export function KptListbox({ options = [], value, defaultValue = null, onValueCh
99
119
  pick(activeIndex);
100
120
  return;
101
121
  }
102
- if (multiple && (event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'a') {
122
+ if (multiple && !readOnly && (event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'a') {
103
123
  event.preventDefault();
104
124
  const all = visible.filter((_, index) => !disabledMap[index]).map((option) => option.value);
105
- const list = Array.isArray(current) ? current : [];
106
- // Drugi raz `Ctrl+A` przy wszystkich zaznaczonych — odznacza widoczne.
125
+ const list = Array.isArray(current) ? (current) : [];
126
+ // Drugi raz `Ctrl+A` przy wszystkich zaznaczonych - odznacza widoczne.
107
127
  const allOn = all.every((item) => list.includes(item));
108
128
  setCurrent(allOn ? list.filter((item) => !all.includes(item)) : [...list, ...all.filter((item) => !list.includes(item))]);
109
129
  return;
@@ -119,8 +139,8 @@ export function KptListbox({ options = [], value, defaultValue = null, onValueCh
119
139
  return;
120
140
  event.preventDefault();
121
141
  listRef.current?.focus();
122
- } })] })) : null, _jsx("div", { ref: listRef, className: "kpt-listbox__items", role: "listbox", id: field.id, tabIndex: disabled ? -1 : 0, "aria-multiselectable": multiple || undefined, "aria-activedescendant": activeOptionId, "aria-disabled": disabled || undefined, "aria-invalid": showInvalid || undefined, "aria-required": required || undefined, "aria-label": ariaLabel ?? undefined, "aria-labelledby": ariaLabel ? undefined : field.labelledBy, "aria-describedby": field.describedBy, onKeyDown: onKeyDown, onFocus: onFocus, onBlur: () => onTouch?.(), children: visible.length ? (visible.map((option, index) => (_jsxs("div", { role: "option", id: optionId(index), className: cn('kpt-listbox__item', isSelected(option.value) && 'is-selected', index === activeIndex && 'is-active'), "aria-selected": isSelected(option.value), "aria-disabled": disabledMap[index] || undefined, onMouseDown: (event) => {
142
+ } })] })) : null, _jsx("div", { ref: listRef, className: "kpt-listbox__items", role: "listbox", id: field.id, tabIndex: disabled ? -1 : 0, "aria-multiselectable": multiple || undefined, "aria-activedescendant": activeOptionId, "aria-disabled": disabled || undefined, "aria-invalid": showInvalid || undefined, "aria-required": required || undefined, "aria-readonly": readOnly || undefined, "aria-label": ariaLabel ?? undefined, "aria-labelledby": ariaLabel ? undefined : field.labelledBy, "aria-describedby": field.describedBy, onKeyDown: onKeyDown, onFocus: onFocus, onBlur: () => onTouch?.(), children: visible.map((option, index) => (_jsxs("div", { role: "option", id: optionId(index), className: cn('kpt-listbox__item', isSelected(option.value) && 'is-selected', index === activeIndex && 'is-active'), "aria-selected": isSelected(option.value), "aria-disabled": disabledMap[index] || undefined, onMouseDown: (event) => {
123
143
  event.preventDefault();
124
144
  listRef.current?.focus();
125
- }, onClick: () => pick(index), children: [_jsx("span", { children: option.label }), isSelected(option.value) ? _jsx(KptIcon, { name: "check" }) : null] }, String(option.value))))) : (_jsx("div", { className: "kpt-listbox__empty", role: "status", children: t('listbox.empty') })) })] }) }));
145
+ }, onClick: (event) => pick(index, event.shiftKey), children: [_jsx("span", { children: option.label }), isSelected(option.value) ? _jsx(KptIcon, { name: "check" }) : null] }, typeof option.value === 'object' && option.value !== null ? `#${index}` : String(option.value)))) }), visible.length === 0 ? (_jsx("div", { className: "kpt-listbox__empty", role: "status", children: t('listbox.empty') })) : null] }) }));
126
146
  }
@@ -6,19 +6,19 @@ Import: `import { KptListbox } from '@konce-pt/react';`
6
6
  ## DOM
7
7
  <div class="kpt-listbox-host">
8
8
  <div class="kpt-listbox">
9
- <div class="kpt-listbox__filter">…icon + input…</div>
10
- <div class="kpt-listbox__items" role="listbox" tabindex="0" aria-activedescendant="…-opt-0">
11
- <div class="kpt-listbox__item is-selected is-active" role="option" id="…-opt-0" aria-selected="true">Admin</div>
9
+ <div class="kpt-listbox__filter">...icon + input...</div>
10
+ <div class="kpt-listbox__items" role="listbox" tabindex="0" aria-activedescendant="...-opt-0">
11
+ <div class="kpt-listbox__item is-selected is-active" role="option" id="...-opt-0" aria-selected="true">Admin</div>
12
12
  </div>
13
13
  </div>
14
14
  </div>
15
15
 
16
16
  ## Props
17
17
  - `options`: `KptOption<T>[]`
18
- - `value` / `defaultValue` / `onValueChange` — `T` (single) or `T[]` (multiple)
18
+ - `value` / `defaultValue` / `onValueChange` - `T` (single) or `T[]` (multiple)
19
19
  - `multiple`, `filter`, `disabled`, `invalid`, `touched`, `onTouch()`
20
20
  - `id`, `ariaLabel`, `required`
21
- - `locale`, `dictionary` — i18n overrides for this component only
21
+ - `locale`, `dictionary` - i18n overrides for this component only
22
22
 
23
23
  ## Clicking the selection clears it
24
24
  In single mode clicking the already selected option deselects it. A listbox is often a filter, and a
@@ -33,17 +33,17 @@ filter has to be clearable without a separate button.
33
33
 
34
34
  ## Keyboard
35
35
  The WAI-ARIA listbox pattern: the list is one Tab stop and the active option is announced through
36
- `aria-activedescendant`. `↑`/`↓`, `Home`/`End` and `PageUp`/`PageDown` move the active option,
36
+ `aria-activedescendant`. `ArrowUp`/`ArrowDown`, `Home`/`End` and `PageUp`/`PageDown` move the active option,
37
37
  skipping disabled ones (no wrapping); typing letters jumps to the next match, diacritics ignored;
38
- `Space`/`Enter` picks; in `multiple` mode `Ctrl`/`⌘`+`A` selects every visible option (again —
39
- clears them). The filter field sits before the list (outside the `listbox`) and `↓` moves focus
38
+ `Space`/`Enter` picks; in `multiple` mode `Ctrl`/`Cmd`+`A` selects every visible option (again -
39
+ clears them). The filter field sits before the list (outside the `listbox`) and `ArrowDown` moves focus
40
40
  from it into the list; the filter itself ignores diacritics ("lodz" finds "Łódź").
41
41
 
42
42
  ## Label and description
43
43
  `id` goes to the list (`role="listbox"`), never the host. A `div` with a role is not named by
44
44
  `label[for]`, so inside `KptFormField` the list gets `aria-labelledby` pointing at the wrapper's
45
45
  label (unless `ariaLabel` is set) and the hint or error in `aria-describedby`. New: `id`,
46
- `ariaLabel`, `required` (→ `aria-required`); `aria-invalid` after touch; `touch` fires on blur of the list.
46
+ `ariaLabel`, `required` (-> `aria-required`); `aria-invalid` after touch; `touch` fires on blur of the list.
47
47
 
48
48
  ## Tokens
49
49
  `--kpt-color-surface`, `--kpt-color-border`, `--kpt-color-primary-subtle` (the selected row),
@@ -53,3 +53,6 @@ label (unless `ariaLabel` is set) and the hint or error in `aria-describedby`. N
53
53
  The list is `role="listbox"` with `aria-multiselectable`, `aria-activedescendant` and a name from
54
54
  `ariaLabel` or `KptFormField`; options are `role="option"` with `aria-selected`/`aria-disabled`.
55
55
  The keyboard is described above.
56
+
57
+ ## Read-only and required
58
+ `readOnly` lets you move through the list and read the choice but not change it (`aria-readonly`). With `multiple`, `Shift+ArrowUp/ArrowDown` selects the option focus moves to and `Shift+click` adds the range from the last clicked option (APG). The empty message sits next to the list.
@@ -0,0 +1,56 @@
1
+ # KptLoader (@konce-pt/react)
2
+
3
+ A loader with character: a loop in one of six materials that turns into a result mark instead of
4
+ disappearing. Same classes and the same drawing as `kpt-loader` in Angular - the SVG and its
5
+ choreography come from `createLoader` in `@konce-pt/motion`. Import:
6
+ `import { KptLoader } from '@konce-pt/react';`
7
+
8
+ Variants: `bubbles` (default), `bridge` (the result is a pill with text), `shapes`, `ring` (progress,
9
+ an icon in the centre), `wave` (progress), `bounce`. Use `KptSpinner` for a small inline "wait".
10
+
11
+ ## DOM
12
+ <span class="kpt-loader-host">
13
+ <span class="kpt-loader" data-variant="bubbles" data-status="loading" style="--_size: 5rem">
14
+ <span class="kpt-loader__stage" aria-hidden="true"><svg class="kpt-loader__svg">...</svg></span>
15
+ <span class="kpt-loader__content"></span>
16
+ <span class="kpt-loader__label" role="status">Loading</span>
17
+ </span>
18
+ </span>
19
+
20
+ ## Props
21
+ - `variant`: KptLoaderVariant (default 'bubbles')
22
+ - `status`: 'loading' | 'success' | 'error' (default 'loading') - a result plays once and the mark
23
+ stays; back to `loading` restarts the loop
24
+ - `value`: number | null (default null), `min` (0), `max` (100) - progress for `ring` and `wave`
25
+ - `size`: string (default '5rem') - the CSS length of 100 drawing units, shared by all variants
26
+ - `colors`: KptLoaderColor[] | null - theme roles ('primary', 'info', 'success', 'warning',
27
+ 'danger') or CSS colours, repeated in order; without it each variant has its palette
28
+ - `successColor` (default 'success'), `errorColor` (default 'danger')
29
+ - `speed`: number (default 1) - multiplies the loop tempo
30
+ - `label`, `successLabel`, `errorLabel`: string - default `loader.label`, `loader.success`,
31
+ `loader.error`; the result texts also go into the `bridge` pill and follow the language live
32
+ - `announce`: boolean (default true) - `false` drops the own status region
33
+ - `onSettled`: (result: KptLoaderResult) => void - the result choreography has finished; not called
34
+ when the loader mounts already in a result
35
+ - `children` - content in the centre of `ring` (other variants ignore it)
36
+ - `locale`, `dictionary` - i18n overrides for this component only
37
+ - `className`, `style`, `ref` and other `<span>` props go to `.kpt-loader`; `hostProps` to the wrapper
38
+
39
+ ## Examples
40
+ <KptLoader />
41
+ <KptLoader variant="bridge" status={saving ? 'loading' : 'success'} onSettled={() => setShown(false)} />
42
+ <KptLoader variant="wave" value={uploaded} max={total} />
43
+ <KptLoader variant="ring" status={status}><KptIcon name="download" /></KptLoader>
44
+ <KptLoader variant="bounce" colors={['danger', 'oklch(0.6 0.2 300)']} speed={1.5} />
45
+
46
+ ## Tokens
47
+ Roles resolve to `--kpt-color-<role>`, marks on filled shapes to `--kpt-color-<role>-contrast`
48
+ (a custom colour gets `--kpt-color-surface`), the `shapes` backdrop to `--kpt-color-<role>-subtle`;
49
+ tracks `--kpt-color-border`, shadows `--kpt-color-overlay-scrim`.
50
+
51
+ ## Motion and accessibility
52
+ One `requestAnimationFrame` loop per loader, stopped off-screen and after the result.
53
+ `<KptMotionProvider enabled={false}>` and the system "reduce motion" setting slow the loop down and
54
+ replace the choreography with the mark in a circle. The drawing is `aria-hidden`; a visually hidden
55
+ `role="status"` region gets the label a frame after mount, then the result text. While `ring` or
56
+ `wave` draws progress the stage is a `role="progressbar"` with `aria-valuenow` 0-100.
@@ -0,0 +1,49 @@
1
+ import { type ComponentPropsWithRef, type ReactNode } from 'react';
2
+ import { type KptLoaderColor, type KptLoaderResult, type KptLoaderStatus, type KptLoaderVariant } from '@konce-pt/motion';
3
+ import type { KptHostProps } from '../internal/types.ts';
4
+ import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
5
+ export type { KptLoaderColor, KptLoaderResult, KptLoaderStatus, KptLoaderVariant } from '@konce-pt/motion';
6
+ export interface KptLoaderProps extends Omit<ComponentPropsWithRef<'span'>, 'children'>, KptI18nOverrides {
7
+ /** Materiał pętli. Domyślnie `bubbles`. */
8
+ variant?: KptLoaderVariant;
9
+ /** `loading` - pętla; `success` / `error` - choreografia wyniku i znak, który zostaje. */
10
+ status?: KptLoaderStatus;
11
+ /** Postęp (`ring`, `wave`); `null` = nieokreślony. Zakres jak w `KptProgress`. */
12
+ value?: number | null;
13
+ min?: number;
14
+ max?: number;
15
+ /** Długość 100 jednostek rysunku (CSS). Wszystkie warianty dzielą skalę. Domyślnie `5rem`. */
16
+ size?: string;
17
+ /** Barwy pętli - role motywu (`primary`, `info`, ...) albo kolory CSS; powtarzane po kolei. */
18
+ colors?: readonly KptLoaderColor[] | null;
19
+ successColor?: KptLoaderColor;
20
+ errorColor?: KptLoaderColor;
21
+ /** Mnożnik tempa pętli. Domyślnie 1. */
22
+ speed?: number;
23
+ /** Etykieta a11y w trakcie; pominięta = `loader.label`. */
24
+ label?: string;
25
+ /** Napis sukcesu (region statusu i kapsuła `bridge`); pominięty = `loader.success`. */
26
+ successLabel?: string;
27
+ /** Napis błędu; pominięty = `loader.error`. */
28
+ errorLabel?: string;
29
+ /** `false` - bez własnego regionu statusu (stan ogłasza komponent wokół, np. `KptUpdate`). */
30
+ announce?: boolean;
31
+ /** Koniec choreografii wyniku - dobry moment, żeby schować loader. */
32
+ onSettled?: (result: KptLoaderResult) => void;
33
+ /** Treść w środku pierścienia (`ring`) - np. ikona. */
34
+ children?: ReactNode;
35
+ /** Propsy wrappera `kpt-loader-host` (odpowiednik hosta Angulara). */
36
+ hostProps?: KptHostProps<'span'>;
37
+ }
38
+ /**
39
+ * Loader z charakterem: pętla w jednym z sześciu materiałów, która przy `status` `success` albo
40
+ * `error` przechodzi w znak wyniku zamiast znikać. Te same klasy co `kpt-loader` w Angularze,
41
+ * a rysunek i choreografię daje `createLoader` z `@konce-pt/motion` - wspólne dla obu portów.
42
+ *
43
+ * @example
44
+ * <KptLoader />
45
+ * <KptLoader variant="bridge" status={saving ? 'loading' : 'success'} />
46
+ * <KptLoader variant="wave" value={uploaded} max={total} />
47
+ * <KptLoader variant="ring" status={status}><KptIcon name="download" /></KptLoader>
48
+ */
49
+ export declare function KptLoader({ variant, status, value, min, max, size, colors, successColor, errorColor, speed, label, successLabel, errorLabel, announce, onSettled, locale, dictionary, children, hostProps, className, style, ...rest }: Readonly<KptLoaderProps>): import("react").JSX.Element;
@@ -0,0 +1,68 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useEffectEvent, useRef, useState } from 'react';
4
+ import { createLoader, loaderProgress, } from '@konce-pt/motion';
5
+ import { cn } from "../internal/cn.js";
6
+ import { useKptMessages } from "../i18n/i18n-context.js";
7
+ import { useKptMotion } from "../motion/motion-context.js";
8
+ /**
9
+ * Loader z charakterem: pętla w jednym z sześciu materiałów, która przy `status` `success` albo
10
+ * `error` przechodzi w znak wyniku zamiast znikać. Te same klasy co `kpt-loader` w Angularze,
11
+ * a rysunek i choreografię daje `createLoader` z `@konce-pt/motion` - wspólne dla obu portów.
12
+ *
13
+ * @example
14
+ * <KptLoader />
15
+ * <KptLoader variant="bridge" status={saving ? 'loading' : 'success'} />
16
+ * <KptLoader variant="wave" value={uploaded} max={total} />
17
+ * <KptLoader variant="ring" status={status}><KptIcon name="download" /></KptLoader>
18
+ */
19
+ export function KptLoader({ variant = 'bubbles', status = 'loading', value = null, min = 0, max = 100, size = '5rem', colors = null, successColor = 'success', errorColor = 'danger', speed = 1, label, successLabel, errorLabel, announce = true, onSettled, locale, dictionary, children, hostProps, className, style, ...rest }) {
20
+ const { t } = useKptMessages({ locale, dictionary });
21
+ const motion = useKptMotion();
22
+ const stageRef = useRef(null);
23
+ const contentRef = useRef(null);
24
+ const controllerRef = useRef(null);
25
+ const [ready, setReady] = useState(false);
26
+ const effectiveLabel = label ?? t('loader.label');
27
+ const successText = successLabel ?? t('loader.success');
28
+ const errorText = errorLabel ?? t('loader.error');
29
+ const progress = loaderProgress(value, min, max);
30
+ // Postęp widać tylko w `ring` i `wave` - tylko tam loader jest paskiem postępu dla czytnika.
31
+ const determinate = status === 'loading' && progress !== null && (variant === 'ring' || variant === 'wave');
32
+ const reduced = motion?.enabled === false;
33
+ const settled = useEffectEvent((result) => onSettled?.(result));
34
+ // Najświeższe opcje dla efektów - bez przepisywania refa w trakcie renderu.
35
+ const currentOptions = useEffectEvent(() => ({
36
+ variant,
37
+ status,
38
+ progress,
39
+ colors,
40
+ successColor,
41
+ errorColor,
42
+ successText,
43
+ errorText,
44
+ speed,
45
+ reduced,
46
+ onSettled: (result) => settled(result),
47
+ }));
48
+ useEffect(() => {
49
+ if (!stageRef.current)
50
+ return;
51
+ const controller = createLoader(stageRef.current, contentRef.current, currentOptions());
52
+ controllerRef.current = controller;
53
+ // Tekst regionu statusu wpisujemy klatkę po pojawieniu się - pusty region nie jest ogłaszany.
54
+ const frame = requestAnimationFrame(() => setReady(true));
55
+ return () => {
56
+ cancelAnimationFrame(frame);
57
+ controller.destroy();
58
+ controllerRef.current = null;
59
+ };
60
+ }, []);
61
+ const colorsKey = (colors ?? []).join('|');
62
+ useEffect(() => {
63
+ controllerRef.current?.update(currentOptions());
64
+ }, [variant, status, progress, colorsKey, successColor, errorColor, successText, errorText, speed, reduced]);
65
+ const announced = !ready ? '' : status === 'success' ? successText : status === 'error' ? errorText : effectiveLabel;
66
+ const { className: hostClassName, ...hostRest } = hostProps ?? {};
67
+ return (_jsx("span", { className: cn('kpt-loader-host', hostClassName), ...hostRest, children: _jsxs("span", { className: cn('kpt-loader', className), "data-variant": variant, "data-status": status, style: { '--_size': size, ...style }, ...rest, children: [_jsx("span", { ref: stageRef, className: "kpt-loader__stage", role: determinate ? 'progressbar' : undefined, "aria-hidden": determinate ? undefined : true, "aria-label": determinate ? effectiveLabel : undefined, "aria-valuemin": determinate ? 0 : undefined, "aria-valuemax": determinate ? 100 : undefined, "aria-valuenow": determinate ? Math.round((progress ?? 0) * 100) : undefined }), _jsx("span", { ref: contentRef, className: "kpt-loader__content", children: children }), announce ? (_jsx("span", { className: "kpt-loader__label", role: "status", children: announced })) : null] }) }));
68
+ }