@konce-pt/react 0.11.1 → 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.
- package/README.md +8 -2
- package/dist/accordion/accordion.js +11 -10
- package/dist/alert/alert.js +1 -0
- package/dist/alert/llms.txt +1 -0
- package/dist/app-shell/app-shell.js +12 -5
- package/dist/app-shell/llms.txt +5 -4
- package/dist/auth/auth.js +17 -8
- package/dist/auth/llms.txt +3 -2
- package/dist/autocomplete/autocomplete.d.ts +1 -1
- package/dist/autocomplete/autocomplete.js +12 -4
- package/dist/autocomplete/llms.txt +3 -0
- package/dist/avatar/avatar.js +1 -0
- package/dist/avatar-group/avatar-group.js +1 -0
- package/dist/backdrop/backdrop.d.ts +0 -19
- package/dist/backdrop/backdrop.js +18 -13
- package/dist/bottom-sheet/bottom-sheet.d.ts +6 -1
- package/dist/bottom-sheet/bottom-sheet.js +3 -2
- package/dist/bottom-sheet/llms.txt +3 -0
- package/dist/breadcrumb/breadcrumb.d.ts +7 -1
- package/dist/breadcrumb/breadcrumb.js +10 -2
- package/dist/breadcrumb/llms.txt +8 -0
- package/dist/button/button.js +2 -1
- package/dist/button-group/button-group.d.ts +15 -2
- package/dist/button-group/button-group.js +31 -4
- package/dist/button-group/llms.txt +9 -6
- package/dist/card/card.js +4 -3
- package/dist/carousel/carousel.js +4 -1
- package/dist/charts/chart-legend.js +1 -0
- package/dist/charts/chart.d.ts +3 -2
- package/dist/charts/chart.js +12 -4
- package/dist/charts/llms.txt +6 -3
- package/dist/chat/chat.js +39 -9
- package/dist/chat/llms.txt +9 -0
- package/dist/chat-composer/chat-composer.d.ts +5 -2
- package/dist/chat-composer/chat-composer.js +10 -1
- package/dist/chat-dock/chat-dock.js +1 -0
- package/dist/chat-message/chat-message.js +2 -1
- package/dist/chat-typing/chat-typing.js +1 -0
- package/dist/checkbox/checkbox.d.ts +1 -1
- package/dist/checkbox/checkbox.js +9 -2
- package/dist/checkbox/llms.txt +3 -0
- package/dist/chip/chip.js +1 -0
- package/dist/chips-input/chips-input.d.ts +3 -1
- package/dist/chips-input/chips-input.js +14 -5
- package/dist/chips-input/llms.txt +3 -0
- package/dist/clock/clock.d.ts +9 -4
- package/dist/clock/clock.js +35 -5
- package/dist/clock/llms.txt +6 -2
- package/dist/color-picker/color-picker.d.ts +5 -1
- package/dist/color-picker/color-picker.js +36 -17
- package/dist/color-picker/llms.txt +3 -0
- package/dist/confirm/confirm-container.js +1 -0
- package/dist/confirm/confirm-store.js +6 -0
- package/dist/confirm/llms.txt +2 -1
- package/dist/context-menu/context-menu.js +35 -12
- package/dist/context-menu/llms.txt +3 -0
- package/dist/data-table/data-table.d.ts +12 -1
- package/dist/data-table/data-table.js +113 -51
- package/dist/data-table/export-csv.d.ts +2 -2
- package/dist/data-table/export-csv.js +7 -9
- package/dist/data-table/llms.txt +19 -4
- package/dist/data-view/data-view.js +1 -0
- package/dist/date-range/date-range.d.ts +16 -4
- package/dist/date-range/date-range.js +69 -46
- package/dist/date-range/llms.txt +17 -3
- package/dist/datepicker/datepicker.d.ts +17 -3
- package/dist/datepicker/datepicker.js +86 -54
- package/dist/datepicker/llms.txt +17 -3
- package/dist/dialog/dialog.js +29 -8
- package/dist/dialog/llms.txt +3 -0
- package/dist/divider/divider.d.ts +7 -1
- package/dist/divider/divider.js +3 -2
- package/dist/divider/llms.txt +1 -0
- package/dist/drawer/drawer.d.ts +6 -1
- package/dist/drawer/drawer.js +3 -2
- package/dist/drawer/llms.txt +3 -0
- package/dist/fieldset/fieldset.js +4 -1
- package/dist/file-upload/file-upload.d.ts +7 -1
- package/dist/file-upload/file-upload.js +18 -7
- package/dist/file-upload/llms.txt +6 -3
- package/dist/form-field/form-field.js +1 -0
- package/dist/galleria/galleria.js +4 -1
- package/dist/i18n/i18n-context.d.ts +3 -3
- package/dist/i18n/i18n-context.js +25 -3
- package/dist/icon/icon.js +4 -1
- package/dist/image/image.js +4 -4
- package/dist/input/input.js +1 -0
- package/dist/input-mask/input-mask.d.ts +7 -2
- package/dist/input-mask/input-mask.js +15 -41
- package/dist/input-mask/llms.txt +3 -0
- package/dist/input-number/input-number.d.ts +3 -1
- package/dist/input-number/input-number.js +15 -11
- package/dist/input-number/llms.txt +5 -2
- package/dist/input-otp/input-otp.d.ts +16 -3
- package/dist/input-otp/input-otp.js +43 -19
- package/dist/input-otp/llms.txt +11 -0
- package/dist/internal/mask.d.ts +14 -0
- package/dist/internal/mask.js +62 -0
- package/dist/internal/number-group.d.ts +22 -0
- package/dist/internal/number-group.js +53 -0
- package/dist/internal/option-key.d.ts +6 -0
- package/dist/internal/option-key.js +8 -0
- package/dist/internal/overlay.js +37 -7
- package/dist/internal/password-strength.d.ts +6 -0
- package/dist/internal/password-strength.js +74 -0
- package/dist/internal/scroll-lock.d.ts +6 -0
- package/dist/internal/scroll-lock.js +35 -0
- package/dist/internal/slot-children.d.ts +13 -0
- package/dist/internal/slot-children.js +31 -0
- package/dist/internal/step.d.ts +12 -0
- package/dist/internal/step.js +31 -0
- package/dist/internal/tween.js +2 -3
- package/dist/internal/use-controllable-state.d.ts +5 -0
- package/dist/internal/use-controllable-state.js +25 -4
- package/dist/internal/use-modal-focus.d.ts +1 -0
- package/dist/internal/use-modal-focus.js +12 -1
- package/dist/knob/knob.d.ts +5 -2
- package/dist/knob/knob.js +15 -14
- package/dist/knob/llms.txt +4 -2
- package/dist/listbox/listbox.d.ts +3 -1
- package/dist/listbox/listbox.js +30 -10
- package/dist/listbox/llms.txt +3 -0
- package/dist/map/llms.txt +5 -1
- package/dist/map/map-create-dialog.js +30 -22
- package/dist/map/map-hours-field.js +1 -0
- package/dist/map/map-image-field.js +2 -1
- package/dist/map/map-legend.js +1 -0
- package/dist/map/map-poi-card.js +2 -1
- package/dist/map/map-search.js +54 -30
- package/dist/map/map.d.ts +6 -1
- package/dist/map/map.js +62 -34
- package/dist/megamenu/llms.txt +11 -0
- package/dist/megamenu/megamenu.d.ts +7 -1
- package/dist/megamenu/megamenu.js +15 -6
- package/dist/menu/menu-rows.js +1 -0
- package/dist/menu/menu.js +10 -3
- package/dist/menubar/llms.txt +11 -0
- package/dist/menubar/menubar.d.ts +7 -1
- package/dist/menubar/menubar.js +15 -3
- package/dist/meter-group/meter-group.js +1 -0
- package/dist/order-list/llms.txt +3 -0
- package/dist/order-list/order-list.js +21 -6
- package/dist/paginator/paginator.js +7 -4
- package/dist/panel/panel.js +5 -3
- package/dist/password/llms.txt +6 -2
- package/dist/password/password.js +4 -14
- package/dist/peek/llms.txt +1 -1
- package/dist/peek/peek.js +37 -15
- package/dist/pick-list/llms.txt +3 -0
- package/dist/pick-list/pick-list.js +19 -3
- package/dist/popover/llms.txt +3 -0
- package/dist/popover/popover.d.ts +3 -1
- package/dist/popover/popover.js +10 -3
- package/dist/progress/progress.js +1 -0
- package/dist/public-api.d.ts +1 -1
- package/dist/radio-group/llms.txt +3 -0
- package/dist/radio-group/radio-group.d.ts +3 -1
- package/dist/radio-group/radio-group.js +13 -2
- package/dist/rating/rating.js +1 -0
- package/dist/rich-text/llms.txt +14 -0
- package/dist/rich-text/rich-text.d.ts +3 -9
- package/dist/rich-text/rich-text.js +241 -96
- package/dist/rich-text/sanitize.d.ts +9 -0
- package/dist/rich-text/sanitize.js +77 -29
- package/dist/roadmap/llms.txt +9 -3
- package/dist/roadmap/roadmap.js +88 -11
- package/dist/scroll-top/scroll-top.js +1 -0
- package/dist/select/llms.txt +3 -0
- package/dist/select/select.d.ts +3 -1
- package/dist/select/select.js +46 -32
- package/dist/shared/types.d.ts +21 -0
- package/dist/shared/types.js +9 -1
- package/dist/sidenav/sidenav.d.ts +1 -2
- package/dist/sidenav/sidenav.js +4 -3
- package/dist/slider/llms.txt +3 -0
- package/dist/slider/slider.d.ts +3 -1
- package/dist/slider/slider.js +15 -4
- package/dist/speed-dial/speed-dial.js +2 -1
- package/dist/spinner/spinner.js +1 -0
- package/dist/split-button/split-button.js +10 -3
- package/dist/splitter/splitter.js +23 -8
- package/dist/stepper/llms.txt +3 -0
- package/dist/stepper/stepper.js +9 -5
- package/dist/switch/llms.txt +3 -0
- package/dist/switch/switch.d.ts +1 -1
- package/dist/switch/switch.js +9 -2
- package/dist/switch-group/llms.txt +3 -0
- package/dist/switch-group/switch-group.d.ts +3 -1
- package/dist/switch-group/switch-group.js +5 -3
- package/dist/tabs/llms.txt +3 -0
- package/dist/tabs/tabs.js +30 -14
- package/dist/textarea/textarea.js +1 -0
- package/dist/toast/llms.txt +3 -0
- package/dist/toast/toast-container.js +1 -0
- package/dist/toast/toast-store.d.ts +5 -1
- package/dist/toast/toast-store.js +1 -1
- package/dist/tooltip/llms.txt +5 -3
- package/dist/tooltip/tooltip.d.ts +3 -2
- package/dist/tooltip/tooltip.js +148 -55
- package/dist/tree/llms.txt +1 -0
- package/dist/tree/tree.d.ts +6 -1
- package/dist/tree/tree.js +14 -7
- package/package.json +15 -14
package/dist/tooltip/tooltip.js
CHANGED
|
@@ -1,13 +1,86 @@
|
|
|
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;
|
|
8
10
|
/**
|
|
9
|
-
*
|
|
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
|
+
}
|
|
23
|
+
/** Strona przeciwna i dwie boczne — kolejność prób, gdy zadana strona się nie nadaje. */
|
|
24
|
+
const FALLBACKS = {
|
|
25
|
+
top: ['top', 'bottom', 'right', 'left'],
|
|
26
|
+
bottom: ['bottom', 'top', 'right', 'left'],
|
|
27
|
+
left: ['left', 'right', 'top', 'bottom'],
|
|
28
|
+
right: ['right', 'left', 'top', 'bottom'],
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Ustawia dymek przy hoście, na pierwszej stronie z `FALLBACKS`, która się nadaje: dymek mieści się
|
|
32
|
+
* w oknie, a krawędź hosta od tej strony nie jest niczym przykryta (przyklejony nagłówek, menu, panel
|
|
33
|
+
* — sprawdzane `elementFromPoint` tuż przy krawędzi). Bez tego dymek przycisku wsuniętego pod
|
|
34
|
+
* nagłówek wyskakiwał nad nagłówkiem, odcięty od tego, co opisuje. Gdy żadna strona się nie nadaje,
|
|
35
|
+
* zostaje zadana. Na koniec dymek jest dociśnięty do okna z marginesem 4 px.
|
|
36
|
+
* Bliźniak tej funkcji żyje w drugim porcie — zmiany w obu naraz.
|
|
37
|
+
*/
|
|
38
|
+
function placeTooltip(host, tip, preferred) {
|
|
39
|
+
const r = host.getBoundingClientRect();
|
|
40
|
+
const t = tip.getBoundingClientRect();
|
|
41
|
+
const gap = 8;
|
|
42
|
+
const margin = 4;
|
|
43
|
+
const vw = document.documentElement.clientWidth;
|
|
44
|
+
const vh = document.documentElement.clientHeight;
|
|
45
|
+
const at = (side) => {
|
|
46
|
+
switch (side) {
|
|
47
|
+
case 'bottom':
|
|
48
|
+
return { top: r.bottom + gap, left: r.left + r.width / 2 - t.width / 2 };
|
|
49
|
+
case 'left':
|
|
50
|
+
return { top: r.top + r.height / 2 - t.height / 2, left: r.left - t.width - gap };
|
|
51
|
+
case 'right':
|
|
52
|
+
return { top: r.top + r.height / 2 - t.height / 2, left: r.right + gap };
|
|
53
|
+
default:
|
|
54
|
+
return { top: r.top - t.height - gap, left: r.left + r.width / 2 - t.width / 2 };
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
const fits = (side) => {
|
|
58
|
+
const { top, left } = at(side);
|
|
59
|
+
// Po osi poprzecznej dymek i tak zostanie dociśnięty do okna — liczy się tylko oś strony.
|
|
60
|
+
return side === 'top' || side === 'bottom'
|
|
61
|
+
? top >= margin && top + t.height <= vh - margin
|
|
62
|
+
: left >= margin && left + t.width <= vw - margin;
|
|
63
|
+
};
|
|
64
|
+
// Punkt 2 px w głąb hosta przy krawędzi danej strony; widoczny, gdy na wierzchu jest host, jego
|
|
65
|
+
// potomek albo sam dymek (stoi jeszcze w poprzednim miejscu).
|
|
66
|
+
const exposed = (side) => {
|
|
67
|
+
const x = side === 'left' ? r.left + 2 : side === 'right' ? r.right - 2 : r.left + r.width / 2;
|
|
68
|
+
const y = side === 'top' ? r.top + 2 : side === 'bottom' ? r.bottom - 2 : r.top + r.height / 2;
|
|
69
|
+
if (x < 0 || y < 0 || x >= vw || y >= vh)
|
|
70
|
+
return false;
|
|
71
|
+
const hit = document.elementFromPoint(x, y);
|
|
72
|
+
return !hit || host.contains(hit) || tip.contains(hit);
|
|
73
|
+
};
|
|
74
|
+
const side = FALLBACKS[preferred].find((s) => fits(s) && exposed(s)) ?? preferred;
|
|
75
|
+
const { top, left } = at(side);
|
|
76
|
+
tip.dataset['side'] = side;
|
|
77
|
+
tip.style.top = `${Math.min(Math.max(margin, top), Math.max(margin, vh - t.height - margin))}px`;
|
|
78
|
+
tip.style.left = `${Math.min(Math.max(margin, left), Math.max(margin, vw - t.width - margin))}px`;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
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`.
|
|
11
84
|
*
|
|
12
85
|
* Port dyrektywy `[kptTooltip]`: nie renderuje własnego opakowania, tylko dokleja handlery
|
|
13
86
|
* i ref do przekazanego elementu, więc drzewo DOM zostaje takie samo jak bez tooltipa.
|
|
@@ -23,9 +96,18 @@ const HIDE_DELAY_MS = 120;
|
|
|
23
96
|
*/
|
|
24
97
|
export function KptTooltip({ text, position = 'top', onlyWhenTruncated = false, children }) {
|
|
25
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
|
+
}, []);
|
|
26
107
|
const tipRef = useRef(null);
|
|
27
108
|
const descRef = useRef(null);
|
|
28
109
|
const hideTimer = useRef(undefined);
|
|
110
|
+
const showTimer = useRef(undefined);
|
|
29
111
|
const descId = `kpt-tooltip-desc-${useId().replace(/[^a-zA-Z0-9]/g, '')}`;
|
|
30
112
|
// Trwały opis dla czytników: dymek powstaje dopiero przy pokazaniu, więc nie da się go
|
|
31
113
|
// wskazać wcześniej. Opis żyje razem z komponentem i aktualizuje się ze zmianą tekstu.
|
|
@@ -60,63 +142,41 @@ export function KptTooltip({ text, position = 'top', onlyWhenTruncated = false,
|
|
|
60
142
|
const host = hostRef.current;
|
|
61
143
|
if (!tip || !host)
|
|
62
144
|
return;
|
|
63
|
-
|
|
64
|
-
const t = tip.getBoundingClientRect();
|
|
65
|
-
const gap = 8;
|
|
66
|
-
let top;
|
|
67
|
-
let left;
|
|
68
|
-
switch (position) {
|
|
69
|
-
case 'bottom':
|
|
70
|
-
top = r.bottom + gap;
|
|
71
|
-
left = r.left + r.width / 2 - t.width / 2;
|
|
72
|
-
break;
|
|
73
|
-
case 'left':
|
|
74
|
-
top = r.top + r.height / 2 - t.height / 2;
|
|
75
|
-
left = r.left - t.width - gap;
|
|
76
|
-
break;
|
|
77
|
-
case 'right':
|
|
78
|
-
top = r.top + r.height / 2 - t.height / 2;
|
|
79
|
-
left = r.right + gap;
|
|
80
|
-
break;
|
|
81
|
-
default:
|
|
82
|
-
top = r.top - t.height - gap;
|
|
83
|
-
left = r.left + r.width / 2 - t.width / 2;
|
|
84
|
-
}
|
|
85
|
-
tip.style.top = `${Math.max(4, top)}px`;
|
|
86
|
-
tip.style.left = `${Math.max(4, left)}px`;
|
|
145
|
+
placeTooltip(host, tip, position);
|
|
87
146
|
}, [position]);
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
hideRef.current();
|
|
91
|
-
}, []);
|
|
147
|
+
/** Sprzątanie bieżącego dymka (nasłuchy i element) — ustawiane przy pokazaniu, wołane przy ukryciu. */
|
|
148
|
+
const cleanupRef = useRef(null);
|
|
92
149
|
const cancelHide = useCallback(() => {
|
|
93
150
|
if (hideTimer.current !== undefined)
|
|
94
151
|
clearTimeout(hideTimer.current);
|
|
95
152
|
hideTimer.current = undefined;
|
|
96
153
|
}, []);
|
|
154
|
+
const cancelShow = useCallback(() => {
|
|
155
|
+
if (showTimer.current !== undefined)
|
|
156
|
+
clearTimeout(showTimer.current);
|
|
157
|
+
showTimer.current = undefined;
|
|
158
|
+
}, []);
|
|
97
159
|
const hide = useCallback(() => {
|
|
98
160
|
cancelHide();
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
tipRef.current = null;
|
|
104
|
-
}, [reposition, onKeyDown, cancelHide]);
|
|
105
|
-
// `onKeyDown` potrzebuje aktualnego `hide`, a `hide` zdejmuje `onKeyDown` — ref przerywa krąg.
|
|
106
|
-
const hideRef = useRef(hide);
|
|
107
|
-
hideRef.current = hide;
|
|
161
|
+
cancelShow();
|
|
162
|
+
cleanupRef.current?.();
|
|
163
|
+
cleanupRef.current = null;
|
|
164
|
+
}, [cancelHide, cancelShow]);
|
|
108
165
|
const scheduleHide = useCallback(() => {
|
|
166
|
+
cancelShow();
|
|
109
167
|
cancelHide();
|
|
110
|
-
hideTimer.current = setTimeout(
|
|
111
|
-
}, [cancelHide]);
|
|
112
|
-
useEffect(() =>
|
|
168
|
+
hideTimer.current = setTimeout(hide, HIDE_DELAY_MS);
|
|
169
|
+
}, [cancelHide, cancelShow, hide]);
|
|
170
|
+
useEffect(() => hide, [hide]);
|
|
113
171
|
const show = useCallback(() => {
|
|
114
172
|
cancelHide();
|
|
115
|
-
|
|
173
|
+
cancelShow();
|
|
174
|
+
const host = hostRef.current;
|
|
175
|
+
if (tipRef.current || !text || !host)
|
|
116
176
|
return;
|
|
117
177
|
// Sprawdzamy dopiero teraz, a nie przy renderze: dzięki temu wynik jest zawsze aktualny
|
|
118
178
|
// po zmianie szerokości czy powiększenia, bez żadnych obserwatorów.
|
|
119
|
-
if (onlyWhenTruncated && !isTruncated(
|
|
179
|
+
if (onlyWhenTruncated && !isTruncated(host))
|
|
120
180
|
return;
|
|
121
181
|
const tip = document.createElement('div');
|
|
122
182
|
tip.className = 'kpt-tooltip';
|
|
@@ -125,26 +185,56 @@ export function KptTooltip({ text, position = 'top', onlyWhenTruncated = false,
|
|
|
125
185
|
if (descRef.current)
|
|
126
186
|
tip.setAttribute('aria-hidden', 'true');
|
|
127
187
|
tip.textContent = text;
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
'border-radius:var(--kpt-radius-sm,0.25rem);font-size:var(--kpt-font-size-xs,0.75rem);' +
|
|
131
|
-
'line-height:1.4;background:var(--kpt-color-on-surface);color:var(--kpt-color-surface);' +
|
|
132
|
-
'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';
|
|
133
190
|
tip.addEventListener('mouseenter', cancelHide);
|
|
134
191
|
tip.addEventListener('mouseleave', scheduleHide);
|
|
135
|
-
|
|
192
|
+
tooltipContainer(host).appendChild(tip);
|
|
136
193
|
tipRef.current = tip;
|
|
137
194
|
reposition();
|
|
195
|
+
const onKeyDown = (event) => {
|
|
196
|
+
if (event.key === 'Escape')
|
|
197
|
+
hide();
|
|
198
|
+
};
|
|
138
199
|
// Faza przechwytywania łapie także przewijanie zagnieżdżonych kontenerów, bo zdarzenie
|
|
139
200
|
// `scroll` elementu się nie propaguje.
|
|
140
201
|
window.addEventListener('scroll', reposition, true);
|
|
141
202
|
window.addEventListener('resize', reposition);
|
|
142
203
|
document.addEventListener('keydown', onKeyDown);
|
|
143
|
-
requestAnimationFrame(() => {
|
|
144
|
-
|
|
145
|
-
tipRef.current.style.opacity = '1';
|
|
204
|
+
const frame = requestAnimationFrame(() => {
|
|
205
|
+
tip.dataset['visible'] = '';
|
|
146
206
|
});
|
|
147
|
-
|
|
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]);
|
|
148
238
|
/** Fokus przeszedł do innego elementu w obrębie hosta — dymek zostaje. */
|
|
149
239
|
const onBlur = (event) => {
|
|
150
240
|
const next = event.relatedTarget;
|
|
@@ -156,13 +246,16 @@ export function KptTooltip({ text, position = 'top', onlyWhenTruncated = false,
|
|
|
156
246
|
return _jsx(_Fragment, { children: children });
|
|
157
247
|
const child = children;
|
|
158
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 */
|
|
159
251
|
return cloneElement(child, {
|
|
160
252
|
ref: composeRefs(hostRef, props['ref']),
|
|
161
|
-
onMouseEnter: chain(props['onMouseEnter'],
|
|
253
|
+
onMouseEnter: chain(props['onMouseEnter'], scheduleShow),
|
|
162
254
|
onMouseLeave: chain(props['onMouseLeave'], scheduleHide),
|
|
163
255
|
onFocus: chain(props['onFocus'], show),
|
|
164
256
|
onBlur: chain(props['onBlur'], onBlur),
|
|
165
257
|
});
|
|
258
|
+
/* eslint-enable react-hooks/refs */
|
|
166
259
|
}
|
|
167
260
|
/** Handler dziecka najpierw, nasz po nim — jak w scalaniu propsów slotu. */
|
|
168
261
|
function chain(theirs, ours) {
|
package/dist/tree/llms.txt
CHANGED
|
@@ -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.
|
package/dist/tree/tree.d.ts
CHANGED
|
@@ -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]
|
|
64
|
-
|
|
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.
|
|
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": "
|
|
64
|
-
"react-dom": "
|
|
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/
|
|
69
|
-
"@konce-pt/
|
|
70
|
-
"@konce-pt/chart": "0.
|
|
71
|
-
"@konce-pt/
|
|
72
|
-
"@konce-pt/
|
|
73
|
-
"@konce-pt/
|
|
74
|
-
"@konce-pt/
|
|
75
|
-
"@konce-pt/
|
|
76
|
-
"@konce-pt/mention": "0.
|
|
77
|
-
"@konce-pt/
|
|
78
|
-
"@konce-pt/
|
|
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
|
}
|