@konce-pt/react 0.11.2 → 0.12.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 (203) hide show
  1. package/README.md +8 -2
  2. package/dist/accordion/accordion.js +11 -10
  3. package/dist/alert/alert.js +1 -0
  4. package/dist/alert/llms.txt +1 -0
  5. package/dist/app-shell/app-shell.js +12 -5
  6. package/dist/app-shell/llms.txt +5 -4
  7. package/dist/auth/auth.js +17 -8
  8. package/dist/auth/llms.txt +3 -2
  9. package/dist/autocomplete/autocomplete.d.ts +1 -1
  10. package/dist/autocomplete/autocomplete.js +12 -4
  11. package/dist/autocomplete/llms.txt +3 -0
  12. package/dist/avatar/avatar.js +1 -0
  13. package/dist/avatar-group/avatar-group.js +1 -0
  14. package/dist/backdrop/backdrop.d.ts +0 -19
  15. package/dist/backdrop/backdrop.js +18 -13
  16. package/dist/bottom-sheet/bottom-sheet.d.ts +6 -1
  17. package/dist/bottom-sheet/bottom-sheet.js +3 -2
  18. package/dist/bottom-sheet/llms.txt +3 -0
  19. package/dist/breadcrumb/breadcrumb.d.ts +7 -1
  20. package/dist/breadcrumb/breadcrumb.js +10 -2
  21. package/dist/breadcrumb/llms.txt +8 -0
  22. package/dist/button/button.js +2 -1
  23. package/dist/button-group/button-group.d.ts +14 -1
  24. package/dist/button-group/button-group.js +30 -4
  25. package/dist/button-group/llms.txt +9 -6
  26. package/dist/card/card.js +4 -3
  27. package/dist/carousel/carousel.js +4 -1
  28. package/dist/charts/chart-legend.js +1 -0
  29. package/dist/charts/chart.d.ts +3 -2
  30. package/dist/charts/chart.js +12 -4
  31. package/dist/charts/llms.txt +6 -3
  32. package/dist/chat/chat.js +39 -9
  33. package/dist/chat/llms.txt +9 -0
  34. package/dist/chat-composer/chat-composer.d.ts +5 -2
  35. package/dist/chat-composer/chat-composer.js +10 -1
  36. package/dist/chat-dock/chat-dock.js +1 -0
  37. package/dist/chat-message/chat-message.js +2 -1
  38. package/dist/chat-typing/chat-typing.js +1 -0
  39. package/dist/checkbox/checkbox.d.ts +1 -1
  40. package/dist/checkbox/checkbox.js +9 -2
  41. package/dist/checkbox/llms.txt +3 -0
  42. package/dist/chip/chip.js +1 -0
  43. package/dist/chips-input/chips-input.d.ts +3 -1
  44. package/dist/chips-input/chips-input.js +14 -5
  45. package/dist/chips-input/llms.txt +3 -0
  46. package/dist/clock/clock.d.ts +9 -4
  47. package/dist/clock/clock.js +35 -5
  48. package/dist/clock/llms.txt +6 -2
  49. package/dist/color-picker/color-picker.d.ts +5 -1
  50. package/dist/color-picker/color-picker.js +36 -17
  51. package/dist/color-picker/llms.txt +3 -0
  52. package/dist/confirm/confirm-container.js +1 -0
  53. package/dist/confirm/confirm-store.js +6 -0
  54. package/dist/confirm/llms.txt +2 -1
  55. package/dist/context-menu/context-menu.js +35 -12
  56. package/dist/context-menu/llms.txt +3 -0
  57. package/dist/data-table/data-table.d.ts +12 -1
  58. package/dist/data-table/data-table.js +113 -51
  59. package/dist/data-table/export-csv.d.ts +2 -2
  60. package/dist/data-table/export-csv.js +7 -9
  61. package/dist/data-table/llms.txt +19 -4
  62. package/dist/data-view/data-view.js +1 -0
  63. package/dist/date-range/date-range.d.ts +16 -4
  64. package/dist/date-range/date-range.js +69 -46
  65. package/dist/date-range/llms.txt +17 -3
  66. package/dist/datepicker/datepicker.d.ts +17 -3
  67. package/dist/datepicker/datepicker.js +86 -54
  68. package/dist/datepicker/llms.txt +17 -3
  69. package/dist/dialog/dialog.js +29 -8
  70. package/dist/dialog/llms.txt +3 -0
  71. package/dist/divider/divider.d.ts +7 -1
  72. package/dist/divider/divider.js +3 -2
  73. package/dist/divider/llms.txt +1 -0
  74. package/dist/drawer/drawer.d.ts +6 -1
  75. package/dist/drawer/drawer.js +3 -2
  76. package/dist/drawer/llms.txt +3 -0
  77. package/dist/fieldset/fieldset.js +4 -1
  78. package/dist/file-upload/file-upload.d.ts +7 -1
  79. package/dist/file-upload/file-upload.js +18 -7
  80. package/dist/file-upload/llms.txt +6 -3
  81. package/dist/form-field/form-field.js +1 -0
  82. package/dist/galleria/galleria.js +4 -1
  83. package/dist/i18n/i18n-context.d.ts +3 -3
  84. package/dist/i18n/i18n-context.js +25 -3
  85. package/dist/icon/icon.js +4 -1
  86. package/dist/image/image.js +4 -4
  87. package/dist/input/input.js +1 -0
  88. package/dist/input-mask/input-mask.d.ts +7 -2
  89. package/dist/input-mask/input-mask.js +15 -41
  90. package/dist/input-mask/llms.txt +3 -0
  91. package/dist/input-number/input-number.d.ts +3 -1
  92. package/dist/input-number/input-number.js +15 -11
  93. package/dist/input-number/llms.txt +5 -2
  94. package/dist/input-otp/input-otp.d.ts +16 -3
  95. package/dist/input-otp/input-otp.js +43 -19
  96. package/dist/input-otp/llms.txt +11 -0
  97. package/dist/internal/mask.d.ts +14 -0
  98. package/dist/internal/mask.js +62 -0
  99. package/dist/internal/number-group.d.ts +22 -0
  100. package/dist/internal/number-group.js +53 -0
  101. package/dist/internal/option-key.d.ts +6 -0
  102. package/dist/internal/option-key.js +8 -0
  103. package/dist/internal/overlay.js +37 -7
  104. package/dist/internal/password-strength.d.ts +6 -0
  105. package/dist/internal/password-strength.js +74 -0
  106. package/dist/internal/scroll-lock.d.ts +6 -0
  107. package/dist/internal/scroll-lock.js +35 -0
  108. package/dist/internal/slot-children.d.ts +13 -0
  109. package/dist/internal/slot-children.js +31 -0
  110. package/dist/internal/step.d.ts +12 -0
  111. package/dist/internal/step.js +31 -0
  112. package/dist/internal/tween.js +2 -3
  113. package/dist/internal/use-controllable-state.d.ts +5 -0
  114. package/dist/internal/use-controllable-state.js +25 -4
  115. package/dist/internal/use-modal-focus.d.ts +1 -0
  116. package/dist/internal/use-modal-focus.js +12 -1
  117. package/dist/knob/knob.d.ts +5 -2
  118. package/dist/knob/knob.js +15 -14
  119. package/dist/knob/llms.txt +4 -2
  120. package/dist/listbox/listbox.d.ts +3 -1
  121. package/dist/listbox/listbox.js +30 -10
  122. package/dist/listbox/llms.txt +3 -0
  123. package/dist/map/llms.txt +5 -1
  124. package/dist/map/map-create-dialog.js +30 -22
  125. package/dist/map/map-hours-field.js +1 -0
  126. package/dist/map/map-image-field.js +2 -1
  127. package/dist/map/map-legend.js +1 -0
  128. package/dist/map/map-poi-card.js +2 -1
  129. package/dist/map/map-search.js +54 -30
  130. package/dist/map/map.d.ts +6 -1
  131. package/dist/map/map.js +62 -34
  132. package/dist/megamenu/llms.txt +11 -0
  133. package/dist/megamenu/megamenu.d.ts +7 -1
  134. package/dist/megamenu/megamenu.js +15 -6
  135. package/dist/menu/menu-rows.js +1 -0
  136. package/dist/menu/menu.js +10 -3
  137. package/dist/menubar/llms.txt +11 -0
  138. package/dist/menubar/menubar.d.ts +7 -1
  139. package/dist/menubar/menubar.js +15 -3
  140. package/dist/meter-group/meter-group.js +1 -0
  141. package/dist/order-list/llms.txt +3 -0
  142. package/dist/order-list/order-list.js +21 -6
  143. package/dist/paginator/paginator.js +7 -4
  144. package/dist/panel/panel.js +5 -3
  145. package/dist/password/llms.txt +6 -2
  146. package/dist/password/password.js +4 -14
  147. package/dist/peek/llms.txt +1 -1
  148. package/dist/peek/peek.js +37 -15
  149. package/dist/pick-list/llms.txt +3 -0
  150. package/dist/pick-list/pick-list.js +19 -3
  151. package/dist/popover/llms.txt +3 -0
  152. package/dist/popover/popover.d.ts +3 -1
  153. package/dist/popover/popover.js +10 -3
  154. package/dist/progress/progress.js +1 -0
  155. package/dist/public-api.d.ts +1 -1
  156. package/dist/radio-group/llms.txt +3 -0
  157. package/dist/radio-group/radio-group.d.ts +3 -1
  158. package/dist/radio-group/radio-group.js +13 -2
  159. package/dist/rating/rating.js +1 -0
  160. package/dist/rich-text/llms.txt +14 -0
  161. package/dist/rich-text/rich-text.d.ts +3 -9
  162. package/dist/rich-text/rich-text.js +241 -96
  163. package/dist/rich-text/sanitize.d.ts +9 -0
  164. package/dist/rich-text/sanitize.js +77 -29
  165. package/dist/roadmap/llms.txt +9 -3
  166. package/dist/roadmap/roadmap.js +88 -11
  167. package/dist/scroll-top/scroll-top.js +1 -0
  168. package/dist/select/llms.txt +3 -0
  169. package/dist/select/select.d.ts +3 -1
  170. package/dist/select/select.js +46 -32
  171. package/dist/shared/types.d.ts +21 -0
  172. package/dist/shared/types.js +9 -1
  173. package/dist/sidenav/sidenav.d.ts +1 -2
  174. package/dist/sidenav/sidenav.js +4 -3
  175. package/dist/slider/llms.txt +3 -0
  176. package/dist/slider/slider.d.ts +3 -1
  177. package/dist/slider/slider.js +15 -4
  178. package/dist/speed-dial/speed-dial.js +2 -1
  179. package/dist/spinner/spinner.js +1 -0
  180. package/dist/split-button/split-button.js +10 -3
  181. package/dist/splitter/splitter.js +23 -8
  182. package/dist/stepper/llms.txt +3 -0
  183. package/dist/stepper/stepper.js +9 -5
  184. package/dist/switch/llms.txt +3 -0
  185. package/dist/switch/switch.d.ts +1 -1
  186. package/dist/switch/switch.js +9 -2
  187. package/dist/switch-group/llms.txt +3 -0
  188. package/dist/switch-group/switch-group.d.ts +3 -1
  189. package/dist/switch-group/switch-group.js +5 -3
  190. package/dist/tabs/llms.txt +3 -0
  191. package/dist/tabs/tabs.js +30 -14
  192. package/dist/textarea/textarea.js +1 -0
  193. package/dist/toast/llms.txt +3 -0
  194. package/dist/toast/toast-container.js +1 -0
  195. package/dist/toast/toast-store.d.ts +5 -1
  196. package/dist/toast/toast-store.js +1 -1
  197. package/dist/tooltip/llms.txt +5 -3
  198. package/dist/tooltip/tooltip.d.ts +3 -2
  199. package/dist/tooltip/tooltip.js +90 -31
  200. package/dist/tree/llms.txt +1 -0
  201. package/dist/tree/tree.d.ts +6 -1
  202. package/dist/tree/tree.js +14 -7
  203. package/package.json +15 -14
@@ -1,10 +1,25 @@
1
+ 'use client';
1
2
  import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
3
  import { cloneElement, isValidElement, useCallback, useEffect, useId, useRef } from 'react';
3
4
  import { composeRefs } from "../internal/compose-refs.js";
5
+ import { isDevBuild } from "../internal/dev.js";
4
6
  /** Elementy, które same przyjmują fokus — na nie trafia `aria-describedby`. */
5
7
  const FOCUSABLE = 'a[href], button, input, select, textarea, [tabindex]:not([tabindex="-1"])';
6
8
  /** Ile czasu ma wskaźnik na przejście z elementu na dymek, zanim ten zniknie (WCAG 1.4.13). */
7
9
  const HIDE_DELAY_MS = 120;
10
+ /**
11
+ * Zwłoka pokazania pod wskaźnikiem — przejazd myszą po pasku ikon nie zapala po kolei wszystkich
12
+ * dymków. Fokus z klawiatury pokazuje dymek od razu.
13
+ */
14
+ const SHOW_DELAY_MS = 300;
15
+ /**
16
+ * Gdzie dokleić dymek: do najbliższego otwartego `<dialog>` albo popovera hosta, inaczej do `body`.
17
+ * Modalny `<dialog>` leży w warstwie szczytowej — dymek z `body` byłby pod nim niewidoczny.
18
+ * Bliźniak w porcie Angulara.
19
+ */
20
+ function tooltipContainer(host) {
21
+ return host.closest('dialog[open], [popover]') ?? host.ownerDocument.body;
22
+ }
8
23
  /** Strona przeciwna i dwie boczne — kolejność prób, gdy zadana strona się nie nadaje. */
9
24
  const FALLBACKS = {
10
25
  top: ['top', 'bottom', 'right', 'left'],
@@ -63,8 +78,9 @@ function placeTooltip(host, tip, preferred) {
63
78
  tip.style.left = `${Math.min(Math.max(margin, left), Math.max(margin, vw - t.width - margin))}px`;
64
79
  }
65
80
  /**
66
- * Dymek podpowiedzi pokazywany na hover i focus. Lekki — element dodawany do `body`
67
- * i pozycjonowany względem dziecka.
81
+ * Dymek podpowiedzi pokazywany na hover i focus. Lekki — element dodawany do `body` (albo do
82
+ * otwartego `<dialog>`/popovera dziecka, żeby był widoczny w warstwie szczytowej) i pozycjonowany
83
+ * względem dziecka. Wygląd daje klasa `.kpt-tooltip` z `@konce-pt/styles` w warstwie `kpt.components`.
68
84
  *
69
85
  * Port dyrektywy `[kptTooltip]`: nie renderuje własnego opakowania, tylko dokleja handlery
70
86
  * i ref do przekazanego elementu, więc drzewo DOM zostaje takie samo jak bez tooltipa.
@@ -80,9 +96,18 @@ function placeTooltip(host, tip, preferred) {
80
96
  */
81
97
  export function KptTooltip({ text, position = 'top', onlyWhenTruncated = false, children }) {
82
98
  const hostRef = useRef(null);
99
+ // Dymek przypina się do elementu przez `ref` dziecka. Własny komponent, który nie przekazuje
100
+ // `ref` do elementu DOM, zostawia hosta pustym — dymek wtedy po cichu nie działa.
101
+ useEffect(() => {
102
+ if (isDevBuild() && isValidElement(children) && !hostRef.current) {
103
+ console.warn('[KptTooltip] Dziecko nie przekazało `ref` do elementu DOM — dymek nie ma się do czego przypiąć.');
104
+ }
105
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- sprawdzenie raz, po montażu
106
+ }, []);
83
107
  const tipRef = useRef(null);
84
108
  const descRef = useRef(null);
85
109
  const hideTimer = useRef(undefined);
110
+ const showTimer = useRef(undefined);
86
111
  const descId = `kpt-tooltip-desc-${useId().replace(/[^a-zA-Z0-9]/g, '')}`;
87
112
  // Trwały opis dla czytników: dymek powstaje dopiero przy pokazaniu, więc nie da się go
88
113
  // wskazać wcześniej. Opis żyje razem z komponentem i aktualizuje się ze zmianą tekstu.
@@ -119,38 +144,39 @@ export function KptTooltip({ text, position = 'top', onlyWhenTruncated = false,
119
144
  return;
120
145
  placeTooltip(host, tip, position);
121
146
  }, [position]);
122
- const onKeyDown = useCallback((event) => {
123
- if (event.key === 'Escape')
124
- hideRef.current();
125
- }, []);
147
+ /** Sprzątanie bieżącego dymka (nasłuchy i element) — ustawiane przy pokazaniu, wołane przy ukryciu. */
148
+ const cleanupRef = useRef(null);
126
149
  const cancelHide = useCallback(() => {
127
150
  if (hideTimer.current !== undefined)
128
151
  clearTimeout(hideTimer.current);
129
152
  hideTimer.current = undefined;
130
153
  }, []);
154
+ const cancelShow = useCallback(() => {
155
+ if (showTimer.current !== undefined)
156
+ clearTimeout(showTimer.current);
157
+ showTimer.current = undefined;
158
+ }, []);
131
159
  const hide = useCallback(() => {
132
160
  cancelHide();
133
- window.removeEventListener('scroll', reposition, true);
134
- window.removeEventListener('resize', reposition);
135
- document.removeEventListener('keydown', onKeyDown);
136
- tipRef.current?.remove();
137
- tipRef.current = null;
138
- }, [reposition, onKeyDown, cancelHide]);
139
- // `onKeyDown` potrzebuje aktualnego `hide`, a `hide` zdejmuje `onKeyDown` — ref przerywa krąg.
140
- const hideRef = useRef(hide);
141
- hideRef.current = hide;
161
+ cancelShow();
162
+ cleanupRef.current?.();
163
+ cleanupRef.current = null;
164
+ }, [cancelHide, cancelShow]);
142
165
  const scheduleHide = useCallback(() => {
166
+ cancelShow();
143
167
  cancelHide();
144
- hideTimer.current = setTimeout(() => hideRef.current(), HIDE_DELAY_MS);
145
- }, [cancelHide]);
146
- useEffect(() => () => hideRef.current(), []);
168
+ hideTimer.current = setTimeout(hide, HIDE_DELAY_MS);
169
+ }, [cancelHide, cancelShow, hide]);
170
+ useEffect(() => hide, [hide]);
147
171
  const show = useCallback(() => {
148
172
  cancelHide();
149
- if (tipRef.current || !text)
173
+ cancelShow();
174
+ const host = hostRef.current;
175
+ if (tipRef.current || !text || !host)
150
176
  return;
151
177
  // Sprawdzamy dopiero teraz, a nie przy renderze: dzięki temu wynik jest zawsze aktualny
152
178
  // po zmianie szerokości czy powiększenia, bez żadnych obserwatorów.
153
- if (onlyWhenTruncated && !isTruncated(hostRef.current))
179
+ if (onlyWhenTruncated && !isTruncated(host))
154
180
  return;
155
181
  const tip = document.createElement('div');
156
182
  tip.className = 'kpt-tooltip';
@@ -159,26 +185,56 @@ export function KptTooltip({ text, position = 'top', onlyWhenTruncated = false,
159
185
  if (descRef.current)
160
186
  tip.setAttribute('aria-hidden', 'true');
161
187
  tip.textContent = text;
162
- tip.style.cssText =
163
- 'position:fixed;z-index:1400;max-width:16rem;padding:0.35rem 0.6rem;' +
164
- 'border-radius:var(--kpt-radius-sm,0.25rem);font-size:var(--kpt-font-size-xs,0.75rem);' +
165
- 'line-height:1.4;background:var(--kpt-color-on-surface);color:var(--kpt-color-surface);' +
166
- 'box-shadow:var(--kpt-elevation-2);opacity:0;transition:opacity 120ms;';
188
+ // Inline tylko to, bez czego dymek nie stanie przy hoście; wygląd daje klasa w `@layer`.
189
+ tip.style.position = 'fixed';
167
190
  tip.addEventListener('mouseenter', cancelHide);
168
191
  tip.addEventListener('mouseleave', scheduleHide);
169
- document.body.appendChild(tip);
192
+ tooltipContainer(host).appendChild(tip);
170
193
  tipRef.current = tip;
171
194
  reposition();
195
+ const onKeyDown = (event) => {
196
+ if (event.key === 'Escape')
197
+ hide();
198
+ };
172
199
  // Faza przechwytywania łapie także przewijanie zagnieżdżonych kontenerów, bo zdarzenie
173
200
  // `scroll` elementu się nie propaguje.
174
201
  window.addEventListener('scroll', reposition, true);
175
202
  window.addEventListener('resize', reposition);
176
203
  document.addEventListener('keydown', onKeyDown);
177
- requestAnimationFrame(() => {
178
- if (tipRef.current)
179
- tipRef.current.style.opacity = '1';
204
+ const frame = requestAnimationFrame(() => {
205
+ tip.dataset['visible'] = '';
180
206
  });
181
- }, [text, onlyWhenTruncated, reposition, onKeyDown, cancelHide, scheduleHide]);
207
+ cleanupRef.current = () => {
208
+ cancelAnimationFrame(frame);
209
+ window.removeEventListener('scroll', reposition, true);
210
+ window.removeEventListener('resize', reposition);
211
+ document.removeEventListener('keydown', onKeyDown);
212
+ tip.remove();
213
+ tipRef.current = null;
214
+ };
215
+ }, [text, onlyWhenTruncated, reposition, cancelHide, cancelShow, scheduleHide, hide]);
216
+ /** Najechanie wskaźnikiem pokazuje dymek po chwili; fokus — od razu. */
217
+ const scheduleShow = useCallback(() => {
218
+ cancelHide();
219
+ if (tipRef.current || showTimer.current !== undefined)
220
+ return;
221
+ showTimer.current = setTimeout(() => {
222
+ showTimer.current = undefined;
223
+ show();
224
+ }, SHOW_DELAY_MS);
225
+ }, [cancelHide, show]);
226
+ // Zmiana tekstu przy widocznym dymku — bez tego dymek pokazywałby starą treść do zamknięcia.
227
+ useEffect(() => {
228
+ const tip = tipRef.current;
229
+ if (!tip)
230
+ return;
231
+ if (!text)
232
+ hide();
233
+ else if (tip.textContent !== text) {
234
+ tip.textContent = text;
235
+ reposition();
236
+ }
237
+ }, [text, hide, reposition]);
182
238
  /** Fokus przeszedł do innego elementu w obrębie hosta — dymek zostaje. */
183
239
  const onBlur = (event) => {
184
240
  const next = event.relatedTarget;
@@ -190,13 +246,16 @@ export function KptTooltip({ text, position = 'top', onlyWhenTruncated = false,
190
246
  return _jsx(_Fragment, { children: children });
191
247
  const child = children;
192
248
  const props = child.props;
249
+ /* eslint-disable react-hooks/refs -- composeRefs i chain tylko składają funkcje; refy czytają one
250
+ dopiero przy podpięciu elementu i w zdarzeniach, nigdy w renderze */
193
251
  return cloneElement(child, {
194
252
  ref: composeRefs(hostRef, props['ref']),
195
- onMouseEnter: chain(props['onMouseEnter'], show),
253
+ onMouseEnter: chain(props['onMouseEnter'], scheduleShow),
196
254
  onMouseLeave: chain(props['onMouseLeave'], scheduleHide),
197
255
  onFocus: chain(props['onFocus'], show),
198
256
  onBlur: chain(props['onBlur'], onBlur),
199
257
  });
258
+ /* eslint-enable react-hooks/refs */
200
259
  }
201
260
  /** Handler dziecka najpierw, nasz po nim — jak w scalaniu propsów slotu. */
202
261
  function chain(theirs, ours) {
@@ -55,3 +55,4 @@ selectable trees `aria-selected`, disabled rows `aria-disabled`. The chevron is
55
55
  (`aria-hidden`): a button inside a `treeitem` would be a nested control and a second Tab stop per
56
56
  row, and the keyboard expands with the arrows anyway. The focus ring is drawn on the row, not on the
57
57
  `<li>`, which would outline the whole subtree.
58
+ `ariaLabel` names the `role="tree"` element (e.g. "Project files") — a separate prop because `aria-label` from the rest props lands on the wrapper, not on the tree.
@@ -17,6 +17,11 @@ export interface KptTreeProps<T> extends Omit<ComponentPropsWithRef<'div'>, 'onS
17
17
  defaultSelected?: KptTreeNode<T> | null;
18
18
  onSelectedChange?: (node: KptTreeNode<T> | null) => void;
19
19
  onNodeClick?: (node: KptTreeNode<T>) => void;
20
+ /**
21
+ * Nazwa drzewa dla czytnika (`role="tree"` bez nazwy to samo „drzewo”), np. „Pliki projektu”.
22
+ * Trafia na element `role="tree"`, nie na opakowanie — dlatego osobny prop, a nie `aria-label`.
23
+ */
24
+ ariaLabel?: string;
20
25
  }
21
26
  /**
22
27
  * Drzewo hierarchiczne: rozwijanie, zaznaczanie, ikony.
@@ -35,4 +40,4 @@ export interface KptTreeProps<T> extends Omit<ComponentPropsWithRef<'div'>, 'onS
35
40
  * @example
36
41
  * <KptTree nodes={tree} selectable selected={node} onSelectedChange={setNode} />
37
42
  */
38
- export declare function KptTree<T>({ nodes, selectable, selected, defaultSelected, onSelectedChange, onNodeClick, locale: _locale, dictionary: _dictionary, className, ...rest }: Readonly<KptTreeProps<T>>): import("react").JSX.Element;
43
+ export declare function KptTree<T>({ nodes, selectable, selected, defaultSelected, onSelectedChange, onNodeClick, ariaLabel, locale: _locale, dictionary: _dictionary, className, ...rest }: Readonly<KptTreeProps<T>>): import("react").JSX.Element;
package/dist/tree/tree.js CHANGED
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  import { useEffect, useMemo, useRef, useState, } from 'react';
3
4
  import { cn } from "../internal/cn.js";
@@ -21,7 +22,7 @@ import { treeKeyAction } from "./tree-keys.js";
21
22
  * @example
22
23
  * <KptTree nodes={tree} selectable selected={node} onSelectedChange={setNode} />
23
24
  */
24
- export function KptTree({ nodes = [], selectable = false, selected, defaultSelected = null, onSelectedChange, onNodeClick, locale: _locale, dictionary: _dictionary, className, ...rest }) {
25
+ export function KptTree({ nodes = [], selectable = false, selected, defaultSelected = null, onSelectedChange, onNodeClick, ariaLabel, locale: _locale, dictionary: _dictionary, className, ...rest }) {
25
26
  const [active, setActive] = useControllableState({
26
27
  value: selected,
27
28
  defaultValue: defaultSelected,
@@ -34,6 +35,7 @@ export function KptTree({ nodes = [], selectable = false, selected, defaultSelec
34
35
  const [focused, setFocused] = useState(null);
35
36
  /** Węzeł, na który po renderze trzeba przenieść fokus DOM (klawiatura). */
36
37
  const [focusRequest, setFocusRequest] = useState(null);
38
+ const handledFocus = useRef(null);
37
39
  const treeRef = useRef(null);
38
40
  const hasChildren = (node) => !!node.children?.length;
39
41
  const isOpen = (node) => toggled.get(node) ?? !!node.expanded;
@@ -56,12 +58,17 @@ export function KptTree({ nodes = [], selectable = false, selected, defaultSelec
56
58
  // Jedyny wiersz z `tabIndex=0`: ostatnio skupiony, a gdy go nie widać — zaznaczony albo pierwszy.
57
59
  const visible = rows.map((r) => r.node);
58
60
  const tabStop = focused && visible.includes(focused) ? focused : active && visible.includes(active) ? active : (visible[0] ?? null);
61
+ // Żądanie nie jest zerowane w efekcie (setState w efekcie = dodatkowy przebieg renderu): każde jest
62
+ // nowym obiektem, a obsłużone zapamiętuje ref — kolejne przebiegi efektu go pomijają.
59
63
  useEffect(() => {
60
- if (!focusRequest)
64
+ if (!focusRequest || handledFocus.current === focusRequest)
61
65
  return;
62
- const index = rows.findIndex((r) => r.node === focusRequest);
63
- treeRef.current?.querySelectorAll('[role="treeitem"]')[index]?.focus();
64
- setFocusRequest(null);
66
+ const index = rows.findIndex((r) => r.node === focusRequest.node);
67
+ const element = treeRef.current?.querySelectorAll('[role="treeitem"]')[index];
68
+ if (!element)
69
+ return; // wiersz jeszcze nie wyrenderowany (rozwijanie) — spróbujemy po zmianie `rows`
70
+ handledFocus.current = focusRequest;
71
+ element.focus();
65
72
  }, [focusRequest, rows]);
66
73
  const toggle = (node) => {
67
74
  const next = new Map(toggled);
@@ -94,7 +101,7 @@ export function KptTree({ nodes = [], selectable = false, selected, defaultSelec
94
101
  onRow(target);
95
102
  else {
96
103
  setFocused(target);
97
- setFocusRequest(target);
104
+ setFocusRequest({ node: target });
98
105
  }
99
106
  };
100
107
  const renderList = (list, level) => list.map((node, index) => (_jsxs("li", { className: "kpt-tree__node", role: "treeitem", "aria-level": level + 1, "aria-expanded": hasChildren(node) ? isOpen(node) : undefined, "aria-selected": selectable ? active === node : undefined, "aria-disabled": node.disabled ? true : undefined, tabIndex: node === tabStop ? 0 : -1, onFocus: (event) => {
@@ -104,5 +111,5 @@ export function KptTree({ nodes = [], selectable = false, selected, defaultSelec
104
111
  event.stopPropagation();
105
112
  toggle(node);
106
113
  }, children: _jsx(KptIcon, { name: "chevron-right", className: cn('kpt-tree__chevron', isOpen(node) && 'is-open') }) })) : (_jsx("span", { className: "kpt-tree__spacer" })), node.icon ? _jsx(KptIcon, { name: node.icon, className: "kpt-tree__icon" }) : null, _jsx("span", { className: "kpt-tree__label", children: node.label })] }), hasChildren(node) && isOpen(node) ? _jsx("ul", { role: "group", children: renderList(node.children, level + 1) }) : null] }, index)));
107
- return (_jsx("div", { className: cn('kpt-tree-host', className), ...rest, children: _jsx("ul", { ref: treeRef, className: "kpt-tree", role: "tree", children: renderList(nodes, 0) }) }));
114
+ return (_jsx("div", { className: cn('kpt-tree-host', className), ...rest, children: _jsx("ul", { ref: treeRef, className: "kpt-tree", role: "tree", "aria-label": ariaLabel || undefined, children: renderList(nodes, 0) }) }));
108
115
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@konce-pt/react",
3
- "version": "0.11.2",
3
+ "version": "0.12.0",
4
4
  "description": "Open-source React 19 component library on the Koncept UI design system — the same kpt-* CSS layer and --kpt-* tokens as the Angular port. Zero runtime dependencies.",
5
5
  "license": "MIT",
6
6
  "author": "konce.pt",
@@ -60,22 +60,22 @@
60
60
  }
61
61
  },
62
62
  "peerDependencies": {
63
- "react": ">=19.0.0",
64
- "react-dom": ">=19.0.0",
63
+ "react": "^19.2.0",
64
+ "react-dom": "^19.2.0",
65
65
  "leaflet": "^1.9.0"
66
66
  },
67
67
  "dependencies": {
68
- "@konce-pt/chat": "0.11.2",
69
- "@konce-pt/backdrop": "0.11.2",
70
- "@konce-pt/chart": "0.11.2",
71
- "@konce-pt/i18n": "0.11.2",
72
- "@konce-pt/mention": "0.11.2",
73
- "@konce-pt/table": "0.11.2",
74
- "@konce-pt/roadmap": "0.11.2",
75
- "@konce-pt/tokens": "0.11.2",
76
- "@konce-pt/map": "0.11.2",
77
- "@konce-pt/datetime": "0.11.2",
78
- "@konce-pt/icons": "0.11.2"
68
+ "@konce-pt/datetime": "0.12.0",
69
+ "@konce-pt/icons": "0.12.0",
70
+ "@konce-pt/chart": "0.12.0",
71
+ "@konce-pt/backdrop": "0.12.0",
72
+ "@konce-pt/map": "0.12.0",
73
+ "@konce-pt/tokens": "0.12.0",
74
+ "@konce-pt/i18n": "0.12.0",
75
+ "@konce-pt/table": "0.12.0",
76
+ "@konce-pt/mention": "0.12.0",
77
+ "@konce-pt/chat": "0.12.0",
78
+ "@konce-pt/roadmap": "0.12.0"
79
79
  },
80
80
  "devDependencies": {
81
81
  "@types/react": "^19.2.0",
@@ -93,6 +93,7 @@
93
93
  },
94
94
  "scripts": {
95
95
  "build": "tsc -p tsconfig.build.json && node scripts/copy-llms.mjs",
96
+ "lint": "eslint .",
96
97
  "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\""
97
98
  }
98
99
  }