@konce-pt/react 0.12.0 → 0.15.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 (378) hide show
  1. package/README.md +122 -64
  2. package/dist/accordion/accordion.d.ts +14 -6
  3. package/dist/accordion/accordion.js +14 -7
  4. package/dist/accordion/llms.txt +22 -10
  5. package/dist/alert/alert.d.ts +2 -2
  6. package/dist/alert/alert.js +2 -2
  7. package/dist/alert/llms.txt +10 -10
  8. package/dist/app-shell/app-shell.d.ts +14 -14
  9. package/dist/app-shell/app-shell.js +11 -11
  10. package/dist/app-shell/llms.txt +13 -13
  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 +7 -7
  14. package/dist/auth/llms.txt +9 -9
  15. package/dist/autocomplete/autocomplete.d.ts +19 -6
  16. package/dist/autocomplete/autocomplete.js +23 -10
  17. package/dist/autocomplete/llms.txt +16 -5
  18. package/dist/avatar/avatar.d.ts +6 -6
  19. package/dist/avatar/avatar.js +5 -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 +3 -3
  23. package/dist/avatar-group/llms.txt +8 -8
  24. package/dist/backdrop/backdrop.d.ts +7 -7
  25. package/dist/backdrop/backdrop.js +16 -16
  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 +4 -4
  31. package/dist/bottom-sheet/bottom-sheet.js +2 -2
  32. package/dist/bottom-sheet/llms.txt +9 -9
  33. package/dist/breadcrumb/breadcrumb.d.ts +2 -2
  34. package/dist/breadcrumb/breadcrumb.js +3 -3
  35. package/dist/breadcrumb/llms.txt +3 -3
  36. package/dist/button/button.d.ts +3 -3
  37. package/dist/button/button.js +8 -8
  38. package/dist/button/llms.txt +27 -19
  39. package/dist/button-group/button-group.d.ts +9 -9
  40. package/dist/button-group/button-group.js +30 -8
  41. package/dist/button-group/llms.txt +20 -14
  42. package/dist/card/card.d.ts +8 -8
  43. package/dist/card/card.js +6 -6
  44. package/dist/card/llms.txt +20 -20
  45. package/dist/carousel/carousel.d.ts +6 -6
  46. package/dist/carousel/carousel.js +4 -4
  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 +1 -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 +1 -1
  53. package/dist/charts/chart.js +8 -8
  54. package/dist/charts/llms.txt +6 -6
  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 +42 -18
  58. package/dist/chat/llms.txt +32 -17
  59. package/dist/chat-composer/chat-composer.d.ts +17 -7
  60. package/dist/chat-composer/chat-composer.js +21 -8
  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 +9 -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 +17 -7
  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 +3 -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 +26 -7
  74. package/dist/checkbox/llms.txt +24 -12
  75. package/dist/chip/chip.d.ts +4 -4
  76. package/dist/chip/chip.js +2 -2
  77. package/dist/chip/llms.txt +11 -11
  78. package/dist/chips-input/chips-input.d.ts +4 -4
  79. package/dist/chips-input/chips-input.js +35 -9
  80. package/dist/chips-input/llms.txt +15 -8
  81. package/dist/clock/clock.d.ts +4 -4
  82. package/dist/clock/clock.js +7 -7
  83. package/dist/clock/llms.txt +8 -8
  84. package/dist/color-picker/color-picker.d.ts +6 -6
  85. package/dist/color-picker/color-picker.js +3 -3
  86. package/dist/color-picker/llms.txt +5 -5
  87. package/dist/confirm/confirm-container.d.ts +3 -3
  88. package/dist/confirm/confirm-container.js +5 -5
  89. package/dist/confirm/confirm-store.d.ts +4 -4
  90. package/dist/confirm/confirm-store.js +7 -7
  91. package/dist/confirm/llms.txt +6 -6
  92. package/dist/context-menu/context-menu.d.ts +1 -1
  93. package/dist/context-menu/context-menu.js +5 -5
  94. package/dist/context-menu/llms.txt +7 -7
  95. package/dist/data-table/data-table-types.d.ts +5 -5
  96. package/dist/data-table/data-table.d.ts +29 -8
  97. package/dist/data-table/data-table.js +340 -95
  98. package/dist/data-table/export-csv.d.ts +3 -3
  99. package/dist/data-table/export-csv.js +4 -4
  100. package/dist/data-table/llms.txt +127 -94
  101. package/dist/data-view/data-view.d.ts +13 -5
  102. package/dist/data-view/data-view.js +67 -6
  103. package/dist/data-view/llms.txt +29 -10
  104. package/dist/date-range/date-range.d.ts +9 -9
  105. package/dist/date-range/date-range.js +24 -24
  106. package/dist/date-range/llms.txt +20 -20
  107. package/dist/datepicker/datepicker.d.ts +10 -10
  108. package/dist/datepicker/datepicker.js +29 -29
  109. package/dist/datepicker/llms.txt +24 -24
  110. package/dist/dialog/dialog.d.ts +12 -7
  111. package/dist/dialog/dialog.js +4 -4
  112. package/dist/dialog/llms.txt +14 -11
  113. package/dist/divider/divider.d.ts +1 -1
  114. package/dist/divider/llms.txt +4 -4
  115. package/dist/drawer/drawer.d.ts +5 -5
  116. package/dist/drawer/drawer.js +2 -2
  117. package/dist/drawer/llms.txt +11 -11
  118. package/dist/empty/empty.d.ts +2 -2
  119. package/dist/empty/empty.js +1 -1
  120. package/dist/empty/llms.txt +9 -9
  121. package/dist/fab/fab.d.ts +1 -1
  122. package/dist/fab/fab.js +1 -1
  123. package/dist/fab/llms.txt +10 -6
  124. package/dist/fieldset/fieldset.d.ts +18 -4
  125. package/dist/fieldset/fieldset.js +9 -6
  126. package/dist/fieldset/llms.txt +26 -9
  127. package/dist/file-upload/file-upload.d.ts +3 -3
  128. package/dist/file-upload/file-upload.js +7 -7
  129. package/dist/file-upload/llms.txt +7 -7
  130. package/dist/form-field/form-field-context.d.ts +4 -4
  131. package/dist/form-field/form-field.d.ts +12 -4
  132. package/dist/form-field/form-field.js +26 -6
  133. package/dist/form-field/llms.txt +25 -11
  134. package/dist/galleria/galleria.d.ts +3 -3
  135. package/dist/galleria/galleria.js +5 -5
  136. package/dist/galleria/llms.txt +7 -7
  137. package/dist/grid/col.d.ts +4 -4
  138. package/dist/grid/col.js +2 -2
  139. package/dist/grid/flex.d.ts +3 -3
  140. package/dist/grid/flex.js +1 -1
  141. package/dist/grid/grid.d.ts +7 -7
  142. package/dist/grid/grid.js +3 -3
  143. package/dist/grid/layout.d.ts +2 -2
  144. package/dist/grid/layout.js +2 -2
  145. package/dist/grid/llms.txt +15 -15
  146. package/dist/grid-entry/public-api.js +1 -1
  147. package/dist/i18n/i18n-context.d.ts +3 -3
  148. package/dist/i18n/i18n-context.js +7 -7
  149. package/dist/icon/icon-registry.d.ts +3 -3
  150. package/dist/icon/icon-registry.js +3 -3
  151. package/dist/icon/icon.d.ts +4 -4
  152. package/dist/icon/icon.js +6 -6
  153. package/dist/icon/llms.txt +13 -13
  154. package/dist/icon-button/icon-button.d.ts +2 -2
  155. package/dist/icon-button/icon-button.js +1 -1
  156. package/dist/icon-button/llms.txt +9 -5
  157. package/dist/icons-entry/public-api.d.ts +2 -2
  158. package/dist/icons-entry/public-api.js +3 -3
  159. package/dist/image/image.js +1 -1
  160. package/dist/image/llms.txt +13 -13
  161. package/dist/input/input.d.ts +18 -5
  162. package/dist/input/input.js +21 -6
  163. package/dist/input/llms.txt +25 -11
  164. package/dist/input-mask/input-mask.d.ts +1 -1
  165. package/dist/input-mask/input-mask.js +2 -2
  166. package/dist/input-mask/llms.txt +5 -5
  167. package/dist/input-number/input-number.d.ts +9 -9
  168. package/dist/input-number/input-number.js +11 -11
  169. package/dist/input-number/llms.txt +14 -14
  170. package/dist/input-otp/input-otp.d.ts +6 -6
  171. package/dist/input-otp/input-otp.js +10 -10
  172. package/dist/input-otp/llms.txt +9 -9
  173. package/dist/internal/calendar-focus.d.ts +1 -1
  174. package/dist/internal/calendar-focus.js +3 -3
  175. package/dist/internal/cn.d.ts +1 -1
  176. package/dist/internal/mask.d.ts +2 -2
  177. package/dist/internal/mask.js +4 -4
  178. package/dist/internal/option-key.d.ts +2 -2
  179. package/dist/internal/option-key.js +2 -2
  180. package/dist/internal/option-nav.d.ts +4 -4
  181. package/dist/internal/option-nav.js +5 -5
  182. package/dist/internal/overlay-container.js +1 -1
  183. package/dist/internal/overlay.d.ts +3 -3
  184. package/dist/internal/overlay.js +8 -8
  185. package/dist/internal/password-strength.d.ts +2 -2
  186. package/dist/internal/password-strength.js +6 -6
  187. package/dist/internal/roving.d.ts +2 -2
  188. package/dist/internal/roving.js +3 -3
  189. package/dist/internal/scroll-lock.d.ts +1 -1
  190. package/dist/internal/scroll-lock.js +2 -2
  191. package/dist/internal/slot-children.d.ts +3 -3
  192. package/dist/internal/slot-children.js +4 -4
  193. package/dist/internal/slot.d.ts +3 -3
  194. package/dist/internal/slot.js +2 -2
  195. package/dist/internal/step.d.ts +3 -3
  196. package/dist/internal/step.js +4 -4
  197. package/dist/internal/tween.d.ts +5 -5
  198. package/dist/internal/tween.js +5 -5
  199. package/dist/internal/types.d.ts +2 -2
  200. package/dist/internal/use-breakpoint.d.ts +2 -2
  201. package/dist/internal/use-breakpoint.js +4 -4
  202. package/dist/internal/use-controllable-state.d.ts +1 -1
  203. package/dist/internal/use-controllable-state.js +3 -3
  204. package/dist/internal/use-id.d.ts +2 -2
  205. package/dist/internal/use-id.js +2 -2
  206. package/dist/internal/use-modal-focus.d.ts +1 -1
  207. package/dist/internal/use-modal-focus.js +3 -3
  208. package/dist/knob/knob.d.ts +4 -4
  209. package/dist/knob/knob.js +14 -10
  210. package/dist/knob/llms.txt +17 -12
  211. package/dist/listbox/listbox.d.ts +6 -6
  212. package/dist/listbox/listbox.js +9 -9
  213. package/dist/listbox/llms.txt +10 -10
  214. package/dist/loader/llms.txt +56 -0
  215. package/dist/loader/loader.d.ts +49 -0
  216. package/dist/loader/loader.js +68 -0
  217. package/dist/map/llms.txt +25 -22
  218. package/dist/map/map-create-dialog.d.ts +4 -4
  219. package/dist/map/map-create-dialog.js +10 -10
  220. package/dist/map/map-hours-field.d.ts +4 -4
  221. package/dist/map/map-hours-field.js +7 -7
  222. package/dist/map/map-image-field.d.ts +2 -2
  223. package/dist/map/map-image-field.js +4 -4
  224. package/dist/map/map-legend.d.ts +4 -4
  225. package/dist/map/map-legend.js +2 -2
  226. package/dist/map/map-poi-card.d.ts +3 -3
  227. package/dist/map/map-poi-card.js +9 -9
  228. package/dist/map/map-search.d.ts +17 -6
  229. package/dist/map/map-search.js +21 -18
  230. package/dist/map/map.d.ts +21 -14
  231. package/dist/map/map.js +30 -29
  232. package/dist/map/ports.d.ts +9 -9
  233. package/dist/map/ports.js +6 -6
  234. package/dist/map-entry/public-api.js +2 -2
  235. package/dist/megamenu/llms.txt +9 -9
  236. package/dist/megamenu/megamenu.d.ts +6 -6
  237. package/dist/megamenu/megamenu.js +10 -10
  238. package/dist/mention/llms.txt +15 -15
  239. package/dist/mention/mention.d.ts +5 -5
  240. package/dist/mention/mention.js +2 -2
  241. package/dist/mention/mentions.d.ts +1 -1
  242. package/dist/mention/mentions.js +1 -1
  243. package/dist/menu/llms.txt +12 -12
  244. package/dist/menu/menu-item.d.ts +4 -4
  245. package/dist/menu/menu-keys.d.ts +2 -2
  246. package/dist/menu/menu-keys.js +5 -5
  247. package/dist/menu/menu.d.ts +2 -2
  248. package/dist/menu/menu.js +6 -6
  249. package/dist/menubar/llms.txt +10 -10
  250. package/dist/menubar/menubar.d.ts +5 -5
  251. package/dist/menubar/menubar.js +10 -10
  252. package/dist/meter-group/llms.txt +12 -12
  253. package/dist/meter-group/meter-group.d.ts +2 -2
  254. package/dist/meter-group/meter-group.js +1 -1
  255. package/dist/motion/motion-context.d.ts +22 -0
  256. package/dist/motion/motion-context.js +28 -0
  257. package/dist/order-list/llms.txt +22 -10
  258. package/dist/order-list/order-list.d.ts +12 -4
  259. package/dist/order-list/order-list.js +104 -21
  260. package/dist/paginator/llms.txt +8 -8
  261. package/dist/paginator/paginator.js +1 -1
  262. package/dist/panel/llms.txt +25 -13
  263. package/dist/panel/panel.d.ts +11 -3
  264. package/dist/panel/panel.js +7 -4
  265. package/dist/password/llms.txt +16 -8
  266. package/dist/password/password.d.ts +2 -2
  267. package/dist/password/password.js +24 -3
  268. package/dist/peek/llms.txt +10 -10
  269. package/dist/peek/peek.d.ts +6 -6
  270. package/dist/peek/peek.js +7 -7
  271. package/dist/pick-list/llms.txt +33 -13
  272. package/dist/pick-list/pick-list.d.ts +12 -4
  273. package/dist/pick-list/pick-list.js +111 -23
  274. package/dist/popover/llms.txt +11 -11
  275. package/dist/popover/popover.d.ts +4 -4
  276. package/dist/popover/popover.js +5 -5
  277. package/dist/progress/llms.txt +17 -17
  278. package/dist/progress/progress.d.ts +7 -7
  279. package/dist/progress/progress.js +6 -6
  280. package/dist/public-api.d.ts +15 -3
  281. package/dist/public-api.js +13 -7
  282. package/dist/radio-group/llms.txt +13 -6
  283. package/dist/radio-group/radio-group.d.ts +11 -4
  284. package/dist/radio-group/radio-group.js +15 -7
  285. package/dist/rating/llms.txt +20 -13
  286. package/dist/rating/rating-pointer.d.ts +16 -0
  287. package/dist/rating/rating-pointer.js +26 -0
  288. package/dist/rating/rating.d.ts +6 -6
  289. package/dist/rating/rating.js +76 -12
  290. package/dist/rich-text/lists.d.ts +2 -2
  291. package/dist/rich-text/lists.js +7 -7
  292. package/dist/rich-text/llms.txt +26 -17
  293. package/dist/rich-text/rich-text.d.ts +12 -7
  294. package/dist/rich-text/rich-text.js +78 -54
  295. package/dist/rich-text/sanitize.d.ts +3 -3
  296. package/dist/rich-text/sanitize.js +12 -12
  297. package/dist/rich-text/tables.d.ts +3 -3
  298. package/dist/rich-text/tables.js +3 -3
  299. package/dist/roadmap/llms.txt +15 -15
  300. package/dist/roadmap/roadmap.d.ts +6 -6
  301. package/dist/roadmap/roadmap.js +19 -19
  302. package/dist/roadmap-entry/public-api.js +2 -2
  303. package/dist/scroll-top/llms.txt +6 -6
  304. package/dist/scroll-top/scroll-top.d.ts +1 -1
  305. package/dist/scroll-top/scroll-top.js +1 -1
  306. package/dist/select/llms.txt +19 -12
  307. package/dist/select/select.d.ts +8 -8
  308. package/dist/select/select.js +47 -21
  309. package/dist/shared/types.d.ts +5 -5
  310. package/dist/shared/types.js +1 -1
  311. package/dist/sidenav/llms.txt +9 -9
  312. package/dist/sidenav/sidenav.d.ts +1 -1
  313. package/dist/sidenav/sidenav.js +1 -1
  314. package/dist/skeleton/llms.txt +3 -3
  315. package/dist/skeleton/skeleton.d.ts +1 -1
  316. package/dist/skeleton/skeleton.js +1 -1
  317. package/dist/slider/llms.txt +32 -13
  318. package/dist/slider/slider-drag.d.ts +26 -0
  319. package/dist/slider/slider-drag.js +79 -0
  320. package/dist/slider/slider.d.ts +6 -5
  321. package/dist/slider/slider.js +27 -8
  322. package/dist/speech/llms.txt +97 -0
  323. package/dist/speech/speech-button.d.ts +44 -0
  324. package/dist/speech/speech-button.js +86 -0
  325. package/dist/speech/speech-wave.d.ts +19 -0
  326. package/dist/speech/speech-wave.js +16 -0
  327. package/dist/speech/use-speech-recognition.d.ts +81 -0
  328. package/dist/speech/use-speech-recognition.js +179 -0
  329. package/dist/speed-dial/llms.txt +14 -8
  330. package/dist/speed-dial/speed-dial.d.ts +1 -1
  331. package/dist/speed-dial/speed-dial.js +1 -1
  332. package/dist/spinner/llms.txt +7 -7
  333. package/dist/spinner/spinner.d.ts +1 -1
  334. package/dist/spinner/spinner.js +1 -1
  335. package/dist/split-button/llms.txt +21 -15
  336. package/dist/split-button/split-button.d.ts +2 -2
  337. package/dist/split-button/split-button.js +4 -4
  338. package/dist/splitter/llms.txt +11 -11
  339. package/dist/splitter/splitter.d.ts +3 -3
  340. package/dist/splitter/splitter.js +5 -5
  341. package/dist/stepper/llms.txt +33 -14
  342. package/dist/stepper/stepper.d.ts +6 -6
  343. package/dist/stepper/stepper.js +53 -10
  344. package/dist/switch/llms.txt +12 -5
  345. package/dist/switch/switch.d.ts +10 -3
  346. package/dist/switch/switch.js +12 -5
  347. package/dist/switch-group/llms.txt +9 -9
  348. package/dist/switch-group/switch-group.d.ts +4 -4
  349. package/dist/switch-group/switch-group.js +4 -4
  350. package/dist/tabs/llms.txt +10 -10
  351. package/dist/tabs/tabs.d.ts +5 -5
  352. package/dist/tabs/tabs.js +8 -8
  353. package/dist/textarea/llms.txt +23 -12
  354. package/dist/textarea/textarea.d.ts +19 -6
  355. package/dist/textarea/textarea.js +21 -7
  356. package/dist/timeline/llms.txt +4 -4
  357. package/dist/timeline/timeline.d.ts +1 -1
  358. package/dist/timeline/timeline.js +1 -1
  359. package/dist/toast/llms.txt +47 -17
  360. package/dist/toast/toast-container.d.ts +9 -3
  361. package/dist/toast/toast-container.js +17 -9
  362. package/dist/toast/toast-store.d.ts +40 -4
  363. package/dist/toast/toast-store.js +36 -1
  364. package/dist/toolbar/llms.txt +7 -7
  365. package/dist/toolbar/toolbar.d.ts +3 -3
  366. package/dist/toolbar/toolbar.js +2 -2
  367. package/dist/tooltip/llms.txt +10 -10
  368. package/dist/tooltip/tooltip.d.ts +3 -3
  369. package/dist/tooltip/tooltip.js +18 -18
  370. package/dist/tree/llms.txt +25 -12
  371. package/dist/tree/tree-keys.d.ts +5 -5
  372. package/dist/tree/tree-keys.js +7 -7
  373. package/dist/tree/tree.d.ts +14 -7
  374. package/dist/tree/tree.js +53 -18
  375. package/dist/update/llms.txt +54 -0
  376. package/dist/update/update.d.ts +49 -0
  377. package/dist/update/update.js +82 -0
  378. package/package.json +15 -13
@@ -1,7 +1,9 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
3
+ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react';
4
4
  import { foldMentionText } from '@konce-pt/mention';
5
+ import { flowChangeOf, prepareFlowMotion, resolveEnterMotion } from '@konce-pt/motion';
6
+ import { useKptMotion } from "../motion/motion-context.js";
5
7
  import { cn } from "../internal/cn.js";
6
8
  import { optionKey } from "../internal/option-key.js";
7
9
  import { KptOverlay } from "../internal/overlay.js";
@@ -11,7 +13,7 @@ import { KptTypeahead, OPTION_NAV_KEYS, isTypeaheadKey, nextOptionIndex, typeahe
11
13
  import { useKptMessages } from "../i18n/i18n-context.js";
12
14
  import { joinIds, useKptFieldA11y, useKptFormFieldFilled } from "../form-field/form-field-context.js";
13
15
  import { KptIcon } from "../icon/icon.js";
14
- /** Filtr opcji bez wielkości liter i diakrytyków, jak w `KptListbox`: „lodz” znajduje „Łódź”. */
16
+ /** Filtr opcji bez wielkości liter i diakrytyków, jak w `KptListbox`: "lodz" znajduje "Łódź". */
15
17
  function filterSelectOptions(options, text) {
16
18
  const q = foldMentionText(text.trim());
17
19
  return q ? options.filter((option) => foldMentionText(option.label).includes(q)) : options;
@@ -20,23 +22,23 @@ function filterSelectOptions(options, text) {
20
22
  * Lista wyboru (select) na własnym panelu. Warianty: pojedynczy, z wyszukiwarką (`filter`)
21
23
  * i wielokrotny (`multiple`, chipy).
22
24
  *
23
- * Klawiatura według wzorca WAI-ARIA „select-only combobox”: fokus zostaje na polu, a aktywna opcja
24
- * 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,
25
27
  * strzałki, `Home`/`End`, `PageUp`/`PageDown` chodzą po opcjach (z pominięciem wyłączonych),
26
28
  * wpisywane litery skaczą do pasującej opcji (bez polskich znaków), `Enter` wybiera, `Escape`
27
29
  * zamyka. Z `filter` fokus przechodzi do pola wyszukiwania, które obsługuje te same klawisze.
28
30
  * W trybie `multiple` `Backspace` na zamkniętym polu usuwa ostatni wybór.
29
31
  *
30
32
  * Wewnątrz `KptFormField` zgłasza wypełnienie hookiem `useKptFormFieldFilled`, więc etykieta
31
- * 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
32
34
  * odczytać, tu nie ma.
33
35
  *
34
36
  * @example
35
- * <KptSelect options={roles} value={role} onValueChange={setRole} placeholder="Wybierz…" />
37
+ * <KptSelect options={roles} value={role} onValueChange={setRole} placeholder="Wybierz..." />
36
38
  * <KptSelect options={tags} multiple filter value={tags} onValueChange={setTags} />
37
39
  */
38
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 }) {
39
- /** `div` z rolą nie dostaje nazwy z `label[for]` — stąd `aria-labelledby` na etykietę wrappera. */
41
+ /** `div` z rolą nie dostaje nazwy z `label[for]` - stąd `aria-labelledby` na etykietę wrappera. */
40
42
  const field = useKptFieldA11y(id, 'kpt-select-trigger');
41
43
  const { t } = useKptMessages({ locale, dictionary });
42
44
  const triggerRef = useRef(null);
@@ -71,11 +73,11 @@ export function KptSelect({ options = [], placeholder = '', multiple = false, fi
71
73
  const filled = selectedOptions.length > 0;
72
74
  useKptFormFieldFilled(filled);
73
75
  const isSelected = (option) => selectedValues.includes(option.value);
74
- /** Skrajna opcja, na której ma się otworzyć lista (`Home`/`End`/`↑` na zamkniętym polu). */
76
+ /** Skrajna opcja, na której ma się otworzyć lista (`Home`/`End`/`ArrowUp` na zamkniętym polu). */
75
77
  const [openEdge, setOpenEdge] = useState(null);
76
- // Panel może zostać otwarty także z zewnątrz (`open`), nie tylko z pola — filtr i aktywną
77
- // pozycję ustawiamy więc przy każdym przejściu w stan otwarty, wzorcem „poprzednia wartość
78
- // w stanie” (w tym samym renderze, bez efektu). Lista otwiera się na wybranej opcji; zmiana
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
79
81
  // wyboru w trybie `multiple` przy otwartym panelu nie przesuwa aktywnej opcji.
80
82
  const [wasOpen, setWasOpen] = useState(isOpen);
81
83
  if (wasOpen !== isOpen) {
@@ -89,7 +91,7 @@ export function KptSelect({ options = [], placeholder = '', multiple = false, fi
89
91
  setOpenEdge(null);
90
92
  }
91
93
  }
92
- // Z `filter` fokus idzie do wyszukiwarki — klatkę później, bo `KptOverlay` mierzy panel
94
+ // Z `filter` fokus idzie do wyszukiwarki - klatkę później, bo `KptOverlay` mierzy panel
93
95
  // w `visibility: hidden`.
94
96
  useEffect(() => {
95
97
  if (!isOpen || !filter)
@@ -108,16 +110,40 @@ export function KptSelect({ options = [], placeholder = '', multiple = false, fi
108
110
  setOpen(false);
109
111
  onTouch?.();
110
112
  }, [isOpen, setOpen, onTouch]);
111
- /** 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. */
112
114
  const closeAndFocus = () => {
113
115
  close();
114
116
  triggerRef.current?.focus();
115
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
+ };
116
142
  const pick = (option) => {
117
143
  if (option.disabled)
118
144
  return;
119
145
  if (multiple) {
120
- 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]);
121
147
  }
122
148
  else {
123
149
  setCurrent(option.value);
@@ -130,7 +156,7 @@ export function KptSelect({ options = [], placeholder = '', multiple = false, fi
130
156
  if (found !== null)
131
157
  setActiveIndex(found);
132
158
  };
133
- /** 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`. */
134
160
  const onKeyDown = (event, source) => {
135
161
  if (disabled || readOnly)
136
162
  return;
@@ -145,7 +171,7 @@ export function KptSelect({ options = [], placeholder = '', multiple = false, fi
145
171
  }
146
172
  else if ((key === 'Backspace' || key === 'Delete') && multiple && selectedValues.length) {
147
173
  event.preventDefault();
148
- setCurrent(selectedValues.slice(0, -1));
174
+ setSelected(selectedValues.slice(0, -1));
149
175
  }
150
176
  else if (isTypeaheadKey(event.nativeEvent) && !filter) {
151
177
  setOpen(true);
@@ -158,7 +184,7 @@ export function KptSelect({ options = [], placeholder = '', multiple = false, fi
158
184
  closeAndFocus();
159
185
  }
160
186
  else if (key === 'Tab') {
161
- // 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.
162
188
  if (source === 'search') {
163
189
  event.preventDefault();
164
190
  closeAndFocus();
@@ -188,15 +214,15 @@ export function KptSelect({ options = [], placeholder = '', multiple = false, fi
188
214
  setAnchor(el);
189
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) => {
190
216
  onBlur?.(event);
191
- // 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.
192
218
  if (!isOpen)
193
219
  onTouch?.();
194
- }, ...rest, children: [_jsx("div", { className: "kpt-select__value", 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) => {
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) => {
195
221
  event.stopPropagation();
196
- setCurrent(selectedValues.filter((v) => v !== option.value));
222
+ setSelected(selectedValues.filter((v) => v !== option.value));
197
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) => {
198
224
  setFilterText(event.target.value);
199
- // Po zawężeniu aktywna jest pierwsza pasująca opcja — `Enter` wybiera ją od razu.
225
+ // Po zawężeniu aktywna jest pierwsza pasująca opcja - `Enter` wybiera ją od razu.
200
226
  const narrowed = filterSelectOptions(options, event.target.value).map((o) => !!o.disabled);
201
227
  setActiveIndex(nextOptionIndex(narrowed, -1, 'ArrowDown') ?? -1);
202
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] }) })] }));
@@ -6,13 +6,13 @@ 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
11
  /**
12
12
  * Klik w odnośnik komponentu nawigacji (`kpt-breadcrumb`, `kpt-menubar`, `kpt-megamenu`). Linki
13
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/⌘/Shift/środkowy przycisk i `target="_blank"`
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
16
  * zostają natywne (nowa karta, nowe okno).
17
17
  */
18
18
  export interface KptNavigateEvent<T = unknown> {
@@ -20,12 +20,12 @@ export interface KptNavigateEvent<T = unknown> {
20
20
  item: T;
21
21
  /** Adres z `href`. */
22
22
  url: string;
23
- /** Oryginalne zdarzenie — `preventDefault()` zatrzymuje przeładowanie strony. */
23
+ /** Oryginalne zdarzenie - `preventDefault()` zatrzymuje przeładowanie strony. */
24
24
  event: MouseEvent;
25
25
  }
26
26
  /**
27
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`
28
+ * nie ma `target` innego niż `_self`. `target` podaje wołający - w React `currentTarget`
29
29
  * zdarzenia natywnego to korzeń aplikacji, nie kliknięty odnośnik.
30
30
  */
31
31
  export declare function kptIsPlainClick(event: Pick<MouseEvent, 'button' | 'ctrlKey' | 'metaKey' | 'shiftKey' | 'altKey'>, target?: string): boolean;
@@ -1,6 +1,6 @@
1
1
  /**
2
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`
3
+ * nie ma `target` innego niż `_self`. `target` podaje wołający - w React `currentTarget`
4
4
  * zdarzenia natywnego to korzeń aplikacji, nie kliknięty odnośnik.
5
5
  */
6
6
  export function kptIsPlainClick(event, target = '') {
@@ -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,7 +1,7 @@
1
1
  import { type ComponentPropsWithRef } from 'react';
2
2
  export type KptSidenavProps = ComponentPropsWithRef<'div'>;
3
3
  /**
4
- * Panel boczny (sidenav) — stylowany kontener nawigacji, zwykle podawany do slotu
4
+ * Panel boczny (sidenav) - stylowany kontener nawigacji, zwykle podawany do slotu
5
5
  * `sidenav` komponentu `<KptAppShell>`.
6
6
  *
7
7
  * @example
@@ -8,7 +8,7 @@ import { slotChildren } from "../internal/slot-children.js";
8
8
  * przyklejona do dołu panelu niezależnie od długości nawigacji.
9
9
  */
10
10
  /**
11
- * Panel boczny (sidenav) — stylowany kontener nawigacji, zwykle podawany do slotu
11
+ * Panel boczny (sidenav) - stylowany kontener nawigacji, zwykle podawany do slotu
12
12
  * `sidenav` komponentu `<KptAppShell>`.
13
13
  *
14
14
  * @example
@@ -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.
@@ -10,7 +10,7 @@ export interface KptSkeletonProps extends Omit<ComponentPropsWithRef<'span'>, 'c
10
10
  /**
11
11
  * Placeholder ładowania z animacją shimmer.
12
12
  *
13
- * Wariant `text` skraca ostatnią linię do 60% — pełny prostokąt nie wygląda jak akapit, a to
13
+ * Wariant `text` skraca ostatnią linię do 60% - pełny prostokąt nie wygląda jak akapit, a to
14
14
  * właśnie akapit ma udawać. Przy jednej linii nie ma czego udawać, więc ta ma pełną szerokość.
15
15
  *
16
16
  * @example
@@ -3,7 +3,7 @@ import { cn } from "../internal/cn.js";
3
3
  /**
4
4
  * Placeholder ładowania z animacją shimmer.
5
5
  *
6
- * Wariant `text` skraca ostatnią linię do 60% — pełny prostokąt nie wygląda jak akapit, a to
6
+ * Wariant `text` skraca ostatnią linię do 60% - pełny prostokąt nie wygląda jak akapit, a to
7
7
  * właśnie akapit ma udawać. Przy jednej linii nie ma czego udawać, więc ta ma pełną szerokość.
8
8
  *
9
9
  * @example
@@ -6,33 +6,39 @@ Import: `import { KptSlider } from '@konce-pt/react';`
6
6
  ## DOM
7
7
  <!-- single -->
8
8
  <div class="kpt-slider-host">
9
- <input class="kpt-slider__input" type="range" style="--_pct: 40%">
9
+ <div class="kpt-slider__track" style="--_p: 0.4">
10
+ <input class="kpt-slider__input" type="range">
11
+ <span class="kpt-slider__bar" aria-hidden="true"><span class="kpt-slider__bar-fill"></span></span>
12
+ <span class="kpt-slider__knob" aria-hidden="true"></span>
13
+ </div>
10
14
  <span class="kpt-slider__value">40</span>
11
15
  </div>
12
16
 
13
17
  <!-- range -->
14
18
  <div class="kpt-slider-host">
15
- <div class="kpt-slider__range">
16
- <div class="kpt-slider__rail"></div>
17
- <div class="kpt-slider__fill" style="left: 20%; right: 20%"></div>
19
+ <div class="kpt-slider__track kpt-slider__track--range" style="--_lo: 0.2; --_hi: 0.8">
18
20
  <input class="kpt-slider__thumb kpt-slider__thumb--low" type="range" aria-label="Minimum">
19
21
  <input class="kpt-slider__thumb kpt-slider__thumb--high" type="range" aria-label="Maximum">
22
+ <span class="kpt-slider__bar" aria-hidden="true"><span class="kpt-slider__bar-fill"></span></span>
23
+ <span class="kpt-slider__knob kpt-slider__knob--low" aria-hidden="true"></span>
24
+ <span class="kpt-slider__knob kpt-slider__knob--high" aria-hidden="true"></span>
20
25
  </div>
21
- <span class="kpt-slider__value">20–80</span>
26
+ <span class="kpt-slider__value">20-80</span>
22
27
  </div>
23
28
 
24
29
  ## Props
25
- - `value` / `defaultValue` / `onValueChange` — `number`, or `[number, number]` in `range` mode
26
- - `range`: boolean — two handles
30
+ - `value` / `defaultValue` / `onValueChange` - `number`, or `[number, number]` in `range` mode
31
+ - `range`: boolean - two handles
27
32
  - `min` (default 0), `max` (default 100), `step` (default 1)
28
- - `showValue`: boolean — prints the current value next to the slider
33
+ - `showValue`: boolean - prints the current value next to the slider
29
34
  - `disabled`, `onTouch()`
30
35
  - `className`, `ref` and every other `<div>` prop go to the host
31
36
 
32
37
  ## Native inputs all the way down
33
38
  Both handles are native `<input type="range">` stacked on each other, so the keyboard, touch
34
- gestures and screen readers work with no handling of our own — there is no custom drag logic to get
35
- wrong. The rail and the fill are decoration underneath them.
39
+ gestures and screen readers work with no handling of our own. The only custom pointer logic is the
40
+ press on the track next to the handles (see "Pointer"). The track, fill and handles you see are
41
+ decoration drawn under the transparent inputs.
36
42
 
37
43
  ## The handles cannot cross
38
44
  Moving the low handle above the high one clamps it to the high value, and the other way round. The
@@ -43,7 +49,7 @@ pair therefore always stays ordered, so the fill never inverts.
43
49
  <KptSlider range value={span} onValueChange={(v) => setSpan(v as [number, number])} showValue />
44
50
 
45
51
  ## Screen reader and keyboard
46
- `suffix`: string (" zł", "%", " km") — shown after the value and put into `aria-valuetext` ("40
52
+ `suffix`: string (" zł", "%", " km") - shown after the value and put into `aria-valuetext` ("40
47
53
  zł"), so the unit is read too. `aria-invalid` after touch.
48
54
 
49
55
  ## Label and description
@@ -55,12 +61,25 @@ previously hard-coded and mixed-language); inside `kpt-form-field` each thumb re
55
61
  Minimum" through `aria-labelledby`, without one `ariaLabel` gives "<ariaLabel>, Minimum". `id` goes
56
62
  to the single input or the lower thumb.
57
63
 
64
+ ## Pointer
65
+ The handle reacts to the pointer: it grows on hover, and while held it grows more on a spring with
66
+ a soft halo in the primary colour and a `grabbing` cursor. A click on the track (and a key press) glides
67
+ the handle and the fill to the new value instead of jumping; while dragging they follow the pointer with
68
+ no delay. The native `input[type=range]` stays transparent on top (keyboard, screen reader, dragging) and
69
+ the track, fill and handle are drawn next to it from `--_p` (0..1); `range` draws two handles from
70
+ `--_lo`/`--_hi`. The glide is pure CSS and drops out under "reduce motion".
71
+
72
+ In `range` mode a press on the track outside the handles - before the range, after it or inside it -
73
+ moves the nearest handle there, focuses it (arrow keys keep working) and keeps dragging it until
74
+ release. The handles themselves are dragged natively. When both handles sit in the upper half, the
75
+ lower one is stacked on top, so two handles pushed against `max` can still be pulled apart.
76
+
58
77
  ## Tokens
59
- `--kpt-color-primary` (the fill and the handles), `--kpt-color-muted` (the rail).
78
+ `--kpt-color-primary` (the fill and the handles), `--kpt-color-surface-variant` (the track).
60
79
 
61
80
  ## Accessibility
62
81
  Native range inputs carry their own role and value semantics. In `range` mode each handle gets an
63
82
  `aria-label` telling the two apart, because "slider" twice would say nothing.
64
83
 
65
84
  ## Read-only and required
66
- `readOnly` keeps the thumb focusable but undoes every move (`aria-readonly`). The range thumbs can no longer cross: the clamped value is written back to the native input, so the thumb stops at the other one. `slider` does not accept `aria-required` — the field asterisk and the validator carry it.
85
+ `readOnly` keeps the thumb focusable but undoes every move (`aria-readonly`). The range thumbs can no longer cross: the clamped value is written back to the native input, so the thumb stops at the other one. `slider` does not accept `aria-required` - the field asterisk and the validator carry it.
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Oznacza tor suwaka jako przeciągany (`data-dragging`) od pierwszego ruchu wskaźnika o kilka pikseli
3
+ * do puszczenia. Samo kliknięcie zostawia tor bez znacznika, więc skok wartości jedzie przejściem CSS.
4
+ * Ta sama funkcja jest w porcie Angulara (`slider/slider-drag.ts`).
5
+ */
6
+ export declare function kptTrackDrag(track: HTMLElement, startX: number): void;
7
+ /** Koniec zakresu: dolny albo górny uchwyt. */
8
+ export type KptRangeEnd = 'low' | 'high';
9
+ export interface KptRangePressOptions {
10
+ min: number;
11
+ max: number;
12
+ step: number;
13
+ low: number;
14
+ high: number;
15
+ /** Ustawia wybrany koniec. Pilnowanie, by uchwyty się nie minęły, zostaje w komponencie. */
16
+ set: (end: KptRangeEnd, value: number) => void;
17
+ /** Puszczenie wskaźnika. */
18
+ release?: () => void;
19
+ }
20
+ /**
21
+ * Wciśnięcie toru zakresu poza uchwytem. Najbliższy uchwyt skacze w to miejsce (wewnątrz zakresu
22
+ * też - ten bliższy), dostaje fokus, żeby dalej działały strzałki, i idzie za wskaźnikiem aż do
23
+ * puszczenia. Natywne suwaki tego nie zrobią: przy dwóch nałożonych inputach wskaźnik łapią tylko
24
+ * kciuki, a kliknięcie w tor trafiłoby w jeden z nich zawsze, niezależnie od odległości.
25
+ */
26
+ export declare function kptRangePress(track: HTMLElement, event: PointerEvent, opts: KptRangePressOptions): void;
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Oznacza tor suwaka jako przeciągany (`data-dragging`) od pierwszego ruchu wskaźnika o kilka pikseli
3
+ * do puszczenia. Samo kliknięcie zostawia tor bez znacznika, więc skok wartości jedzie przejściem CSS.
4
+ * Ta sama funkcja jest w porcie Angulara (`slider/slider-drag.ts`).
5
+ */
6
+ export function kptTrackDrag(track, startX) {
7
+ const move = (e) => {
8
+ if (Math.abs(e.clientX - startX) >= 3)
9
+ track.setAttribute('data-dragging', '');
10
+ };
11
+ const up = () => {
12
+ window.removeEventListener('pointermove', move);
13
+ window.removeEventListener('pointerup', up);
14
+ window.removeEventListener('pointercancel', up);
15
+ track.removeAttribute('data-dragging');
16
+ };
17
+ window.addEventListener('pointermove', move);
18
+ window.addEventListener('pointerup', up);
19
+ window.addEventListener('pointercancel', up);
20
+ }
21
+ /**
22
+ * Wciśnięcie toru zakresu poza uchwytem. Najbliższy uchwyt skacze w to miejsce (wewnątrz zakresu
23
+ * też - ten bliższy), dostaje fokus, żeby dalej działały strzałki, i idzie za wskaźnikiem aż do
24
+ * puszczenia. Natywne suwaki tego nie zrobią: przy dwóch nałożonych inputach wskaźnik łapią tylko
25
+ * kciuki, a kliknięcie w tor trafiłoby w jeden z nich zawsze, niezależnie od odległości.
26
+ */
27
+ export function kptRangePress(track, event, opts) {
28
+ if (event.button !== 0)
29
+ return;
30
+ // Bez tego przeglądarka zaznacza tekst w trakcie ciągnięcia i zabiera fokus z uchwytu.
31
+ event.preventDefault();
32
+ const first = rangeValueAt(track, event.clientX, opts);
33
+ const end = first < opts.low ? 'low' : first > opts.high ? 'high' : first - opts.low <= opts.high - first ? 'low' : 'high';
34
+ let last = first;
35
+ opts.set(end, first);
36
+ track.querySelector(`.kpt-slider__thumb--${end}`)?.focus({ preventScroll: true });
37
+ track.setAttribute('data-pressed', end);
38
+ const move = (e) => {
39
+ const next = rangeValueAt(track, e.clientX, opts);
40
+ if (next === last)
41
+ return;
42
+ last = next;
43
+ opts.set(end, next);
44
+ };
45
+ const up = () => {
46
+ window.removeEventListener('pointermove', move);
47
+ window.removeEventListener('pointerup', up);
48
+ window.removeEventListener('pointercancel', up);
49
+ track.removeAttribute('data-pressed');
50
+ opts.release?.();
51
+ };
52
+ window.addEventListener('pointermove', move);
53
+ window.addEventListener('pointerup', up);
54
+ window.addEventListener('pointercancel', up);
55
+ }
56
+ /**
57
+ * Wartość pod wskaźnikiem, przyciągnięta do kroku tak jak natywny suwak. Środek uchwytu jeździ
58
+ * od połowy jego szerokości do szerokości toru minus połowa - stąd odjęcie szerokości uchwytu.
59
+ */
60
+ function rangeValueAt(track, clientX, { min, max, step }) {
61
+ const rect = track.getBoundingClientRect();
62
+ const thumb = track.querySelector('.kpt-slider__knob')?.offsetWidth ?? 0;
63
+ const usable = rect.width - thumb;
64
+ let frac = usable > 0 ? (clientX - rect.left - thumb / 2) / usable : 0;
65
+ frac = Math.min(1, Math.max(0, frac));
66
+ if (getComputedStyle(track).direction === 'rtl')
67
+ frac = 1 - frac;
68
+ if (max <= min)
69
+ return min;
70
+ if (!(step > 0))
71
+ return min + frac * (max - min);
72
+ let steps = Math.round((frac * (max - min)) / step);
73
+ // Jak natywny suwak: ostatni krok nie wychodzi za `max`, gdy zakres nie dzieli się przez krok.
74
+ if (min + steps * step > max)
75
+ steps -= 1;
76
+ // Ułamkowy krok (0.1) zostawia ogon zmiennoprzecinkowy - przycięcie do liczby miejsc kroku.
77
+ const decimals = String(step).split('.')[1]?.length ?? 0;
78
+ return Number((min + steps * step).toFixed(decimals));
79
+ }
@@ -13,24 +13,25 @@ export interface KptSliderProps extends Omit<ComponentPropsWithRef<'div'>, 'onCh
13
13
  step?: number;
14
14
  /** Pokazuje bieżącą wartość obok suwaka. */
15
15
  showValue?: boolean;
16
- /** Jednostka za liczbą („ zł”, „%”, „ km”) — w pokazanej wartości i w `aria-valuetext`. */
16
+ /** Jednostka za liczbą (" zł", "%", " km") - w pokazanej wartości i w `aria-valuetext`. */
17
17
  suffix?: string;
18
18
  invalid?: boolean;
19
19
  touched?: boolean;
20
- /** Id elementu fokusowalnego (nie hosta); bez niego generowane, w `KptFormField` — z wrappera. */
20
+ /** Id elementu fokusowalnego (nie hosta); bez niego generowane, w `KptFormField` - z wrappera. */
21
21
  id?: string;
22
22
  /** Nazwa dostępna, gdy kontrolki nie opisuje etykieta (`<label for>` albo `KptFormField`). */
23
23
  ariaLabel?: string | null;
24
24
  disabled?: boolean;
25
- /** Tylko do odczytu — uchwyt można skupić, ale nie przesunąć (`aria-readonly`). */
25
+ /** Tylko do odczytu - uchwyt można skupić, ale nie przesunąć (`aria-readonly`). */
26
26
  readOnly?: boolean;
27
27
  onTouch?: () => void;
28
28
  }
29
29
  /**
30
30
  * Suwak wartości. W trybie `range` udostępnia dwa uchwyty, a wartością jest para `[min, max]`.
31
31
  *
32
- * Oba uchwyty to natywne `<input type="range">` leżące na sobie — dzięki temu klawiatura,
33
- * gesty dotykowe i czytniki ekranu działają bez ani jednej linii własnej obsługi.
32
+ * Oba uchwyty to natywne `<input type="range">` leżące na sobie - klawiatura, gesty dotykowe
33
+ * i czytniki ekranu działają bez własnej obsługi. Własna jest tylko jedna rzecz: wciśnięcie toru
34
+ * obok kciuków przesuwa najbliższy uchwyt (`kptRangePress`).
34
35
  *
35
36
  * @example
36
37
  * <KptSlider value={volume} onValueChange={setVolume} min={0} max={100} step={5} showValue />