@konce-pt/react 0.11.2 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (382) hide show
  1. package/README.md +128 -64
  2. package/dist/accordion/accordion.d.ts +14 -6
  3. package/dist/accordion/accordion.js +25 -17
  4. package/dist/accordion/llms.txt +22 -10
  5. package/dist/alert/alert.d.ts +2 -2
  6. package/dist/alert/alert.js +3 -2
  7. package/dist/alert/llms.txt +10 -9
  8. package/dist/app-shell/app-shell.d.ts +14 -14
  9. package/dist/app-shell/app-shell.js +22 -15
  10. package/dist/app-shell/llms.txt +16 -15
  11. package/dist/auth/auth-types.d.ts +6 -6
  12. package/dist/auth/auth.d.ts +2 -2
  13. package/dist/auth/auth.js +24 -15
  14. package/dist/auth/llms.txt +12 -11
  15. package/dist/autocomplete/autocomplete.d.ts +19 -6
  16. package/dist/autocomplete/autocomplete.js +32 -11
  17. package/dist/autocomplete/llms.txt +19 -5
  18. package/dist/avatar/avatar.d.ts +6 -6
  19. package/dist/avatar/avatar.js +6 -5
  20. package/dist/avatar/llms.txt +10 -10
  21. package/dist/avatar-group/avatar-group.d.ts +4 -4
  22. package/dist/avatar-group/avatar-group.js +4 -3
  23. package/dist/avatar-group/llms.txt +8 -8
  24. package/dist/backdrop/backdrop.d.ts +7 -26
  25. package/dist/backdrop/backdrop.js +32 -27
  26. package/dist/backdrop/llms.txt +27 -27
  27. package/dist/badge/badge.d.ts +3 -3
  28. package/dist/badge/badge.js +1 -1
  29. package/dist/badge/llms.txt +11 -11
  30. package/dist/bottom-sheet/bottom-sheet.d.ts +8 -3
  31. package/dist/bottom-sheet/bottom-sheet.js +5 -4
  32. package/dist/bottom-sheet/llms.txt +11 -8
  33. package/dist/breadcrumb/breadcrumb.d.ts +8 -2
  34. package/dist/breadcrumb/breadcrumb.js +11 -3
  35. package/dist/breadcrumb/llms.txt +10 -2
  36. package/dist/button/button.d.ts +3 -3
  37. package/dist/button/button.js +10 -9
  38. package/dist/button/llms.txt +27 -19
  39. package/dist/button-group/button-group.d.ts +18 -5
  40. package/dist/button-group/button-group.js +54 -6
  41. package/dist/button-group/llms.txt +27 -18
  42. package/dist/card/card.d.ts +8 -8
  43. package/dist/card/card.js +10 -9
  44. package/dist/card/llms.txt +20 -20
  45. package/dist/carousel/carousel.d.ts +6 -6
  46. package/dist/carousel/carousel.js +8 -5
  47. package/dist/carousel/llms.txt +10 -10
  48. package/dist/charts/chart-legend.d.ts +3 -3
  49. package/dist/charts/chart-legend.js +2 -1
  50. package/dist/charts/chart-tooltip.d.ts +3 -3
  51. package/dist/charts/chart-tooltip.js +1 -1
  52. package/dist/charts/chart.d.ts +4 -3
  53. package/dist/charts/chart.js +18 -10
  54. package/dist/charts/llms.txt +10 -7
  55. package/dist/charts-entry/public-api.js +2 -2
  56. package/dist/chat/chat.d.ts +17 -10
  57. package/dist/chat/chat.js +77 -23
  58. package/dist/chat/llms.txt +39 -15
  59. package/dist/chat-composer/chat-composer.d.ts +20 -7
  60. package/dist/chat-composer/chat-composer.js +29 -7
  61. package/dist/chat-composer/llms.txt +20 -11
  62. package/dist/chat-dock/chat-dock.d.ts +11 -4
  63. package/dist/chat-dock/chat-dock.js +10 -6
  64. package/dist/chat-dock/llms.txt +15 -14
  65. package/dist/chat-entry/public-api.js +1 -1
  66. package/dist/chat-message/chat-message.d.ts +12 -5
  67. package/dist/chat-message/chat-message.js +19 -8
  68. package/dist/chat-message/llms.txt +16 -15
  69. package/dist/chat-typing/chat-typing.d.ts +2 -2
  70. package/dist/chat-typing/chat-typing.js +4 -3
  71. package/dist/chat-typing/llms.txt +6 -6
  72. package/dist/checkbox/checkbox.d.ts +13 -6
  73. package/dist/checkbox/checkbox.js +31 -5
  74. package/dist/checkbox/llms.txt +26 -11
  75. package/dist/chip/chip.d.ts +4 -4
  76. package/dist/chip/chip.js +3 -2
  77. package/dist/chip/llms.txt +11 -11
  78. package/dist/chips-input/chips-input.d.ts +6 -4
  79. package/dist/chips-input/chips-input.js +47 -12
  80. package/dist/chips-input/llms.txt +18 -8
  81. package/dist/clock/clock.d.ts +11 -6
  82. package/dist/clock/clock.js +38 -8
  83. package/dist/clock/llms.txt +12 -8
  84. package/dist/color-picker/color-picker.d.ts +9 -5
  85. package/dist/color-picker/color-picker.js +37 -18
  86. package/dist/color-picker/llms.txt +8 -5
  87. package/dist/confirm/confirm-container.d.ts +3 -3
  88. package/dist/confirm/confirm-container.js +6 -5
  89. package/dist/confirm/confirm-store.d.ts +4 -4
  90. package/dist/confirm/confirm-store.js +11 -5
  91. package/dist/confirm/llms.txt +8 -7
  92. package/dist/context-menu/context-menu.d.ts +1 -1
  93. package/dist/context-menu/context-menu.js +37 -14
  94. package/dist/context-menu/llms.txt +10 -7
  95. package/dist/data-table/data-table-types.d.ts +5 -5
  96. package/dist/data-table/data-table.d.ts +37 -5
  97. package/dist/data-table/data-table.js +426 -119
  98. package/dist/data-table/export-csv.d.ts +3 -3
  99. package/dist/data-table/export-csv.js +8 -10
  100. package/dist/data-table/llms.txt +127 -79
  101. package/dist/data-view/data-view.d.ts +13 -5
  102. package/dist/data-view/data-view.js +68 -6
  103. package/dist/data-view/llms.txt +29 -10
  104. package/dist/date-range/date-range.d.ts +20 -8
  105. package/dist/date-range/date-range.js +86 -63
  106. package/dist/date-range/llms.txt +32 -18
  107. package/dist/datepicker/datepicker.d.ts +22 -8
  108. package/dist/datepicker/datepicker.js +103 -71
  109. package/dist/datepicker/llms.txt +36 -22
  110. package/dist/dialog/dialog.d.ts +4 -4
  111. package/dist/dialog/dialog.js +32 -11
  112. package/dist/dialog/llms.txt +13 -10
  113. package/dist/divider/divider.d.ts +7 -1
  114. package/dist/divider/divider.js +3 -2
  115. package/dist/divider/llms.txt +4 -3
  116. package/dist/drawer/drawer.d.ts +9 -4
  117. package/dist/drawer/drawer.js +5 -4
  118. package/dist/drawer/llms.txt +13 -10
  119. package/dist/empty/empty.d.ts +2 -2
  120. package/dist/empty/empty.js +1 -1
  121. package/dist/empty/llms.txt +9 -9
  122. package/dist/fab/fab.d.ts +1 -1
  123. package/dist/fab/fab.js +1 -1
  124. package/dist/fab/llms.txt +10 -6
  125. package/dist/fieldset/fieldset.d.ts +18 -4
  126. package/dist/fieldset/fieldset.js +12 -6
  127. package/dist/fieldset/llms.txt +26 -9
  128. package/dist/file-upload/file-upload.d.ts +8 -2
  129. package/dist/file-upload/file-upload.js +22 -11
  130. package/dist/file-upload/llms.txt +12 -9
  131. package/dist/form-field/form-field-context.d.ts +4 -4
  132. package/dist/form-field/form-field.d.ts +12 -4
  133. package/dist/form-field/form-field.js +27 -6
  134. package/dist/form-field/llms.txt +25 -11
  135. package/dist/galleria/galleria.d.ts +3 -3
  136. package/dist/galleria/galleria.js +9 -6
  137. package/dist/galleria/llms.txt +7 -7
  138. package/dist/grid/col.d.ts +4 -4
  139. package/dist/grid/col.js +2 -2
  140. package/dist/grid/flex.d.ts +3 -3
  141. package/dist/grid/flex.js +1 -1
  142. package/dist/grid/grid.d.ts +7 -7
  143. package/dist/grid/grid.js +3 -3
  144. package/dist/grid/layout.d.ts +2 -2
  145. package/dist/grid/layout.js +2 -2
  146. package/dist/grid/llms.txt +15 -15
  147. package/dist/grid-entry/public-api.js +1 -1
  148. package/dist/i18n/i18n-context.d.ts +6 -6
  149. package/dist/i18n/i18n-context.js +27 -5
  150. package/dist/icon/icon-registry.d.ts +3 -3
  151. package/dist/icon/icon-registry.js +3 -3
  152. package/dist/icon/icon.d.ts +4 -4
  153. package/dist/icon/icon.js +10 -7
  154. package/dist/icon/llms.txt +13 -13
  155. package/dist/icon-button/icon-button.d.ts +2 -2
  156. package/dist/icon-button/icon-button.js +1 -1
  157. package/dist/icon-button/llms.txt +9 -5
  158. package/dist/icons-entry/public-api.d.ts +2 -2
  159. package/dist/icons-entry/public-api.js +3 -3
  160. package/dist/image/image.js +5 -5
  161. package/dist/image/llms.txt +13 -13
  162. package/dist/input/input.d.ts +18 -5
  163. package/dist/input/input.js +22 -6
  164. package/dist/input/llms.txt +25 -11
  165. package/dist/input-mask/input-mask.d.ts +7 -2
  166. package/dist/input-mask/input-mask.js +17 -43
  167. package/dist/input-mask/llms.txt +8 -5
  168. package/dist/input-number/input-number.d.ts +11 -9
  169. package/dist/input-number/input-number.js +25 -21
  170. package/dist/input-number/llms.txt +17 -14
  171. package/dist/input-otp/input-otp.d.ts +18 -5
  172. package/dist/input-otp/input-otp.js +48 -24
  173. package/dist/input-otp/llms.txt +18 -7
  174. package/dist/internal/calendar-focus.d.ts +1 -1
  175. package/dist/internal/calendar-focus.js +3 -3
  176. package/dist/internal/cn.d.ts +1 -1
  177. package/dist/internal/mask.d.ts +14 -0
  178. package/dist/internal/mask.js +62 -0
  179. package/dist/internal/number-group.d.ts +22 -0
  180. package/dist/internal/number-group.js +53 -0
  181. package/dist/internal/option-key.d.ts +6 -0
  182. package/dist/internal/option-key.js +8 -0
  183. package/dist/internal/option-nav.d.ts +4 -4
  184. package/dist/internal/option-nav.js +5 -5
  185. package/dist/internal/overlay-container.js +1 -1
  186. package/dist/internal/overlay.d.ts +3 -3
  187. package/dist/internal/overlay.js +40 -10
  188. package/dist/internal/password-strength.d.ts +6 -0
  189. package/dist/internal/password-strength.js +74 -0
  190. package/dist/internal/roving.d.ts +2 -2
  191. package/dist/internal/roving.js +3 -3
  192. package/dist/internal/scroll-lock.d.ts +6 -0
  193. package/dist/internal/scroll-lock.js +35 -0
  194. package/dist/internal/slot-children.d.ts +13 -0
  195. package/dist/internal/slot-children.js +31 -0
  196. package/dist/internal/slot.d.ts +3 -3
  197. package/dist/internal/slot.js +2 -2
  198. package/dist/internal/step.d.ts +12 -0
  199. package/dist/internal/step.js +31 -0
  200. package/dist/internal/tween.d.ts +5 -5
  201. package/dist/internal/tween.js +7 -8
  202. package/dist/internal/types.d.ts +2 -2
  203. package/dist/internal/use-breakpoint.d.ts +2 -2
  204. package/dist/internal/use-breakpoint.js +4 -4
  205. package/dist/internal/use-controllable-state.d.ts +5 -0
  206. package/dist/internal/use-controllable-state.js +25 -4
  207. package/dist/internal/use-id.d.ts +2 -2
  208. package/dist/internal/use-id.js +2 -2
  209. package/dist/internal/use-modal-focus.d.ts +2 -1
  210. package/dist/internal/use-modal-focus.js +14 -3
  211. package/dist/knob/knob.d.ts +7 -4
  212. package/dist/knob/knob.js +27 -22
  213. package/dist/knob/llms.txt +18 -11
  214. package/dist/listbox/listbox.d.ts +8 -6
  215. package/dist/listbox/listbox.js +37 -17
  216. package/dist/listbox/llms.txt +12 -9
  217. package/dist/loader/llms.txt +56 -0
  218. package/dist/loader/loader.d.ts +49 -0
  219. package/dist/loader/loader.js +68 -0
  220. package/dist/map/llms.txt +30 -23
  221. package/dist/map/map-create-dialog.d.ts +4 -4
  222. package/dist/map/map-create-dialog.js +36 -28
  223. package/dist/map/map-hours-field.d.ts +4 -4
  224. package/dist/map/map-hours-field.js +8 -7
  225. package/dist/map/map-image-field.d.ts +2 -2
  226. package/dist/map/map-image-field.js +6 -5
  227. package/dist/map/map-legend.d.ts +4 -4
  228. package/dist/map/map-legend.js +3 -2
  229. package/dist/map/map-poi-card.d.ts +3 -3
  230. package/dist/map/map-poi-card.js +10 -9
  231. package/dist/map/map-search.d.ts +17 -6
  232. package/dist/map/map-search.js +70 -43
  233. package/dist/map/map.d.ts +25 -13
  234. package/dist/map/map.js +86 -57
  235. package/dist/map/ports.d.ts +9 -9
  236. package/dist/map/ports.js +6 -6
  237. package/dist/map-entry/public-api.js +2 -2
  238. package/dist/megamenu/llms.txt +18 -7
  239. package/dist/megamenu/megamenu.d.ts +12 -6
  240. package/dist/megamenu/megamenu.js +23 -14
  241. package/dist/mention/llms.txt +15 -15
  242. package/dist/mention/mention.d.ts +5 -5
  243. package/dist/mention/mention.js +2 -2
  244. package/dist/mention/mentions.d.ts +1 -1
  245. package/dist/mention/mentions.js +1 -1
  246. package/dist/menu/llms.txt +12 -12
  247. package/dist/menu/menu-item.d.ts +4 -4
  248. package/dist/menu/menu-keys.d.ts +2 -2
  249. package/dist/menu/menu-keys.js +5 -5
  250. package/dist/menu/menu-rows.js +1 -0
  251. package/dist/menu/menu.d.ts +2 -2
  252. package/dist/menu/menu.js +15 -8
  253. package/dist/menubar/llms.txt +19 -8
  254. package/dist/menubar/menubar.d.ts +11 -5
  255. package/dist/menubar/menubar.js +23 -11
  256. package/dist/meter-group/llms.txt +12 -12
  257. package/dist/meter-group/meter-group.d.ts +2 -2
  258. package/dist/meter-group/meter-group.js +2 -1
  259. package/dist/motion/motion-context.d.ts +22 -0
  260. package/dist/motion/motion-context.js +28 -0
  261. package/dist/order-list/llms.txt +24 -9
  262. package/dist/order-list/order-list.d.ts +12 -4
  263. package/dist/order-list/order-list.js +123 -25
  264. package/dist/paginator/llms.txt +8 -8
  265. package/dist/paginator/paginator.js +7 -4
  266. package/dist/panel/llms.txt +25 -13
  267. package/dist/panel/panel.d.ts +11 -3
  268. package/dist/panel/panel.js +12 -7
  269. package/dist/password/llms.txt +19 -7
  270. package/dist/password/password.d.ts +2 -2
  271. package/dist/password/password.js +27 -16
  272. package/dist/peek/llms.txt +11 -11
  273. package/dist/peek/peek.d.ts +6 -6
  274. package/dist/peek/peek.js +41 -19
  275. package/dist/pick-list/llms.txt +35 -12
  276. package/dist/pick-list/pick-list.d.ts +12 -4
  277. package/dist/pick-list/pick-list.js +128 -24
  278. package/dist/popover/llms.txt +13 -10
  279. package/dist/popover/popover.d.ts +6 -4
  280. package/dist/popover/popover.js +14 -7
  281. package/dist/progress/llms.txt +17 -17
  282. package/dist/progress/progress.d.ts +7 -7
  283. package/dist/progress/progress.js +7 -6
  284. package/dist/public-api.d.ts +16 -4
  285. package/dist/public-api.js +13 -7
  286. package/dist/radio-group/llms.txt +16 -6
  287. package/dist/radio-group/radio-group.d.ts +12 -3
  288. package/dist/radio-group/radio-group.js +23 -4
  289. package/dist/rating/llms.txt +20 -13
  290. package/dist/rating/rating-pointer.d.ts +16 -0
  291. package/dist/rating/rating-pointer.js +26 -0
  292. package/dist/rating/rating.d.ts +6 -6
  293. package/dist/rating/rating.js +77 -12
  294. package/dist/rich-text/lists.d.ts +2 -2
  295. package/dist/rich-text/lists.js +7 -7
  296. package/dist/rich-text/llms.txt +35 -12
  297. package/dist/rich-text/rich-text.d.ts +13 -14
  298. package/dist/rich-text/rich-text.js +311 -142
  299. package/dist/rich-text/sanitize.d.ts +11 -2
  300. package/dist/rich-text/sanitize.js +80 -32
  301. package/dist/rich-text/tables.d.ts +3 -3
  302. package/dist/rich-text/tables.js +3 -3
  303. package/dist/roadmap/llms.txt +23 -17
  304. package/dist/roadmap/roadmap.d.ts +6 -6
  305. package/dist/roadmap/roadmap.js +102 -25
  306. package/dist/roadmap-entry/public-api.js +2 -2
  307. package/dist/scroll-top/llms.txt +6 -6
  308. package/dist/scroll-top/scroll-top.d.ts +1 -1
  309. package/dist/scroll-top/scroll-top.js +2 -1
  310. package/dist/select/llms.txt +22 -12
  311. package/dist/select/select.d.ts +10 -8
  312. package/dist/select/select.js +87 -47
  313. package/dist/shared/types.d.ts +22 -1
  314. package/dist/shared/types.js +9 -1
  315. package/dist/sidenav/llms.txt +9 -9
  316. package/dist/sidenav/sidenav.d.ts +2 -3
  317. package/dist/sidenav/sidenav.js +5 -4
  318. package/dist/skeleton/llms.txt +3 -3
  319. package/dist/skeleton/skeleton.d.ts +1 -1
  320. package/dist/skeleton/skeleton.js +1 -1
  321. package/dist/slider/llms.txt +34 -12
  322. package/dist/slider/slider-drag.d.ts +26 -0
  323. package/dist/slider/slider-drag.js +79 -0
  324. package/dist/slider/slider.d.ts +8 -5
  325. package/dist/slider/slider.js +38 -8
  326. package/dist/speech/llms.txt +97 -0
  327. package/dist/speech/speech-button.d.ts +44 -0
  328. package/dist/speech/speech-button.js +86 -0
  329. package/dist/speech/speech-wave.d.ts +19 -0
  330. package/dist/speech/speech-wave.js +16 -0
  331. package/dist/speech/use-speech-recognition.d.ts +81 -0
  332. package/dist/speech/use-speech-recognition.js +179 -0
  333. package/dist/speed-dial/llms.txt +14 -8
  334. package/dist/speed-dial/speed-dial.d.ts +1 -1
  335. package/dist/speed-dial/speed-dial.js +3 -2
  336. package/dist/spinner/llms.txt +7 -7
  337. package/dist/spinner/spinner.d.ts +1 -1
  338. package/dist/spinner/spinner.js +2 -1
  339. package/dist/split-button/llms.txt +21 -15
  340. package/dist/split-button/split-button.d.ts +2 -2
  341. package/dist/split-button/split-button.js +13 -6
  342. package/dist/splitter/llms.txt +11 -11
  343. package/dist/splitter/splitter.d.ts +3 -3
  344. package/dist/splitter/splitter.js +25 -10
  345. package/dist/stepper/llms.txt +35 -13
  346. package/dist/stepper/stepper.d.ts +6 -6
  347. package/dist/stepper/stepper.js +60 -13
  348. package/dist/switch/llms.txt +15 -5
  349. package/dist/switch/switch.d.ts +10 -3
  350. package/dist/switch/switch.js +18 -4
  351. package/dist/switch-group/llms.txt +11 -8
  352. package/dist/switch-group/switch-group.d.ts +6 -4
  353. package/dist/switch-group/switch-group.js +9 -7
  354. package/dist/tabs/llms.txt +12 -9
  355. package/dist/tabs/tabs.d.ts +5 -5
  356. package/dist/tabs/tabs.js +36 -20
  357. package/dist/textarea/llms.txt +23 -12
  358. package/dist/textarea/textarea.d.ts +19 -6
  359. package/dist/textarea/textarea.js +22 -7
  360. package/dist/timeline/llms.txt +4 -4
  361. package/dist/timeline/timeline.d.ts +1 -1
  362. package/dist/timeline/timeline.js +1 -1
  363. package/dist/toast/llms.txt +50 -17
  364. package/dist/toast/toast-container.d.ts +9 -3
  365. package/dist/toast/toast-container.js +18 -9
  366. package/dist/toast/toast-store.d.ts +42 -2
  367. package/dist/toast/toast-store.js +36 -1
  368. package/dist/toolbar/llms.txt +7 -7
  369. package/dist/toolbar/toolbar.d.ts +3 -3
  370. package/dist/toolbar/toolbar.js +2 -2
  371. package/dist/tooltip/llms.txt +14 -12
  372. package/dist/tooltip/tooltip.d.ts +5 -4
  373. package/dist/tooltip/tooltip.js +100 -41
  374. package/dist/tree/llms.txt +25 -11
  375. package/dist/tree/tree-keys.d.ts +5 -5
  376. package/dist/tree/tree-keys.js +7 -7
  377. package/dist/tree/tree.d.ts +17 -5
  378. package/dist/tree/tree.js +62 -20
  379. package/dist/update/llms.txt +54 -0
  380. package/dist/update/update.d.ts +49 -0
  381. package/dist/update/update.js +82 -0
  382. package/package.json +18 -15
@@ -1,10 +1,13 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react';
3
- import { aggregate, isEmptyFilterValue, matchesColumnFilter, toText, } from '@konce-pt/table';
3
+ import { useEffect, useEffectEvent, useId, useLayoutEffect, useMemo, useRef, useState, } from 'react';
4
+ import { aggregate, columnOffsets, createGlobalFilter, cssLengthToPx, isEmptyFilterValue, matchesColumnFilter, sortRows, toText, virtualColumnRange, virtualRange, } from '@konce-pt/table';
4
5
  import { cn } from "../internal/cn.js";
5
6
  import { useControllableState } from "../internal/use-controllable-state.js";
6
7
  import { useKptMessages } from "../i18n/i18n-context.js";
7
8
  import { KptButton } from "../button/button.js";
9
+ import { KptSpeechButton } from "../speech/speech-button.js";
10
+ import { useKptSpeechRecognition } from "../speech/use-speech-recognition.js";
8
11
  import { KptDatepicker } from "../datepicker/datepicker.js";
9
12
  import { KptDateRange } from "../date-range/date-range.js";
10
13
  import { KptIcon } from "../icon/icon.js";
@@ -17,8 +20,31 @@ import { exportTableCsv } from "./export-csv.js";
17
20
  const REM_PX = 16;
18
21
  const CHECKBOX_FROZEN_W = 3; // rem
19
22
  const EXPANDER_FROZEN_W = 2.5; // rem
20
- const VIRTUAL_BUFFER = 6;
21
- /** Edytory z panelem w nakładce — otwierane od razu i zatwierdzane na zamknięciu panelu. */
23
+ /** Wiersze dorenderowane nad i pod widocznym oknem. */
24
+ const ROW_OVERSCAN = 6;
25
+ /** Kolumny dorenderowane po obu stronach widocznego okna. */
26
+ const COLUMN_OVERSCAN = 2;
27
+ /** Powyżej tylu wierszy filtr globalny czeka na przerwę w pisaniu, zamiast liczyć co znak. */
28
+ const FILTER_DEBOUNCE_ROWS = 2000;
29
+ const FILTER_DEBOUNCE_MS = 150;
30
+ /** Kawałek rozgrzewania indeksu wyszukiwania na jedno wywołanie w czasie bezczynności. */
31
+ const WARMUP_SLICE_MS = 8;
32
+ function scheduleIdle(fn) {
33
+ return typeof requestIdleCallback === 'function' ? { idle: requestIdleCallback(fn) } : { timer: setTimeout(fn, 16) };
34
+ }
35
+ function cancelIdle(handle) {
36
+ if (!handle)
37
+ return;
38
+ if ('idle' in handle)
39
+ cancelIdleCallback(handle.idle);
40
+ else
41
+ clearTimeout(handle.timer);
42
+ }
43
+ /** Okna równe, gdy obejmują te same elementy - przewinięcie w obrębie okna nie budzi renderu. */
44
+ function sameWindow(a, b) {
45
+ return a.start === b.start && a.end === b.end && a.before === b.before && a.after === b.after;
46
+ }
47
+ /** Edytory z panelem w nakładce - otwierane od razu i zatwierdzane na zamknięciu panelu. */
22
48
  const OVERLAY_EDITORS = new Set(['date', 'select', 'multiselect']);
23
49
  /** Tablice (multiselect) porównujemy po zawartości, resztę po referencji. */
24
50
  function sameEditValue(a, b) {
@@ -28,6 +54,16 @@ function sameEditValue(a, b) {
28
54
  return a.length === b.length && a.every((v, i) => v === b[i]);
29
55
  return false;
30
56
  }
57
+ /**
58
+ * Szerokość kolumny w px: z jednostki (`px`/`rem`/`em`), a gdy się nie da - w trybie wirtualnym
59
+ * `columnWidth`, poza nim pomiar z wyrenderowanego nagłówka, a do pierwszego pomiaru 8 rem.
60
+ */
61
+ function columnPx(col, virtual, defaultPx, measured) {
62
+ const fromUnit = cssLengthToPx(col.width, REM_PX);
63
+ if (fromUnit !== null)
64
+ return fromUnit;
65
+ return virtual ? defaultPx : (measured.get(col.field) ?? 8 * REM_PX);
66
+ }
31
67
  const EMPTY_ROWS = [];
32
68
  const EMPTY_FIELDS = [];
33
69
  /**
@@ -46,8 +82,14 @@ const EMPTY_FIELDS = [];
46
82
  function getValue(row, field) {
47
83
  return row[field];
48
84
  }
49
- export function KptDataTable({ columns, data = EMPTY_ROWS, filterable = false, filterRowPosition = 'bottom', pageSize = 0, pageSizeOptions = [5, 10, 20, 50], selectable = 'none', loading = false, emptyMessage, rowKey = null, scrollHeight = null, virtualScroll = false, rowHeight = 41, reorderable = false, exportable = false, exportFilename = 'export.csv', lazy = false, totalRecords = null, expandable = false, multiExpand = true, summaryLabel, summaryValues = null, selection, defaultSelection = EMPTY_ROWS, onSelectionChange, expandedRows, defaultExpandedRows = EMPTY_ROWS, onExpandedRowsChange, hiddenFields, defaultHiddenFields = EMPTY_FIELDS, onHiddenFieldsChange, onRowClick, onLazyLoad, onCellEdit, cells, cellEditors, renderRowDetail, paginatorActions, locale, dictionary, className, style, ...rest }) {
50
- const { t } = useKptMessages({ locale, dictionary });
85
+ export function KptDataTable({ columns, data = EMPTY_ROWS, filterable = false, filterSpeech = false, filterSpeechLang, filterSpeechColor, filterRowPosition = 'bottom', pageSize = 0, pageSizeOptions = [5, 10, 20, 50], selectable = 'none', loading = false, ariaLabel = null, caption = null, clickableRows, emptyMessage, rowKey = null, scrollHeight = null, virtualScroll = false, columnWidth = '10rem', rowHeight = 41, reorderable = false, frozenSystemColumns = false, exportable = false, exportFilename = 'export.csv', lazy = false, totalRecords = null, expandable = false, multiExpand = true, summaryLabel, summaryValues = null, selection, defaultSelection = EMPTY_ROWS, onSelectionChange, expandedRows, defaultExpandedRows = EMPTY_ROWS, onExpandedRowsChange, hiddenFields, defaultHiddenFields = EMPTY_FIELDS, onHiddenFieldsChange, onRowClick, onLazyLoad, onCellEdit, cells, cellEditors, renderRowDetail, paginatorActions, locale, dictionary, className, style, ...rest }) {
86
+ const { t, locale: activeLocale } = useKptMessages({ locale, dictionary });
87
+ const uid = useId();
88
+ const rowsClickable = clickableRows ?? onRowClick !== undefined;
89
+ /** Ogłoszenie po przestawieniu kolumny z klawiatury. */
90
+ const [moveAnnouncement, setMoveAnnouncement] = useState('');
91
+ /** Pole kolumny, której uchwyt ma dostać fokus po przerysowaniu (ustawiane w zdarzeniu, czytane w efekcie). */
92
+ const focusGrip = useRef(null);
51
93
  const [picked, setPicked] = useControllableState({
52
94
  value: selection,
53
95
  defaultValue: defaultSelection,
@@ -58,28 +100,54 @@ export function KptDataTable({ columns, data = EMPTY_ROWS, filterable = false, f
58
100
  defaultValue: defaultExpandedRows,
59
101
  onChange: onExpandedRowsChange,
60
102
  });
61
- const [hidden, setHidden] = useControllableState({
103
+ const [hidden] = useControllableState({
62
104
  value: hiddenFields,
63
105
  defaultValue: defaultHiddenFields,
64
106
  onChange: onHiddenFieldsChange,
65
107
  });
66
108
  const [filterText, setFilterText] = useState('');
109
+ /** Treść pola wyszukiwania - przy dużych zbiorach wyprzedza `filterText` o czas debounce. */
110
+ const [filterInput, setFilterInput] = useState('');
111
+ // Filtr głosem: pole czyści się po kliknięciu, a fraza wchodzi przez `onChange` jak wpisana -
112
+ // z tym samym opóźnieniem dla dużych danych. Bez `filterSpeech` kontroler nie tworzy sesji.
113
+ const filterRef = useRef(null);
114
+ const filterMic = useKptSpeechRecognition({
115
+ enabled: filterable && filterSpeech,
116
+ target: filterRef,
117
+ lang: filterSpeechLang,
118
+ mode: 'replace',
119
+ continuous: false,
120
+ });
121
+ const filterSpeechState = filterable && filterSpeech && filterMic.available ? (filterMic.listening ? 'listening' : 'idle') : undefined;
122
+ const filterTimer = useRef(undefined);
67
123
  const [columnFilters, setColumnFilters] = useState({});
68
124
  const [sortField, setSortField] = useState(null);
69
125
  const [sortDir, setSortDir] = useState(null);
70
126
  const [pageIndex, setPageIndex] = useState(0);
71
127
  const [pageSizeState, setPageSizeState] = useState(pageSize || 10);
128
+ // Nowy `pageSize` od rodzica zastępuje rozmiar wybrany w paginatorze - jak `linkedSignal` w Angularze.
129
+ const [seenPageSize, setSeenPageSize] = useState(pageSize);
130
+ if (seenPageSize !== pageSize) {
131
+ setSeenPageSize(pageSize);
132
+ setPageSizeState(pageSize || 10);
133
+ setPageIndex(0);
134
+ }
72
135
  // Kolejność kolumn żyje w stanie (przestawianie), ale nowe `columns` z zewnątrz ją zastępują.
136
+ // Poprzednie `columns` trzymamy w stanie, nie w refie - wzorzec "zmiana propsa resetuje stan"
137
+ // z react.dev; ref zależałby od tego, czy React nie wyrzucił renderu (StrictMode, concurrent).
73
138
  const [order, setOrder] = useState(columns);
74
- const columnsRef = useRef(columns);
75
- if (columnsRef.current !== columns) {
76
- columnsRef.current = columns;
77
- if (order !== columns)
78
- setOrder(columns);
139
+ const [seenColumns, setSeenColumns] = useState(columns);
140
+ if (seenColumns !== columns) {
141
+ setSeenColumns(columns);
142
+ setOrder(columns);
79
143
  }
144
+ /** Zmierzone szerokości kolumn zamrożonych bez szerokości liczalnej z jednostki (`%`, `ch`). */
145
+ const [measuredWidths, setMeasuredWidths] = useState(() => new Map());
80
146
  const [dragField, setDragField] = useState(null);
81
- const [scrollTop, setScrollTop] = useState(0);
82
- const [viewportHeight, setViewportHeight] = useState(400);
147
+ /** Przewinięcie zapisane wtedy, gdy zmienia okno wierszy albo kolumn - nie przy każdym pikselu. */
148
+ const [scroll, setScroll] = useState({ top: 0, left: 0 });
149
+ const [viewport, setViewport] = useState({ height: 400, width: 1200 });
150
+ const scrollFrame = useRef(0);
83
151
  const [theadOffset, setTheadOffset] = useState(0);
84
152
  const scrollRef = useRef(null);
85
153
  const theadRef = useRef(null);
@@ -91,12 +159,19 @@ export function KptDataTable({ columns, data = EMPTY_ROWS, filterable = false, f
91
159
  const hasColumnFilters = columns.some((col) => col.filter);
92
160
  const hasRowDetail = expandable && !!renderRowDetail;
93
161
  const sameRow = (a, b) => rowKey ? getValue(a, rowKey) === getValue(b, rowKey) : a === b;
94
- // ── Potok danych ──
162
+ /** Tożsamość wiersza: wartość `rowKey` albo sam wiersz - klucz zbiorów zaznaczenia i rozwinięcia. */
163
+ const keyOf = (row) => (rowKey ? getValue(row, rowKey) : row);
164
+ // -- Potok danych --
165
+ /**
166
+ * Filtr globalny z pamięcią złożonego tekstu wiersza (`@konce-pt/table`) - nowy przy zmianie
167
+ * kolumn, więc kolejne znaki frazy nie składają od nowa 20 000 x 100 komórek.
168
+ */
169
+ const globalFilter = useMemo(() => createGlobalFilter(columns.map((col) => col.field)), [columns]);
95
170
  const filtered = useMemo(() => {
96
- const query = filterText.trim().toLowerCase();
97
171
  let rows = data;
98
- if (query) {
99
- rows = rows.filter((row) => columns.some((col) => toText(getValue(row, col.field)).toLowerCase().includes(query)));
172
+ if (filterText.trim()) {
173
+ // Bez diakrytyków i wielkości liter - "lodz" znajduje "Łódź" (`@konce-pt/table`).
174
+ rows = rows.filter((row) => globalFilter(row, filterText));
100
175
  }
101
176
  if (hasColumnFilters) {
102
177
  rows = rows.filter((row) => columns.every((col) => {
@@ -109,24 +184,38 @@ export function KptDataTable({ columns, data = EMPTY_ROWS, filterable = false, f
109
184
  }));
110
185
  }
111
186
  return rows;
112
- }, [columnFilters, columns, data, filterText, hasColumnFilters]);
187
+ }, [columnFilters, columns, data, filterText, globalFilter, hasColumnFilters]);
113
188
  const sorted = useMemo(() => {
114
189
  if (!sortField || !sortDir)
115
190
  return filtered;
116
- const sign = sortDir === 'asc' ? 1 : -1;
117
- return [...filtered].sort((a, b) => {
118
- const av = getValue(a, sortField);
119
- const bv = getValue(b, sortField);
120
- // Puste komórki lądują na końcu niezależnie od kierunku — sortowanie ma pokazywać dane.
121
- if (av == null)
122
- return 1;
123
- if (bv == null)
124
- return -1;
125
- if (typeof av === 'number' && typeof bv === 'number')
126
- return (av - bv) * sign;
127
- return toText(av).localeCompare(toText(bv)) * sign;
128
- });
129
- }, [filtered, sortDir, sortField]);
191
+ // Kolator dla aktywnego języka, klucze liczone raz na wiersz; puste komórki na końcu.
192
+ return sortRows(filtered, sortField, sortDir, activeLocale);
193
+ }, [activeLocale, filtered, sortDir, sortField]);
194
+ // Duży zbiór z wyszukiwaniem: tekst wierszy składamy w tle, kawałkami po kilka ms w czasie
195
+ // bezczynności - inaczej pierwszy znak frazy blokowałby stronę na cały zbiór naraz.
196
+ useEffect(() => {
197
+ if (!filterable || lazy)
198
+ return;
199
+ const rows = data;
200
+ if (rows.length <= FILTER_DEBOUNCE_ROWS)
201
+ return;
202
+ let index = 0;
203
+ let handle = null;
204
+ const step = () => {
205
+ const until = performance.now() + WARMUP_SLICE_MS;
206
+ while (index < rows.length && performance.now() < until)
207
+ globalFilter.prepare(rows[index++]);
208
+ if (index < rows.length)
209
+ handle = scheduleIdle(step);
210
+ };
211
+ handle = scheduleIdle(step);
212
+ return () => cancelIdle(handle);
213
+ }, [data, filterable, globalFilter, lazy]);
214
+ // Odmontowanie: porzucona klatka odczytu przewinięcia i czekający debounce filtra.
215
+ useEffect(() => () => {
216
+ cancelAnimationFrame(scrollFrame.current);
217
+ clearTimeout(filterTimer.current);
218
+ }, []);
130
219
  const paged = useMemo(() => {
131
220
  if (lazy)
132
221
  return data;
@@ -136,56 +225,77 @@ export function KptDataTable({ columns, data = EMPTY_ROWS, filterable = false, f
136
225
  return sorted.slice(start, start + pageSizeState);
137
226
  }, [data, lazy, pageIndex, pageSize, pageSizeState, sorted]);
138
227
  const filteredCount = lazy ? (totalRecords ?? data.length) : filtered.length;
139
- // Tryb lazy: każda zmiana stanu to prośba do rodzica o dane.
228
+ // Tryb lazy: każda zmiana stanu to prośba do rodzica o dane. `onLazyLoad` czytamy przez
229
+ // `useEffectEvent`: nowa referencja funkcji przy każdym renderze rodzica nie może restartować
230
+ // efektu, bo wywołałaby pętlę żądań.
231
+ const requestLazyLoad = useEffectEvent((state) => onLazyLoad?.(state));
140
232
  useEffect(() => {
141
233
  if (!lazy)
142
234
  return;
143
- onLazyLoad?.({ pageIndex, pageSize: pageSizeState, sortField, sortDir, filter: filterText, columnFilters });
144
- // `onLazyLoad` celowo poza zależnościami: nowa referencja funkcji przy każdym renderze
145
- // rodzica wywołałaby pętlę żądań.
146
- // eslint-disable-next-line react-hooks/exhaustive-deps
235
+ requestLazyLoad({ pageIndex, pageSize: pageSizeState, sortField, sortDir, filter: filterText, columnFilters });
147
236
  }, [lazy, pageIndex, pageSizeState, sortField, sortDir, filterText, columnFilters]);
148
237
  // Offset sticky drugiego wiersza nagłówka = wysokość pierwszego.
149
238
  useLayoutEffect(() => {
150
239
  const first = theadRef.current?.rows?.[0];
240
+ // Dokładna wysokość, nie `offsetHeight`: ten zaokrągla (46,5 -> 47) i przy skali ekranu innej
241
+ // niż 100% między wierszami nagłówka zostawała szpara, przez którą prześwitywały wiersze.
151
242
  if (first)
152
- setTheadOffset(first.offsetHeight);
243
+ setTheadOffset(first.getBoundingClientRect().height);
153
244
  }, [filterRowPosition, hasColumnFilters, visibleColumns]);
245
+ // Rozmiar viewportu dla wirtualizacji - także po zmianie rozmiaru kontenera bez przewinięcia.
154
246
  useLayoutEffect(() => {
155
247
  const el = scrollRef.current;
156
- if (el)
157
- setViewportHeight(el.clientHeight);
248
+ if (!el)
249
+ return;
250
+ const measure = () => setViewport((prev) => prev.height === el.clientHeight && prev.width === el.clientWidth
251
+ ? prev
252
+ : { height: el.clientHeight, width: el.clientWidth });
253
+ measure();
254
+ if (typeof ResizeObserver === 'undefined')
255
+ return;
256
+ const observer = new ResizeObserver(measure);
257
+ observer.observe(el);
258
+ return () => observer.disconnect();
158
259
  }, []);
159
- // Wejście w edycję → fokus na kontrolce edytora.
260
+ // Szerokości kolumn zamrożonych, których nie da się policzyć z jednostki - mierzone z nagłówków.
261
+ // Stan zmienia się tylko przy innej wartości, więc pomiar nie zapętla renderu.
262
+ useLayoutEffect(() => {
263
+ const head = theadRef.current;
264
+ if (!head)
265
+ return;
266
+ const next = new Map();
267
+ for (const col of visibleColumns) {
268
+ if (!col.frozen)
269
+ break;
270
+ if (cssLengthToPx(col.width, REM_PX) !== null)
271
+ continue;
272
+ const th = head.querySelector(`th[data-field="${CSS.escape(col.field)}"]`);
273
+ if (th)
274
+ next.set(col.field, th.offsetWidth);
275
+ }
276
+ setMeasuredWidths((prev) => prev.size === next.size && [...next].every(([key, value]) => prev.get(key) === value) ? prev : next);
277
+ }, [visibleColumns]);
278
+ // Po przestawieniu kolumny z klawiatury uchwyt jest przerysowany w nowym miejscu - fokus idzie za nim.
279
+ useLayoutEffect(() => {
280
+ const field = focusGrip.current;
281
+ if (field === null)
282
+ return;
283
+ focusGrip.current = null;
284
+ theadRef.current
285
+ ?.querySelector(`th[data-field="${CSS.escape(field)}"] .kpt-data-table__grip`)
286
+ ?.focus();
287
+ }, [order]);
288
+ // Wejście w edycję -> fokus na kontrolce edytora.
160
289
  useEffect(() => {
161
290
  if (!editingCell)
162
291
  return;
163
292
  editorHostRef.current?.querySelector('input, .kpt-select, [tabindex]')?.focus();
164
293
  }, [editingCell]);
165
- // ── Wirtualizacja ──
166
- const virtualStart = Math.max(0, Math.floor(scrollTop / rowHeight) - VIRTUAL_BUFFER);
167
- const virtualEnd = Math.min(sorted.length, virtualStart + Math.ceil(viewportHeight / rowHeight) + VIRTUAL_BUFFER * 2);
168
- const virtualRows = sorted.slice(virtualStart, virtualEnd);
169
- const topPad = virtualStart * rowHeight;
170
- const bottomPad = (sorted.length - virtualEnd) * rowHeight;
171
- const onScroll = (event) => {
172
- if (!virtualScroll)
173
- return;
174
- const el = event.currentTarget;
175
- setScrollTop(el.scrollTop);
176
- setViewportHeight(el.clientHeight);
177
- };
178
- // ── Kolumny zamrożone ──
179
- const parseWidthPx = (width) => {
180
- if (!width)
181
- return 8 * REM_PX;
182
- const n = Number.parseFloat(width);
183
- if (Number.isNaN(n))
184
- return 8 * REM_PX;
185
- return width.trim().endsWith('px') ? n : n * REM_PX;
186
- };
294
+ // -- Kolumny zamrożone --
187
295
  const leadingCells = (selectable !== 'none' ? 1 : 0) + (hasRowDetail ? 1 : 0);
188
296
  const leadingStickyWidth = (hasRowDetail ? EXPANDER_FROZEN_W * REM_PX : 0) + (selectable !== 'none' ? CHECKBOX_FROZEN_W * REM_PX : 0);
297
+ /** `columnWidth` w px; nieliczalna jednostka wraca do 10 rem. */
298
+ const defaultColumnPx = cssLengthToPx(columnWidth, REM_PX) ?? 10 * REM_PX;
189
299
  const frozenOffsets = useMemo(() => {
190
300
  const map = new Map();
191
301
  let acc = leadingStickyWidth;
@@ -193,15 +303,108 @@ export function KptDataTable({ columns, data = EMPTY_ROWS, filterable = false, f
193
303
  if (!col.frozen)
194
304
  break; // tylko wiodące, sąsiadujące
195
305
  map.set(col.field, acc);
196
- acc += parseWidthPx(col.width);
306
+ acc += columnPx(col, virtualScroll, defaultColumnPx, measuredWidths);
197
307
  }
198
308
  return map;
199
- }, [leadingStickyWidth, visibleColumns]);
200
- const hasFrozen = frozenOffsets.size > 0;
201
- const frozenLeft = (field) => frozenOffsets.get(field);
309
+ }, [defaultColumnPx, leadingStickyWidth, measuredWidths, virtualScroll, visibleColumns]);
310
+ /** Czy cokolwiek jest przyklejone do lewej - kolumny `frozen` albo same systemowe. */
311
+ const hasFrozen = frozenOffsets.size > 0 || (frozenSystemColumns && leadingCells > 0);
202
312
  const expanderStickyLeft = hasFrozen ? 0 : undefined;
203
313
  const checkboxStickyLeft = hasFrozen ? (hasRowDetail ? EXPANDER_FROZEN_W * REM_PX : 0) : undefined;
204
- // ── Przestawianie kolumn ──
314
+ // -- Kolumny do renderowania (metadane + okno wirtualizacji) --
315
+ /** Widoczne kolumny z cechami policzonymi raz - nie przy każdej komórce każdego wiersza. */
316
+ const columnMeta = useMemo(() => visibleColumns.map((col, index) => ({
317
+ col,
318
+ index,
319
+ editable: !!col.edit || !!cellEditors?.[col.field],
320
+ frozen: frozenOffsets.has(col.field),
321
+ left: frozenOffsets.get(col.field),
322
+ align: col.align ?? 'start',
323
+ width: columnPx(col, virtualScroll, defaultColumnPx, measuredWidths),
324
+ })), [cellEditors, defaultColumnPx, frozenOffsets, measuredWidths, virtualScroll, visibleColumns]);
325
+ /** Kolumny przewijalne (za wiodącymi zamrożonymi) i ich sumy prefiksowe szerokości. */
326
+ const scrollable = useMemo(() => {
327
+ const pinned = columnMeta.filter((m) => m.frozen);
328
+ const rest = columnMeta.slice(pinned.length);
329
+ // Kolumny systemowe zasłaniają lewą krawędź okna tylko wtedy, gdy są przyklejone.
330
+ const leading = hasFrozen ? leadingStickyWidth : 0;
331
+ const pinnedWidth = leading + pinned.reduce((sum, m) => sum + m.width, 0);
332
+ return { pinned, rest, pinnedWidth, offsets: columnOffsets(rest.map((m) => m.width)) };
333
+ }, [columnMeta, hasFrozen, leadingStickyWidth]);
334
+ // -- Wirtualizacja --
335
+ const rowWindowAt = (top) => virtualRange({
336
+ offset: top,
337
+ viewport: viewport.height,
338
+ itemSize: rowHeight,
339
+ count: sorted.length,
340
+ overscan: ROW_OVERSCAN,
341
+ });
342
+ const columnWindowAt = (left) => virtualScroll
343
+ ? virtualColumnRange({
344
+ offsets: scrollable.offsets,
345
+ scrollLeft: left,
346
+ viewport: viewport.width,
347
+ overscan: COLUMN_OVERSCAN,
348
+ pinnedWidth: scrollable.pinnedWidth,
349
+ })
350
+ : { start: 0, end: scrollable.rest.length, before: 0, after: 0 };
351
+ const rowWindow = rowWindowAt(scroll.top);
352
+ const columnWindow = columnWindowAt(scroll.left);
353
+ const virtualRows = sorted.slice(rowWindow.start, rowWindow.end);
354
+ const topPad = rowWindow.before;
355
+ const bottomPad = rowWindow.after;
356
+ // Odczyt przewinięcia w `requestAnimationFrame` porównuje okna z tymi z ostatniego renderu -
357
+ // obie strony muszą widzieć bieżące dane, więc odświeżamy je po każdym renderze.
358
+ const windowsAt = useRef(null);
359
+ const shownWindows = useRef(null);
360
+ useLayoutEffect(() => {
361
+ windowsAt.current = (top, left) => ({ row: rowWindowAt(top), col: columnWindowAt(left) });
362
+ shownWindows.current = { row: rowWindow, col: columnWindow };
363
+ });
364
+ /** Jedna klatka na odczyt przewinięcia, a render tylko wtedy, gdy zmienia się okno wierszy lub kolumn. */
365
+ const onScroll = (event) => {
366
+ if (!virtualScroll || scrollFrame.current)
367
+ return;
368
+ const el = event.currentTarget;
369
+ scrollFrame.current = requestAnimationFrame(() => {
370
+ scrollFrame.current = 0;
371
+ const top = el.scrollTop;
372
+ const left = el.scrollLeft;
373
+ const next = windowsAt.current?.(top, left);
374
+ const shown = shownWindows.current;
375
+ if (next && shown && sameWindow(next.row, shown.row) && sameWindow(next.col, shown.col))
376
+ return;
377
+ setScroll({ top, left });
378
+ });
379
+ };
380
+ /** Nowy wynik filtra lub sortowania zaczyna się od góry - stare przewinięcie wskazywałoby w środek innego zbioru. */
381
+ const scrollToTop = () => {
382
+ const el = scrollRef.current;
383
+ if (virtualScroll && el)
384
+ el.scrollTop = 0;
385
+ };
386
+ /** Pozycje wiersza: kolumny zamrożone, dystansownik, okno kolumn, dystansownik. */
387
+ const slots = scrollable.pinned.map((m) => ({ key: m.col.field, meta: m, width: m.width, upTo: m.index }));
388
+ {
389
+ const { rest } = scrollable;
390
+ const { start, end, before, after } = columnWindow;
391
+ const firstIndex = rest[start]?.index ?? scrollable.pinned.length;
392
+ if (before > 0)
393
+ slots.push({ key: '\u0000before', meta: null, width: before, upTo: firstIndex });
394
+ for (let i = start; i < end; i++)
395
+ slots.push({ key: rest[i].col.field, meta: rest[i], width: rest[i].width, upTo: rest[i].index });
396
+ if (after > 0)
397
+ slots.push({ key: '\u0000after', meta: null, width: after, upTo: columnMeta.length });
398
+ }
399
+ /** Pełna szerokość tabeli w trybie wirtualnym (`table-layout: fixed`). */
400
+ const tableWidth = virtualScroll
401
+ ? scrollable.pinnedWidth + scrollable.offsets[scrollable.offsets.length - 1]
402
+ : undefined;
403
+ /** Liczba wierszy nagłówka (etykiety + ewentualnie filtry) - przesunięcie `aria-rowindex`. */
404
+ const headerRowCount = hasColumnFilters ? 2 : 1;
405
+ /** `aria-colindex` komórki kolumny (od 1, z kolumnami systemowymi) - tylko w trybie wirtualnym. */
406
+ const colIndex = (meta) => virtualScroll ? leadingCells + meta.index + 1 : undefined;
407
+ // -- Przestawianie kolumn --
205
408
  const frozenLeadCount = (() => {
206
409
  let n = 0;
207
410
  for (const col of visibleColumns) {
@@ -230,10 +433,31 @@ export function KptDataTable({ columns, data = EMPTY_ROWS, filterable = false, f
230
433
  setOrder(cols);
231
434
  setDragField(null);
232
435
  };
233
- // ── Sortowanie ──
436
+ /** Strzałki na uchwycie przestawiają kolumnę o jedno miejsce - alternatywa dla przeciągania. */
437
+ const onGripKeyDown = (field, event) => {
438
+ const delta = event.key === 'ArrowLeft' ? -1 : event.key === 'ArrowRight' ? 1 : 0;
439
+ if (!delta)
440
+ return;
441
+ event.preventDefault();
442
+ const from = visibleColumns.findIndex((col) => col.field === field);
443
+ const to = from + delta;
444
+ // Kolumny zamrożone zostają wiodące - nie wolno ich minąć ani wyjechać poza tabelę.
445
+ if (from === -1 || to < frozenLeadCount || to >= visibleColumns.length)
446
+ return;
447
+ const cols = [...order];
448
+ const fromIdx = cols.findIndex((col) => col.field === field);
449
+ const toIdx = cols.findIndex((col) => col.field === visibleColumns[to].field);
450
+ const [moved] = cols.splice(fromIdx, 1);
451
+ cols.splice(toIdx, 0, moved);
452
+ setOrder(cols);
453
+ setMoveAnnouncement(t('dataTable.columnMoved', { column: moved.header, position: to + 1, count: visibleColumns.length }));
454
+ focusGrip.current = field;
455
+ };
456
+ // -- Sortowanie --
234
457
  const toggleSort = (col) => {
235
458
  if (!col.sortable)
236
459
  return;
460
+ scrollToTop();
237
461
  if (sortField !== col.field) {
238
462
  setSortField(col.field);
239
463
  setSortDir('asc');
@@ -253,10 +477,26 @@ export function KptDataTable({ columns, data = EMPTY_ROWS, filterable = false, f
253
477
  return 'none';
254
478
  return sortDir === 'asc' ? 'ascending' : 'descending';
255
479
  };
256
- // ── Filtry kolumn ──
480
+ // -- Filtry kolumn --
257
481
  const setColumnFilter = (field, value) => {
258
482
  setColumnFilters((current) => ({ ...current, [field]: value }));
259
483
  setPageIndex(0);
484
+ scrollToTop();
485
+ };
486
+ // -- Filtr globalny --
487
+ const applyFilter = (value) => {
488
+ setFilterText(value);
489
+ setPageIndex(0);
490
+ scrollToTop();
491
+ };
492
+ /** Przy dużych zbiorach filtr czeka na przerwę w pisaniu; pole pokazuje tekst od razu. */
493
+ const onFilterInput = (value) => {
494
+ setFilterInput(value);
495
+ clearTimeout(filterTimer.current);
496
+ if (data.length > FILTER_DEBOUNCE_ROWS)
497
+ filterTimer.current = setTimeout(() => applyFilter(value), FILTER_DEBOUNCE_MS);
498
+ else
499
+ applyFilter(value);
260
500
  };
261
501
  const rangeVal = (field) => {
262
502
  const value = columnFilters[field];
@@ -267,8 +507,10 @@ export function KptDataTable({ columns, data = EMPTY_ROWS, filterable = false, f
267
507
  const max = col.filter?.max ?? 100;
268
508
  return columnFilters[col.field] ?? [min, max];
269
509
  };
270
- // ── Zaznaczanie ──
271
- const isSelected = (row) => picked.some((r) => sameRow(r, row));
510
+ // -- Zaznaczanie --
511
+ /** Klucze zaznaczonych wierszy - przy "zaznacz wszystko" na 20 000 wierszach `some()` dawałoby O(n^2). */
512
+ const selectedKeys = useMemo(() => new Set(picked.map((r) => (rowKey ? getValue(r, rowKey) : r))), [picked, rowKey]);
513
+ const isSelected = (row) => selectedKeys.has(keyOf(row));
272
514
  const toggleRow = (row) => {
273
515
  if (selectable === 'none')
274
516
  return;
@@ -281,20 +523,24 @@ export function KptDataTable({ columns, data = EMPTY_ROWS, filterable = false, f
281
523
  const pageAllSelected = paged.length > 0 && paged.every((row) => isSelected(row));
282
524
  const pageSomeSelected = paged.some((row) => isSelected(row)) && !pageAllSelected;
283
525
  const toggleAllOnPage = () => {
284
- if (pageAllSelected)
285
- setPicked(picked.filter((r) => !paged.some((pr) => sameRow(pr, r))));
526
+ if (pageAllSelected) {
527
+ const pageKeys = new Set(paged.map(keyOf));
528
+ setPicked(picked.filter((r) => !pageKeys.has(keyOf(r))));
529
+ }
286
530
  else
287
531
  setPicked([...picked, ...paged.filter((row) => !isSelected(row))]);
288
532
  };
289
- // ── Rozwijane wiersze ──
290
- const isExpanded = (row) => expanded.some((r) => sameRow(r, row));
533
+ // -- Rozwijane wiersze --
534
+ /** Klucze rozwiniętych wierszy - `isExpanded` w O(1) zamiast przeszukiwania listy dla każdego wiersza. */
535
+ const expandedKeys = useMemo(() => new Set(expanded.map((r) => (rowKey ? getValue(r, rowKey) : r))), [expanded, rowKey]);
536
+ const isExpanded = (row) => expandedKeys.has(keyOf(row));
291
537
  const toggleExpand = (row) => {
292
538
  if (isExpanded(row))
293
539
  setExpanded(expanded.filter((r) => !sameRow(r, row)));
294
540
  else
295
541
  setExpanded(multiExpand ? [...expanded, row] : [row]);
296
542
  };
297
- // ── Edycja inline ──
543
+ // -- Edycja inline --
298
544
  const isEditable = (col) => !!col.edit || !!cellEditors?.[col.field];
299
545
  const isEditingCell = (row, field) => !!editingCell && editingCell.field === field && sameRow(editingCell.row, row);
300
546
  const startEdit = (row, col, event) => {
@@ -324,7 +570,7 @@ export function KptDataTable({ columns, data = EMPTY_ROWS, filterable = false, f
324
570
  /**
325
571
  * Edytory bez panelu (text/number, własne) zatwierdza dopiero wyjście fokusu poza CAŁY edytor:
326
572
  * `KptInputNumber` ma fokusowalne strzałki obok pola, więc reakcja na blur samej kontrolki
327
- * zamykałaby edycję przy każdym kliknięciu w „+".
573
+ * zamykałaby edycję przy każdym kliknięciu w "+".
328
574
  */
329
575
  const onEditorFocusOut = (event) => {
330
576
  if (editorPanelOpen)
@@ -340,7 +586,7 @@ export function KptDataTable({ columns, data = EMPTY_ROWS, filterable = false, f
340
586
  if (!open)
341
587
  commitEdit();
342
588
  };
343
- // ── Wiersz podsumowania ──
589
+ // -- Wiersz podsumowania --
344
590
  const summarySource = lazy ? EMPTY_ROWS : filtered;
345
591
  const overrides = summaryValues ?? {};
346
592
  const summaryFields = visibleColumns
@@ -348,22 +594,61 @@ export function KptDataTable({ columns, data = EMPTY_ROWS, filterable = false, f
348
594
  .map((col) => col.field);
349
595
  const hasSummary = summaryFields.length > 0;
350
596
  const summaryStartIndex = summaryFields[0] === undefined ? -1 : visibleColumns.findIndex((col) => col.field === summaryFields[0]);
351
- const summaryLabelSpan = leadingCells + summaryStartIndex;
352
- const formatSummary = (raw, format) => format ? format(raw) : toText(raw);
353
- const summaryText = (col) => {
354
- if (col.field in overrides)
355
- return formatSummary(overrides[col.field], col.summary?.format);
356
- if (!col.summary)
357
- return '';
358
- const raw = typeof col.summary.fn === 'function'
359
- ? col.summary.fn(summarySource)
360
- : aggregate(summarySource, col.field, col.summary.fn);
361
- return formatSummary(raw, col.summary.format);
362
- };
363
- const colspan = visibleColumns.length + leadingCells;
597
+ /**
598
+ * Komórki wiersza podsumowania: etykieta scala kolumny systemowe, kolumny przed pierwszą
599
+ * podsumowywaną i dystansownik, o ile ten też stoi przed nią; reszta jak w `slots`.
600
+ * Przy kolumnach zamrożonych etykieta kończy się na ich granicy - jest przyklejona do lewej,
601
+ * więc scalona z kolumnami przewijanymi zasłaniałaby je przy przewijaniu w poziomie, a jej
602
+ * szerokość skakałaby przy każdym przestawieniu kolumny. Kolumny za granicą dostają puste komórki.
603
+ * Gdy przyklejone są same kolumny systemowe (`frozenSystemColumns`), są za wąskie na etykietę:
604
+ * dostają pustą przyklejoną komórkę (`systemSpan`), a etykieta staje za nimi, nad kolumnami
605
+ * przed pierwszą liczoną, i przewija się razem z nimi.
606
+ * `labelSpan` równe zero (pierwsza widoczna kolumna jest liczona) = etykieta nie ma się gdzie
607
+ * zmieścić i nie renderuje się wcale.
608
+ */
609
+ const summaryLayout = (() => {
610
+ const isBeforeStart = (slot) => slot.meta ? slot.meta.index < summaryStartIndex : slot.upTo <= summaryStartIndex;
611
+ let frozenSpan = 0;
612
+ const restSlots = [];
613
+ for (const slot of slots) {
614
+ const fitsLabel = !hasFrozen || slot.meta?.frozen === true;
615
+ if (restSlots.length === 0 && isBeforeStart(slot) && fitsLabel)
616
+ frozenSpan++;
617
+ else
618
+ restSlots.push(slot);
619
+ }
620
+ if (hasFrozen && frozenSpan === 0 && leadingCells > 0) {
621
+ let free = 0;
622
+ while (free < restSlots.length && isBeforeStart(restSlots[free]))
623
+ free++;
624
+ if (free > 0)
625
+ return { systemSpan: leadingCells, labelSpan: free, labelSticky: false, slots: restSlots.slice(free) };
626
+ }
627
+ return { systemSpan: 0, labelSpan: leadingCells + frozenSpan, labelSticky: hasFrozen, slots: restSlots };
628
+ })();
629
+ /** Gotowe teksty komórek podsumowania (pole -> tekst) - agregacja całego zbioru raz, nie przy każdym renderze. */
630
+ const summaryTexts = useMemo(() => {
631
+ const values = summaryValues ?? {};
632
+ const format = (raw, fmt) => (fmt ? fmt(raw) : toText(raw));
633
+ const map = new Map();
634
+ for (const col of visibleColumns) {
635
+ if (col.field in values)
636
+ map.set(col.field, format(values[col.field], col.summary?.format));
637
+ else if (col.summary) {
638
+ const raw = typeof col.summary.fn === 'function'
639
+ ? col.summary.fn(summarySource)
640
+ : aggregate(summarySource, col.field, col.summary.fn);
641
+ map.set(col.field, format(raw, col.summary.format));
642
+ }
643
+ }
644
+ return map;
645
+ }, [summarySource, summaryValues, visibleColumns]);
646
+ /** Komórki faktycznie renderowane w wierszu (w trybie wirtualnym okno kolumn + dystansowniki). */
647
+ const colspan = slots.length + leadingCells;
364
648
  const displayCount = virtualScroll ? sorted.length : paged.length;
365
649
  const rowsToRender = virtualScroll ? virtualRows : paged;
366
- // ── Renderowanie ──
650
+ // -- Renderowanie --
651
+ const hspacer = (slot) => (_jsx("td", { className: "kpt-data-table__hspacer", "aria-hidden": "true" }, slot.key));
367
652
  const filterCell = (col) => {
368
653
  const filter = col.filter;
369
654
  if (!filter)
@@ -386,7 +671,8 @@ export function KptDataTable({ columns, data = EMPTY_ROWS, filterable = false, f
386
671
  return null;
387
672
  }
388
673
  };
389
- const filterRow = (_jsxs("tr", { className: "kpt-data-table__filter-row", children: [hasRowDetail ? (_jsx("th", { className: cn('kpt-data-table__expander', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: expanderStickyLeft } })) : null, selectable !== 'none' ? (_jsx("th", { className: cn('kpt-data-table__checkbox', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: checkboxStickyLeft } })) : null, visibleColumns.map((col) => (_jsx("th", { style: { width: col.width, left: frozenLeft(col.field) }, "data-align": col.align ?? 'start', className: cn(frozenOffsets.has(col.field) && 'kpt-data-table__cell--frozen'), children: filterCell(col) }, col.field)))] }));
674
+ const filterRowIndex = filterRowPosition === 'top' ? 1 : 2;
675
+ const filterRow = (_jsxs("tr", { className: "kpt-data-table__filter-row", "aria-rowindex": virtualScroll ? filterRowIndex : undefined, children: [hasRowDetail ? (_jsx("th", { className: cn('kpt-data-table__expander', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: expanderStickyLeft } })) : null, selectable !== 'none' ? (_jsx("th", { className: cn('kpt-data-table__checkbox', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: checkboxStickyLeft } })) : null, slots.map((slot) => slot.meta ? (_jsx("th", { style: { width: virtualScroll ? undefined : slot.meta.col.width, left: slot.meta.left }, "data-align": slot.meta.align, "aria-colindex": colIndex(slot.meta), className: cn(slot.meta.frozen && 'kpt-data-table__cell--frozen'), children: filterCell(slot.meta.col) }, slot.key)) : (hspacer(slot)))] }));
390
676
  const editor = (row, col) => {
391
677
  const custom = cellEditors?.[col.field];
392
678
  if (custom) {
@@ -425,37 +711,58 @@ export function KptDataTable({ columns, data = EMPTY_ROWS, filterable = false, f
425
711
  return null;
426
712
  }
427
713
  };
428
- const renderRow = (row, index) => (_jsxs("tbody", { style: { display: 'contents' }, children: [_jsxs("tr", { className: cn(isSelected(row) && 'kpt-data-table__row--selected', hasRowDetail && isExpanded(row) && 'kpt-data-table__row--expanded'), style: { height: virtualScroll ? rowHeight : undefined }, onClick: () => onRowClick?.(row), children: [hasRowDetail ? (_jsx("td", { className: cn('kpt-data-table__expander', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: expanderStickyLeft }, children: _jsx("button", { type: "button", className: cn('kpt-data-table__expander-btn', isExpanded(row) && 'kpt-data-table__expander-btn--open'), "aria-expanded": isExpanded(row), "aria-label": t('dataTable.expandRow'), onClick: (event) => {
714
+ const renderRow = (row, index, ariaRowIndex) => (_jsxs("tbody", { style: { display: 'contents' }, children: [_jsxs("tr", { "aria-rowindex": ariaRowIndex, className: cn(isSelected(row) && 'kpt-data-table__row--selected', hasRowDetail && isExpanded(row) && 'kpt-data-table__row--expanded', rowsClickable && 'kpt-data-table__row--clickable'), style: { height: virtualScroll ? rowHeight : undefined }, tabIndex: rowsClickable ? 0 : undefined, onClick: () => onRowClick?.(row), onKeyDown: rowsClickable
715
+ ? (event) => {
716
+ // Tylko na samym wierszu - Enter w kontrolce komórki należy do niej.
717
+ if (event.target !== event.currentTarget || (event.key !== 'Enter' && event.key !== ' '))
718
+ return;
719
+ event.preventDefault();
720
+ onRowClick?.(row);
721
+ }
722
+ : undefined, children: [hasRowDetail ? (_jsx("td", { className: cn('kpt-data-table__expander', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: expanderStickyLeft }, children: _jsx("button", { type: "button", className: cn('kpt-data-table__expander-btn', isExpanded(row) && 'kpt-data-table__expander-btn--open'), "aria-expanded": isExpanded(row), "aria-label": t('dataTable.expandRow'), onClick: (event) => {
429
723
  event.stopPropagation();
430
724
  toggleExpand(row);
431
- }, children: _jsx(KptIcon, { name: "chevron-right" }) }) })) : null, selectable !== 'none' ? (_jsx("td", { className: cn('kpt-data-table__checkbox', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: checkboxStickyLeft }, children: _jsx("input", { type: "checkbox", checked: isSelected(row), "aria-label": t('dataTable.selectRow'), onClick: (event) => event.stopPropagation(), onChange: () => toggleRow(row) }) })) : null, visibleColumns.map((col) => {
432
- const editing = isEditingCell(row, col.field);
433
- const editable = isEditable(col) && !editing;
434
- return (_jsx("td", { "data-align": col.align ?? 'start', style: { left: frozenLeft(col.field) }, className: cn(frozenOffsets.has(col.field) && 'kpt-data-table__cell--frozen', editable && 'kpt-data-table__cell--editable'), tabIndex: editable ? 0 : undefined, "aria-label": editable ? t('dataTable.editCell', { column: col.header }) : undefined, onClick: (event) => {
435
- if (isEditable(col))
725
+ }, children: _jsx(KptIcon, { name: "chevron-right" }) }) })) : null, selectable !== 'none' ? (_jsx("td", { className: cn('kpt-data-table__checkbox', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: checkboxStickyLeft }, children: _jsx("input", { type: selectable === 'single' ? 'radio' : 'checkbox', name: selectable === 'single' ? `${uid}-select` : undefined, checked: isSelected(row), "aria-label": t('dataTable.selectRow'), onClick: (event) => {
726
+ event.stopPropagation();
727
+ toggleRow(row);
728
+ }, onChange: () => {
729
+ // Zmianę obsługuje `onClick` (radio nie zgłasza `change` przy odznaczaniu).
730
+ } }) })) : null, slots.map((slot) => {
731
+ const m = slot.meta;
732
+ if (!m)
733
+ return hspacer(slot);
734
+ const col = m.col;
735
+ // Edytor sprawdzamy tylko, gdy jakakolwiek komórka jest w edycji - reszta to odczyt gotowych cech kolumny.
736
+ const editing = m.editable && editingCell !== null && isEditingCell(row, col.field);
737
+ const editable = m.editable && !editing;
738
+ return (_jsx("td", { "data-align": m.align, style: { left: m.left }, "aria-colindex": colIndex(m), className: cn(m.frozen && 'kpt-data-table__cell--frozen', editable && 'kpt-data-table__cell--editable'), tabIndex: editable ? 0 : undefined, "aria-describedby": editable ? `${uid}-edit-${m.index}` : undefined, onClick: (event) => {
739
+ if (m.editable)
436
740
  startEdit(row, col, event);
437
741
  }, onKeyDown: (event) => {
438
742
  if (event.key === 'Enter' && editable)
439
743
  startEdit(row, col, event);
440
- }, children: editing ? (_jsx("div", { ref: editorHostRef, className: "kpt-data-table__editor", onClick: (event) => event.stopPropagation(), onBlur: onEditorFocusOut, children: editor(row, col) })) : cells?.[col.field] ? (cells[col.field](row, getValue(row, col.field))) : getValue(row, col.field) }, col.field));
744
+ }, children: editing ? (_jsx("div", { ref: editorHostRef, className: "kpt-data-table__editor", onClick: (event) => event.stopPropagation(), onBlur: onEditorFocusOut, children: editor(row, col) })) : cells?.[col.field] ? (cells[col.field](row, getValue(row, col.field))) : getValue(row, col.field) }, slot.key));
441
745
  })] }), hasRowDetail && isExpanded(row) && !virtualScroll ? (_jsx("tr", { className: "kpt-data-table__detail-row", children: _jsx("td", { className: "kpt-data-table__detail", colSpan: colspan, children: renderRowDetail?.(row) }) })) : null] }, rowKey ? String(getValue(row, rowKey)) : index));
442
- return (_jsxs("div", { className: cn('kpt-data-table', className), style: { ['--kpt-thead-offset']: `${theadOffset}px`, ...style }, ...rest, children: [filterable || exportable ? (_jsxs("div", { className: "kpt-data-table__toolbar", children: [filterable ? (_jsx("input", { className: "kpt-data-table__filter", type: "search", placeholder: t('dataTable.search'), value: filterText, onChange: (event) => {
443
- setFilterText(event.target.value);
444
- setPageIndex(0);
445
- } })) : null, exportable ? (_jsx(KptButton, { variant: "outline", size: "sm", onClick: () => exportTableCsv(visibleColumns, (lazy ? data : sorted), exportFilename), children: t('dataTable.exportCsv') })) : null] })) : null, _jsx("div", { ref: scrollRef, className: "kpt-data-table__scroll", style: { maxHeight: scrollHeight ?? undefined }, onScroll: onScroll, children: _jsxs("table", { className: cn('kpt-data-table__table', hasFrozen && 'kpt-data-table__table--frozen', virtualScroll && 'kpt-data-table__table--virtual'), children: [_jsxs("thead", { ref: theadRef, children: [hasColumnFilters && filterRowPosition === 'top' ? filterRow : null, _jsxs("tr", { children: [hasRowDetail ? (_jsx("th", { className: cn('kpt-data-table__expander', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: expanderStickyLeft } })) : null, selectable === 'multiple' ? (_jsx("th", { className: cn('kpt-data-table__checkbox', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: checkboxStickyLeft }, children: _jsx("input", { type: "checkbox", checked: pageAllSelected, ref: (el) => {
746
+ return (_jsxs("div", { className: cn('kpt-data-table', className), style: { ['--kpt-thead-offset']: `${theadOffset}px`, ...style }, ...rest, children: [filterable || exportable ? (_jsxs("div", { className: "kpt-data-table__toolbar", children: [filterable ? (_jsxs("div", { className: "kpt-data-table__search", "data-speech": filterSpeechState, children: [_jsx("input", { ref: filterRef, className: "kpt-data-table__filter", type: "search", placeholder: t('dataTable.search'), "aria-label": t('dataTable.search'), value: filterInput, onChange: (event) => onFilterInput(event.target.value) }), filterSpeech ? (_jsx(KptSpeechButton, { listeningColor: filterSpeechColor, speech: filterMic, size: "sm", unsupported: "hide", hostProps: { className: 'kpt-data-table__speech' } })) : null] })) : null, exportable ? (_jsx(KptButton, { variant: "outline", size: "sm", onClick: () => exportTableCsv(visibleColumns, (lazy ? data : sorted), exportFilename), children: t('dataTable.exportCsv') })) : null] })) : null, _jsx("div", { ref: scrollRef, className: "kpt-data-table__scroll", style: { maxHeight: scrollHeight ?? undefined }, onScroll: onScroll, children: _jsxs("table", { className: cn('kpt-data-table__table', hasFrozen && 'kpt-data-table__table--frozen', virtualScroll && 'kpt-data-table__table--virtual'), style: { width: tableWidth }, "aria-label": ariaLabel ?? undefined, "aria-busy": loading || undefined, "aria-rowcount": virtualScroll ? headerRowCount + sorted.length + (hasSummary ? 1 : 0) : undefined, "aria-colcount": virtualScroll ? leadingCells + visibleColumns.length : undefined, children: [caption ? _jsx("caption", { className: "kpt-data-table__caption", children: _jsx("span", { className: "kpt-data-table__caption-text", children: caption }) }) : null, virtualScroll ? (_jsxs("colgroup", { children: [hasRowDetail ? _jsx("col", { className: "kpt-data-table__col-expander" }) : null, selectable !== 'none' ? _jsx("col", { className: "kpt-data-table__col-checkbox" }) : null, slots.map((slot) => (_jsx("col", { style: { width: slot.width } }, slot.key)))] })) : null, _jsxs("thead", { ref: theadRef, children: [hasColumnFilters && filterRowPosition === 'top' ? filterRow : null, _jsxs("tr", { "aria-rowindex": virtualScroll ? (hasColumnFilters && filterRowPosition === 'top' ? 2 : 1) : undefined, children: [hasRowDetail ? (_jsx("th", { className: cn('kpt-data-table__expander', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: expanderStickyLeft } })) : null, selectable === 'multiple' ? (_jsx("th", { className: cn('kpt-data-table__checkbox', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: checkboxStickyLeft }, children: _jsx("input", { type: "checkbox", checked: pageAllSelected, ref: (el) => {
446
747
  if (el)
447
748
  el.indeterminate = pageSomeSelected;
448
- }, "aria-label": t('dataTable.selectAll'), onChange: toggleAllOnPage }) })) : selectable === 'single' ? (_jsx("th", { className: cn('kpt-data-table__checkbox', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: checkboxStickyLeft } })) : null, visibleColumns.map((col) => (_jsxs("th", { draggable: isColDraggable(col) || undefined, style: { width: col.width, left: frozenLeft(col.field) }, "data-align": col.align ?? 'start', className: cn(frozenOffsets.has(col.field) && 'kpt-data-table__cell--frozen', col.sortable && 'kpt-data-table__th--sortable', isColDraggable(col) && 'kpt-data-table__th--draggable', dragField !== null && dragField !== col.field && !col.frozen && 'kpt-data-table__th--dragover'), "aria-sort": ariaSort(col), onClick: () => toggleSort(col), onDragStart: (event) => {
449
- if (col.frozen)
450
- return;
451
- setDragField(col.field);
452
- event.dataTransfer.setData('text/plain', col.field);
453
- event.dataTransfer.effectAllowed = 'move';
454
- }, onDragOver: (event) => {
455
- if (!reorderable)
456
- return;
457
- const visIndex = visibleColumns.findIndex((c) => c.field === col.field);
458
- if (dragField !== null && visIndex >= frozenLeadCount)
459
- event.preventDefault();
460
- }, onDrop: (event) => reorderable && onColDrop(col.field, event), onDragEnd: () => setDragField(null), children: [isColDraggable(col) ? (_jsx(KptIcon, { className: "kpt-data-table__grip", name: "grip-vertical" })) : null, _jsx("span", { children: col.header }), col.sortable ? (_jsx(KptIcon, { className: cn('kpt-data-table__sort', ariaSort(col) !== 'none' && 'kpt-data-table__sort--active'), name: sortField === col.field && sortDir === 'asc' ? 'chevron-up' : 'chevron-down' })) : null] }, col.field)))] }), hasColumnFilters && filterRowPosition === 'bottom' ? filterRow : null] }), loading ? (_jsx("tbody", { children: _jsx("tr", { children: _jsx("td", { className: "kpt-data-table__state", colSpan: colspan, children: t('dataTable.loading') }) }) })) : displayCount === 0 ? (_jsx("tbody", { children: _jsx("tr", { children: _jsx("td", { className: "kpt-data-table__state", colSpan: colspan, children: emptyMessage ?? t('dataTable.empty') }) }) })) : (_jsxs(_Fragment, { children: [virtualScroll && topPad > 0 ? (_jsx("tbody", { children: _jsx("tr", { className: "kpt-data-table__spacer", "aria-hidden": "true", children: _jsx("td", { colSpan: colspan, style: { height: topPad } }) }) })) : null, rowsToRender.map((row, index) => renderRow(row, index)), virtualScroll && bottomPad > 0 ? (_jsx("tbody", { children: _jsx("tr", { className: "kpt-data-table__spacer", "aria-hidden": "true", children: _jsx("td", { colSpan: colspan, style: { height: bottomPad } }) }) })) : null] })), hasSummary && !loading && displayCount > 0 ? (_jsx("tfoot", { children: _jsxs("tr", { className: "kpt-data-table__summary", children: [summaryLabelSpan > 0 ? (_jsx("th", { className: cn('kpt-data-table__summary-label', hasFrozen && 'kpt-data-table__cell--frozen'), colSpan: summaryLabelSpan, style: { left: hasFrozen ? 0 : undefined }, children: summaryLabel ?? t('dataTable.total') })) : null, visibleColumns.map((col, index) => index >= summaryStartIndex ? (_jsx("td", { "data-align": col.align ?? 'end', style: { width: col.width, left: frozenLeft(col.field) }, className: cn(frozenOffsets.has(col.field) && 'kpt-data-table__cell--frozen'), children: summaryText(col) }, col.field)) : null)] }) })) : null] }) }), pageSize > 0 && !virtualScroll ? (_jsxs("div", { className: "kpt-data-table__pager", children: [paginatorActions, _jsx(KptPaginator, { length: filteredCount, pageIndex: pageIndex, onPageIndexChange: setPageIndex, pageSize: pageSizeState, onPageSizeChange: setPageSizeState, pageSizeOptions: pageSizeOptions })] })) : null] }));
749
+ }, "aria-label": t('dataTable.selectAll'), onChange: toggleAllOnPage }) })) : selectable === 'single' ? (_jsx("th", { className: cn('kpt-data-table__checkbox', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: checkboxStickyLeft } })) : null, slots.map((slot) => {
750
+ if (!slot.meta)
751
+ return hspacer(slot);
752
+ const m = slot.meta;
753
+ const col = m.col;
754
+ return (_jsxs("th", { draggable: isColDraggable(col) || undefined, style: { width: virtualScroll ? undefined : col.width, left: m.left }, "data-align": m.align, "aria-colindex": colIndex(m), className: cn(m.frozen && 'kpt-data-table__cell--frozen', col.sortable && 'kpt-data-table__th--sortable', isColDraggable(col) && 'kpt-data-table__th--draggable', dragField !== null && dragField !== col.field && !col.frozen && 'kpt-data-table__th--dragover'), "data-field": col.field, "aria-sort": col.sortable ? ariaSort(col) : undefined, onDragStart: (event) => {
755
+ if (col.frozen)
756
+ return;
757
+ setDragField(col.field);
758
+ event.dataTransfer.setData('text/plain', col.field);
759
+ event.dataTransfer.effectAllowed = 'move';
760
+ }, onDragOver: (event) => {
761
+ if (!reorderable)
762
+ return;
763
+ const visIndex = visibleColumns.findIndex((c) => c.field === col.field);
764
+ if (dragField !== null && visIndex >= frozenLeadCount)
765
+ event.preventDefault();
766
+ }, onDrop: (event) => reorderable && onColDrop(col.field, event), onDragEnd: () => setDragField(null), children: [isColDraggable(col) ? (_jsx("button", { type: "button", className: "kpt-data-table__grip", "aria-label": t('dataTable.moveColumn', { column: col.header }), "aria-keyshortcuts": "ArrowLeft ArrowRight", onKeyDown: (event) => onGripKeyDown(col.field, event), children: _jsx(KptIcon, { name: "grip-vertical" }) })) : null, col.sortable ? (_jsxs("button", { type: "button", className: "kpt-data-table__sort-btn", onClick: () => toggleSort(col), children: [_jsx("span", { children: col.header }), _jsx(KptIcon, { className: cn('kpt-data-table__sort', ariaSort(col) !== 'none' && 'kpt-data-table__sort--active'), name: sortField !== col.field || !sortDir ? 'selector' : sortDir === 'asc' ? 'chevron-up' : 'chevron-down' })] })) : (_jsx("span", { children: col.header }))] }, slot.key));
767
+ })] }), hasColumnFilters && filterRowPosition === 'bottom' ? filterRow : null] }), loading ? (_jsx("tbody", { children: _jsx("tr", { children: _jsx("td", { className: "kpt-data-table__state", colSpan: colspan, children: t('dataTable.loading') }) }) })) : displayCount === 0 ? (_jsx("tbody", { children: _jsx("tr", { children: _jsx("td", { className: "kpt-data-table__state", colSpan: colspan, children: emptyMessage ?? t('dataTable.empty') }) }) })) : (_jsxs(_Fragment, { children: [virtualScroll && topPad > 0 ? (_jsx("tbody", { children: _jsx("tr", { className: "kpt-data-table__spacer", "aria-hidden": "true", children: _jsx("td", { colSpan: colspan, style: { height: topPad } }) }) })) : null, rowsToRender.map((row, index) => renderRow(row, index, virtualScroll ? headerRowCount + rowWindow.start + index + 1 : undefined)), virtualScroll && bottomPad > 0 ? (_jsx("tbody", { children: _jsx("tr", { className: "kpt-data-table__spacer", "aria-hidden": "true", children: _jsx("td", { colSpan: colspan, style: { height: bottomPad } }) }) })) : null] })), hasSummary && !loading && displayCount > 0 ? (_jsx("tfoot", { children: _jsxs("tr", { className: "kpt-data-table__summary", "aria-rowindex": virtualScroll ? headerRowCount + sorted.length + 1 : undefined, children: [summaryLayout.systemSpan > 0 ? (_jsx("td", { className: "kpt-data-table__cell--frozen", colSpan: summaryLayout.systemSpan, style: { left: 0 } })) : null, summaryLayout.labelSpan > 0 ? (_jsx("th", { className: cn('kpt-data-table__summary-label', summaryLayout.labelSticky && 'kpt-data-table__cell--frozen'), colSpan: summaryLayout.labelSpan, style: { left: summaryLayout.labelSticky ? 0 : undefined }, children: summaryLabel ?? t('dataTable.total') })) : null, summaryLayout.slots.map((slot) => slot.meta ? (_jsx("td", { "data-align": slot.meta.col.align ?? 'end', style: { width: virtualScroll ? undefined : slot.meta.col.width, left: slot.meta.left }, "aria-colindex": colIndex(slot.meta), className: cn(slot.meta.frozen && 'kpt-data-table__cell--frozen'), children: summaryTexts.get(slot.meta.col.field) ?? '' }, slot.key)) : (hspacer(slot)))] }) })) : null] }) }), _jsx("div", { className: "kpt-data-table__live", "aria-live": "polite", children: moveAnnouncement }), _jsx("div", { hidden: true, children: visibleColumns.map((col, colIndex) => isEditable(col) ? (_jsx("span", { id: `${uid}-edit-${colIndex}`, children: t('dataTable.editCell', { column: col.header }) }, col.field)) : null) }), pageSize > 0 && !virtualScroll ? (_jsxs("div", { className: "kpt-data-table__pager", children: [paginatorActions, _jsx(KptPaginator, { length: filteredCount, pageIndex: pageIndex, onPageIndexChange: setPageIndex, pageSize: pageSizeState, onPageSizeChange: setPageSizeState, pageSizeOptions: pageSizeOptions })] })) : null] }));
461
768
  }