@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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
3
|
import { useEffect, useRef, useState } from 'react';
|
|
3
4
|
import { cn } from "../internal/cn.js";
|
|
@@ -35,16 +36,27 @@ export function KptOrderList({ items, defaultItems = EMPTY, onItemsChange, heade
|
|
|
35
36
|
/** Pozycja, na którą po renderze trzeba przenieść fokus DOM (klawiatura). */
|
|
36
37
|
const [focusRequest, setFocusRequest] = useState(null);
|
|
37
38
|
const rootRef = useRef(null);
|
|
39
|
+
// Żądanie nie jest zerowane w efekcie (setState w efekcie = dodatkowy przebieg renderu): każde jest
|
|
40
|
+
// nowym obiektem, więc efekt rusza raz na żądanie, także o ten sam indeks.
|
|
38
41
|
useEffect(() => {
|
|
39
|
-
if (focusRequest
|
|
42
|
+
if (!focusRequest)
|
|
40
43
|
return;
|
|
41
|
-
rootRef.current?.querySelectorAll('[role="option"]')[focusRequest]?.focus();
|
|
42
|
-
setFocusRequest(null);
|
|
44
|
+
rootRef.current?.querySelectorAll('[role="option"]')[focusRequest.index]?.focus();
|
|
43
45
|
}, [focusRequest]);
|
|
44
46
|
/**
|
|
45
47
|
* Przesuwa pozycję spod `index` o jedno miejsce i zwraca jej nowy indeks. Jawny indeks zamiast
|
|
46
48
|
* `selected`: klawiatura zaznacza i przesuwa w jednym zdarzeniu, a stan z domknięcia byłby stary.
|
|
47
49
|
*/
|
|
50
|
+
/** Treść regionu `aria-live` po przestawieniu. */
|
|
51
|
+
const [announcement, setAnnouncement] = useState('');
|
|
52
|
+
/**
|
|
53
|
+
* Ogłasza nową pozycję. Ten sam tekst drugi raz pod rząd nie zostałby przeczytany (region reaguje
|
|
54
|
+
* na zmianę), więc naprzemiennie dokładamy niewidoczną spację.
|
|
55
|
+
*/
|
|
56
|
+
const announce = (index, total) => {
|
|
57
|
+
const text = t('orderList.moved', { position: index + 1, total });
|
|
58
|
+
setAnnouncement((current) => (current === text ? text + '\u00a0' : text));
|
|
59
|
+
};
|
|
48
60
|
const moveFrom = (index, dir) => {
|
|
49
61
|
const target = index + dir;
|
|
50
62
|
if (target < 0 || target >= list.length)
|
|
@@ -55,6 +67,7 @@ export function KptOrderList({ items, defaultItems = EMPTY, onItemsChange, heade
|
|
|
55
67
|
// Zaznaczenie wędruje za elementem, nie zostaje na pozycji — inaczej kolejne kliknięcie
|
|
56
68
|
// przesuwałoby sąsiada zamiast tego samego wpisu.
|
|
57
69
|
setSelected(target);
|
|
70
|
+
announce(target, next.length);
|
|
58
71
|
return target;
|
|
59
72
|
};
|
|
60
73
|
const move = (dir) => {
|
|
@@ -67,7 +80,7 @@ export function KptOrderList({ items, defaultItems = EMPTY, onItemsChange, heade
|
|
|
67
80
|
const onItemKeyDown = (index, event) => {
|
|
68
81
|
if (event.altKey && (event.key === 'ArrowUp' || event.key === 'ArrowDown')) {
|
|
69
82
|
event.preventDefault();
|
|
70
|
-
setFocusRequest(moveFrom(index, event.key === 'ArrowUp' ? -1 : 1));
|
|
83
|
+
setFocusRequest({ index: moveFrom(index, event.key === 'ArrowUp' ? -1 : 1) });
|
|
71
84
|
return;
|
|
72
85
|
}
|
|
73
86
|
const next = nextMenuIndex(list.length, index, event.key);
|
|
@@ -75,7 +88,7 @@ export function KptOrderList({ items, defaultItems = EMPTY, onItemsChange, heade
|
|
|
75
88
|
return;
|
|
76
89
|
event.preventDefault();
|
|
77
90
|
setSelected(next);
|
|
78
|
-
setFocusRequest(next);
|
|
91
|
+
setFocusRequest({ index: next });
|
|
79
92
|
};
|
|
80
93
|
const clearDrag = () => {
|
|
81
94
|
setDragIndex(null);
|
|
@@ -114,6 +127,7 @@ export function KptOrderList({ items, defaultItems = EMPTY, onItemsChange, heade
|
|
|
114
127
|
next.splice(index, 0, moved);
|
|
115
128
|
setList(next);
|
|
116
129
|
setSelected(index);
|
|
130
|
+
announce(index, next.length);
|
|
117
131
|
};
|
|
118
132
|
const onListDragOver = (event) => {
|
|
119
133
|
if (dragIndex === null)
|
|
@@ -135,6 +149,7 @@ export function KptOrderList({ items, defaultItems = EMPTY, onItemsChange, heade
|
|
|
135
149
|
next.push(moved);
|
|
136
150
|
setList(next);
|
|
137
151
|
setSelected(next.length - 1);
|
|
152
|
+
announce(next.length - 1, next.length);
|
|
138
153
|
};
|
|
139
|
-
return (
|
|
154
|
+
return (_jsxs("div", { ref: composeRefs(rootRef, ref), className: cn('kpt-order-list-host', className), ...rest, children: [_jsxs("div", { className: "kpt-order-list", children: [_jsxs("div", { className: "kpt-order-list__controls", children: [_jsx("button", { type: "button", disabled: selected === null, "aria-label": t('orderList.up'), onClick: () => move(-1), children: _jsx(KptIcon, { name: "arrow-up" }) }), _jsx("button", { type: "button", disabled: selected === null, "aria-label": t('orderList.down'), onClick: () => move(1), children: _jsx(KptIcon, { name: "arrow-down" }) })] }), _jsxs("div", { className: "kpt-order-list__panel", children: [header ? _jsx("div", { className: "kpt-order-list__header", children: header }) : null, _jsx("ul", { className: cn('kpt-order-list__items', listOver && 'is-dragover'), role: "listbox", "aria-label": header || undefined, onDragOver: onListDragOver, onDragLeave: () => setListOver(false), onDrop: onListDrop, children: list.map((item, index) => (_jsx("li", { className: cn('kpt-order-list__item', selected === index && 'is-selected', dragIndex === index && 'is-dragging', dragdrop && 'kpt-order-list__item--draggable', overIndex === index && dragIndex !== index && 'kpt-order-list__item--dragover'), role: "option", "aria-selected": selected === index, tabIndex: index === tabStop ? 0 : -1, onFocus: () => setSelected(index), onKeyDown: (event) => onItemKeyDown(index, event), draggable: dragdrop || undefined, onClick: () => setSelected(index), onDragStart: (event) => onDragStart(index, event), onDragOver: (event) => onDragOver(index, event), onDragLeave: () => setOverIndex(null), onDrop: (event) => onDrop(index, event), onDragEnd: clearDrag, children: renderItem(item, index) }, index))) })] })] }), _jsx("p", { className: "kpt-visually-hidden", "aria-live": "polite", children: announcement })] }));
|
|
140
155
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
3
|
import { useEffect } from 'react';
|
|
3
4
|
import { KptButton } from "../button/button.js";
|
|
@@ -26,9 +27,11 @@ export function KptPaginator({ length, pageSize, defaultPageSize = 10, onPageSiz
|
|
|
26
27
|
defaultValue: defaultPageIndex,
|
|
27
28
|
onChange: onPageIndexChange,
|
|
28
29
|
});
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
const
|
|
30
|
+
// Rozmiar strony do rachunków — `0` albo ujemny dawałby `Infinity` stron i wieczne „dalej”.
|
|
31
|
+
const step = Math.max(1, size);
|
|
32
|
+
const totalPages = Math.max(1, Math.ceil(length / step));
|
|
33
|
+
const rangeStart = length === 0 ? 0 : index * step + 1;
|
|
34
|
+
const rangeEnd = Math.min(length, (index + 1) * step);
|
|
32
35
|
const canPrev = index > 0;
|
|
33
36
|
const canNext = index < totalPages - 1;
|
|
34
37
|
// Utrzymuj poprawny indeks, gdy zmieni się długość lub rozmiar strony.
|
|
@@ -47,5 +50,5 @@ export function KptPaginator({ length, pageSize, defaultPageSize = 10, onPageSiz
|
|
|
47
50
|
const sizeOptions = pageSizeOptions.includes(size)
|
|
48
51
|
? pageSizeOptions
|
|
49
52
|
: [...pageSizeOptions, size].sort((a, b) => a - b);
|
|
50
|
-
return (_jsxs("div", { className: cn('kpt-paginator', className), ...rest, children: [_jsx("div", { className: "kpt-paginator__size", children: _jsxs("label", { children: [t('paginator.rowsPerPage'), _jsx("select", { className: "kpt-paginator__select", value: size, onChange: onSizeChange, children: sizeOptions.map((option) => (_jsx("option", { value: option, children: option }, option))) })] }) }), _jsx("span", { className: "kpt-paginator__range", children: t('paginator.range', { start: rangeStart, end: rangeEnd, total: length }) }), _jsxs("div", { className: "kpt-paginator__nav", children: [_jsx(KptButton, { variant: "outline", size: "sm", disabled: !canPrev, onClick: () => setIndex(index - 1), "aria-label": t('paginator.prevPage'), children: prevIcon }), _jsx(KptButton, { variant: "outline", size: "sm", disabled: !canNext, onClick: () => setIndex(index + 1), "aria-label": t('paginator.nextPage'), children: nextIcon })] })] }));
|
|
53
|
+
return (_jsxs("div", { className: cn('kpt-paginator', className), ...rest, children: [_jsx("div", { className: "kpt-paginator__size", children: _jsxs("label", { children: [t('paginator.rowsPerPage'), _jsx("select", { className: "kpt-paginator__select", value: size, onChange: onSizeChange, children: sizeOptions.map((option) => (_jsx("option", { value: option, children: option }, option))) })] }) }), _jsx("span", { className: "kpt-paginator__range", "aria-live": "polite", children: t('paginator.range', { start: rangeStart, end: rangeEnd, total: length }) }), _jsxs("div", { className: "kpt-paginator__nav", children: [_jsx(KptButton, { variant: "outline", size: "sm", disabled: !canPrev, onClick: () => setIndex(index - 1), "aria-label": t('paginator.prevPage'), children: prevIcon }), _jsx(KptButton, { variant: "outline", size: "sm", disabled: !canNext, onClick: () => setIndex(index + 1), "aria-label": t('paginator.nextPage'), children: nextIcon })] })] }));
|
|
51
54
|
}
|
package/dist/panel/panel.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
+
import { isValidElement } from 'react';
|
|
3
4
|
import { cn } from "../internal/cn.js";
|
|
5
|
+
import { slotChildren } from "../internal/slot-children.js";
|
|
4
6
|
import { useControllableState } from "../internal/use-controllable-state.js";
|
|
5
7
|
import { useKptId } from "../internal/use-id.js";
|
|
6
8
|
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
@@ -24,10 +26,10 @@ export function KptPanel({ header = '', toggleable = false, collapsed, defaultCo
|
|
|
24
26
|
});
|
|
25
27
|
const actions = [];
|
|
26
28
|
const body = [];
|
|
27
|
-
|
|
29
|
+
for (const child of slotChildren(children)) {
|
|
28
30
|
const isActions = isValidElement(child) && child.type?.kptPanelSlot === 'actions';
|
|
29
31
|
(isActions ? actions : body).push(child);
|
|
30
|
-
}
|
|
32
|
+
}
|
|
31
33
|
return (_jsx("div", { className: cn('kpt-panel-host', className), style: { ['--kpt-anim-duration']: `${animated ? animationDuration : 0}ms`, ...style }, ...rest, children: _jsxs("div", { className: "kpt-panel", children: [_jsxs("div", { className: "kpt-panel__header", children: [_jsx("span", { className: "kpt-panel__title", children: header }), _jsx("span", { className: "kpt-panel__actions", children: actions }), toggleable ? (_jsx("button", { type: "button", className: "kpt-panel__toggle", onClick: () => setCollapsed(!isCollapsed), "aria-expanded": !isCollapsed, "aria-controls": bodyId, "aria-label": header ? t('panel.toggleNamed', { header }) : t('panel.toggle'), children: _jsx(KptIcon, { name: isCollapsed ? 'chevron-down' : 'chevron-up' }) })) : null] }), _jsx("div", { className: cn('kpt-panel__collapse', !isCollapsed && 'is-open'), id: bodyId, inert: isCollapsed, "aria-hidden": isCollapsed, children: _jsx("div", { className: "kpt-panel__collapse-inner", children: _jsx("div", { className: "kpt-panel__body", children: body }) }) })] }) }));
|
|
32
34
|
}
|
|
33
35
|
/** Akcje w nagłówku panelu, obok tytułu. */
|
package/dist/password/llms.txt
CHANGED
|
@@ -28,8 +28,12 @@ fills in the OLD password instead of offering a new one, and the user ends up sa
|
|
|
28
28
|
thing.
|
|
29
29
|
|
|
30
30
|
## How the score is computed
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
A 0–4 score. The score follows length first, not composition rules (NIST SP 800-63B advises against them): an
|
|
32
|
+
approximate entropy over the character classes actually used, cut for repeats (`aaaa`), sequences
|
|
33
|
+
(`abcd`, `1234`, `qwerty`) and the "Word + digits + symbol" shape. `Password1!` is weak, thirty
|
|
34
|
+
lowercase letters are strong, and `ą`/`ż` are letters, not special characters. There is no
|
|
35
|
+
dictionary — it is a hint, not a security control.
|
|
36
|
+
The label comes from `password.strength` — the only array-valued key in
|
|
33
37
|
the dictionary — indexed by the score, so a translation controls the wording without touching code.
|
|
34
38
|
|
|
35
39
|
## Examples
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
3
|
import { useState } from 'react';
|
|
3
4
|
import { cn } from "../internal/cn.js";
|
|
@@ -5,20 +6,8 @@ import { useControllableState } from "../internal/use-controllable-state.js";
|
|
|
5
6
|
import { useKptId } from "../internal/use-id.js";
|
|
6
7
|
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
7
8
|
import { joinIds, useKptFieldA11y } from "../form-field/form-field-context.js";
|
|
9
|
+
import { kptPasswordScore } from "../internal/password-strength.js";
|
|
8
10
|
import { KptIcon } from "../icon/icon.js";
|
|
9
|
-
/** Cztery niezależne kryteria; każde spełnione podnosi wynik o jeden. */
|
|
10
|
-
function scoreOf(password) {
|
|
11
|
-
let score = 0;
|
|
12
|
-
if (password.length >= 8)
|
|
13
|
-
score++;
|
|
14
|
-
if (/[A-Z]/.test(password) && /[a-z]/.test(password))
|
|
15
|
-
score++;
|
|
16
|
-
if (/\d/.test(password))
|
|
17
|
-
score++;
|
|
18
|
-
if (/[^A-Za-z0-9]/.test(password))
|
|
19
|
-
score++;
|
|
20
|
-
return Math.min(score, 4);
|
|
21
|
-
}
|
|
22
11
|
/**
|
|
23
12
|
* Pole hasła z przełącznikiem widoczności i opcjonalnym miernikiem siły.
|
|
24
13
|
*
|
|
@@ -33,6 +22,7 @@ export function KptPassword({ value, defaultValue = '', onValueChange, placehold
|
|
|
33
22
|
const [current, setCurrent] = useControllableState({ value, defaultValue, onChange: onValueChange });
|
|
34
23
|
const [visible, setVisible] = useState(false);
|
|
35
24
|
const showInvalid = invalid && (touched ?? true);
|
|
36
|
-
|
|
25
|
+
// Długość i różnorodność zamiast reguł składu — szczegóły w `internal/password-strength.ts`.
|
|
26
|
+
const score = kptPasswordScore(current);
|
|
37
27
|
return (_jsxs("div", { className: cn('kpt-password-host', className), ...rest, children: [_jsxs("div", { className: cn('kpt-password', disabled && 'kpt-password--disabled', showInvalid && 'kpt-password--invalid'), children: [_jsx("input", { className: "kpt-password__input", id: inputId, type: visible ? 'text' : 'password', value: current, disabled: disabled, readOnly: readOnly, "aria-invalid": showInvalid || undefined, "aria-required": required || undefined, "aria-label": ariaLabel ?? undefined, "aria-describedby": joinIds(field.describedBy, feedback && current && meterId), placeholder: placeholder, autoComplete: autoComplete ?? undefined, onChange: (event) => setCurrent(event.target.value), onBlur: () => onTouch?.() }), _jsx("button", { type: "button", className: "kpt-password__toggle", disabled: disabled, onClick: () => setVisible(!visible), "aria-label": visible ? t('password.hide') : t('password.show'), children: _jsx(KptIcon, { name: visible ? 'eye-off' : 'eye' }) })] }), feedback && current ? (_jsxs("div", { className: "kpt-password__meter", id: meterId, "data-score": score, children: [_jsx("div", { className: "kpt-password__bars", "aria-hidden": "true", children: [0, 1, 2, 3].map((index) => (_jsx("span", { className: cn('kpt-password__bar', index < score && 'is-on') }, index))) }), _jsx("span", { className: "kpt-password__hint", children: messages.password.strength[score] })] })) : null] }));
|
|
38
28
|
}
|
package/dist/peek/llms.txt
CHANGED
|
@@ -31,7 +31,7 @@ Hover opens after `openDelay` and leaving closes after `closeDelay` — the poin
|
|
|
31
31
|
travel from the trigger to the card, so entering the card cancels the close. Focus opens it with no
|
|
32
32
|
delay. A click, Enter or a tap **pins** the card: it stays after the pointer leaves and focus moves
|
|
33
33
|
inside it. That is the only route to the card's content where there is no hover at all. Escape
|
|
34
|
-
closes it and hands focus back to the trigger; so does a pointer press outside.
|
|
34
|
+
closes it and hands focus back to the trigger; so does a pointer press outside. Escape on the trigger itself closes a card opened by hover or focus without moving focus (WCAG 1.4.13); with the card closed the key passes through to an enclosing dialog.
|
|
35
35
|
|
|
36
36
|
## Which of the three
|
|
37
37
|
`KptTooltip` carries plain text and cannot be clicked. `KptPopover` opens on click and puts a
|
package/dist/peek/peek.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { cloneElement, isValidElement, useCallback, useEffect, useId, useRef, useState, } from 'react';
|
|
3
|
+
import { cloneElement, isValidElement, useCallback, useEffect, useEffectEvent, useId, useRef, useState, } from 'react';
|
|
3
4
|
import { cn } from "../internal/cn.js";
|
|
4
5
|
import { KptOverlay } from "../internal/overlay.js";
|
|
5
6
|
import { mergeSlotProps } from "../internal/slot.js";
|
|
@@ -24,6 +25,14 @@ export function KptPeek({ trigger, children, placement = 'bottom', openDelay = 3
|
|
|
24
25
|
const panelRef = useRef(null);
|
|
25
26
|
const timer = useRef(undefined);
|
|
26
27
|
const [open, setOpen] = useState(false);
|
|
28
|
+
/** Kotwica nakładki w stanie (callback ref), nie `triggerRef.current` czytane w renderze. */
|
|
29
|
+
const [anchor, setAnchor] = useState(null);
|
|
30
|
+
const setTriggerRef = useCallback((el) => {
|
|
31
|
+
triggerRef.current = el;
|
|
32
|
+
setAnchor(el);
|
|
33
|
+
}, []);
|
|
34
|
+
/** Tekst wyzwalacza jako nazwa karty — odczytany przy otwarciu, nie w renderze. */
|
|
35
|
+
const [triggerName, setTriggerName] = useState('');
|
|
27
36
|
/** Przypięta karta zostaje po zjeździe kursora — przypina ją klik, Enter albo dotyk. */
|
|
28
37
|
const [pinned, setPinned] = useState(false);
|
|
29
38
|
const panelId = useId();
|
|
@@ -40,21 +49,24 @@ export function KptPeek({ trigger, children, placement = 'bottom', openDelay = 3
|
|
|
40
49
|
timer.current = setTimeout(action, delay);
|
|
41
50
|
}, [stop]);
|
|
42
51
|
const show = useCallback(() => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
setTriggerName(triggerRef.current?.textContent?.trim() ?? '');
|
|
53
|
+
setOpen(true);
|
|
54
|
+
}, []);
|
|
55
|
+
// `onOpenChange` po zmianie stanu, a nie wewnątrz funkcji aktualizującej `setOpen` — ta musi być
|
|
56
|
+
// czysta (StrictMode woła ją dwa razy, a React może ją powtórzyć przy przerwanym renderze).
|
|
57
|
+
const notifyOpen = useEffectEvent((value) => onOpenChange?.(value));
|
|
58
|
+
const reportedOpen = useRef(false);
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
if (reportedOpen.current === open)
|
|
61
|
+
return;
|
|
62
|
+
reportedOpen.current = open;
|
|
63
|
+
notifyOpen(open);
|
|
64
|
+
}, [open]);
|
|
49
65
|
const close = useCallback(() => {
|
|
50
66
|
stop();
|
|
51
67
|
setPinned(false);
|
|
52
|
-
setOpen(
|
|
53
|
-
|
|
54
|
-
onOpenChange?.(false);
|
|
55
|
-
return false;
|
|
56
|
-
});
|
|
57
|
-
}, [onOpenChange, stop]);
|
|
68
|
+
setOpen(false);
|
|
69
|
+
}, [stop]);
|
|
58
70
|
const hoverIn = useCallback(() => {
|
|
59
71
|
if (disabled)
|
|
60
72
|
return;
|
|
@@ -106,19 +118,29 @@ export function KptPeek({ trigger, children, placement = 'bottom', openDelay = 3
|
|
|
106
118
|
}, [close, open]);
|
|
107
119
|
const triggerProps = {
|
|
108
120
|
className: 'kpt-peek__trigger',
|
|
109
|
-
ref:
|
|
121
|
+
ref: setTriggerRef,
|
|
110
122
|
onMouseEnter: hoverIn,
|
|
111
123
|
onMouseLeave: hoverOut,
|
|
112
124
|
onFocus: focusIn,
|
|
113
125
|
onBlur: focusOut,
|
|
114
126
|
onClick: pin,
|
|
127
|
+
// Escape na wyzwalaczu odprawia kartę bez przenoszenia fokusu (WCAG 1.4.13). Zamknięta karta
|
|
128
|
+
// przepuszcza klawisz dalej — Escape nadal zamyka otaczające okno.
|
|
129
|
+
onKeyDown: (event) => {
|
|
130
|
+
if (event.key !== 'Escape' || !open)
|
|
131
|
+
return;
|
|
132
|
+
event.preventDefault();
|
|
133
|
+
event.stopPropagation();
|
|
134
|
+
close();
|
|
135
|
+
},
|
|
115
136
|
'aria-expanded': open,
|
|
116
137
|
'aria-controls': open ? panelId : undefined,
|
|
117
138
|
};
|
|
118
139
|
return (_jsxs("span", { ref: hostRef, className: cn('kpt-peek-host', className), ...rest, children: [isValidElement(trigger) ? (
|
|
119
140
|
// Propsy idą na sam wyzwalacz, nie na opakowanie: `aria-expanded` ma siedzieć na tym
|
|
120
141
|
// elemencie, po którym jeździ czytnik ekranu, a nie na obojętnym `<span>` obok.
|
|
121
|
-
|
|
142
|
+
// eslint-disable-next-line react-hooks/refs -- mergeSlotProps tylko składa handlery; refy czytają one dopiero w zdarzeniach
|
|
143
|
+
cloneElement(trigger, mergeSlotProps(triggerProps, trigger.props))) : (_jsx("span", { ...triggerProps, children: trigger })), _jsx(KptOverlay, { open: open, anchor: anchor, onClose: close, offset: 8, withBackdrop: false, preferAbove: placement === 'top', children: _jsx("div", { ref: panelRef, id: panelId, className: "kpt-peek__panel", role: "dialog", tabIndex: -1, "aria-label": label || triggerName || undefined, "data-pinned": pinned ? '' : undefined, onMouseEnter: hoverIn, onMouseLeave: hoverOut, onFocus: focusIn, onBlur: focusOut, onKeyDown: (event) => {
|
|
122
144
|
if (event.key !== 'Escape')
|
|
123
145
|
return;
|
|
124
146
|
close();
|
package/dist/pick-list/llms.txt
CHANGED
|
@@ -73,3 +73,6 @@ Each list is named after its header (`aria-label`). The four move buttons are la
|
|
|
73
73
|
dictionary, so "move all right" is distinguishable from "move right" without seeing the double
|
|
74
74
|
chevron. Native HTML5 drag and drop has no keyboard equivalent — `Enter` and the buttons cover
|
|
75
75
|
everything it does, and `dragdrop` adds to them, it never replaces them.
|
|
76
|
+
|
|
77
|
+
## Announcements and buttons
|
|
78
|
+
A transfer between the lists is announced in a polite live region: `pickList.moved` with `{count}` and `{list}` (the target header). The move buttons carry `aria-disabled` when there is nothing to move (no selection, empty list) — they stay focusable, so focus does not drop to `<body>` when the selection clears after a move.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
3
|
import { useEffect, useRef, useState } from 'react';
|
|
3
4
|
import { cn } from "../internal/cn.js";
|
|
@@ -62,13 +63,14 @@ export function KptPickList({ source, defaultSource = EMPTY, onSourceChange, tar
|
|
|
62
63
|
const setFocusOf = (side) => (side === 'source' ? setFocusLeft : setFocusRight);
|
|
63
64
|
/** Przystanek Tab listy — ostatnio skupiona pozycja, przycięta do długości listy. */
|
|
64
65
|
const tabIndexOf = (side) => Math.min(focusOf(side), listOf(side).length - 1);
|
|
66
|
+
// Żądanie nie jest zerowane w efekcie (setState w efekcie = dodatkowy przebieg renderu): każde jest
|
|
67
|
+
// nowym obiektem, więc efekt rusza raz na żądanie, także o ten sam indeks.
|
|
65
68
|
useEffect(() => {
|
|
66
69
|
if (!focusRequest)
|
|
67
70
|
return;
|
|
68
71
|
const list = rootRef.current?.querySelectorAll('.kpt-pick-list__items')[focusRequest.side === 'source' ? 0 : 1];
|
|
69
72
|
const options = list?.querySelectorAll('[role="option"]') ?? [];
|
|
70
73
|
options[Math.min(focusRequest.index, options.length - 1)]?.focus();
|
|
71
|
-
setFocusRequest(null);
|
|
72
74
|
}, [focusRequest]);
|
|
73
75
|
const focusItem = (side, index) => {
|
|
74
76
|
setFocusOf(side)(index);
|
|
@@ -82,6 +84,17 @@ export function KptPickList({ source, defaultSource = EMPTY, onSourceChange, tar
|
|
|
82
84
|
next.add(item);
|
|
83
85
|
setSelectionOf(side)(next);
|
|
84
86
|
};
|
|
87
|
+
const headerOf = (side) => side === 'source' ? (sourceHeader ?? t('pickList.available')) : (targetHeader ?? t('pickList.selected'));
|
|
88
|
+
/** Treść regionu `aria-live` po przeniesieniu. */
|
|
89
|
+
const [announcement, setAnnouncement] = useState('');
|
|
90
|
+
/**
|
|
91
|
+
* Ogłasza przeniesienie. Ten sam tekst drugi raz pod rząd nie zostałby przeczytany (region
|
|
92
|
+
* reaguje na zmianę), więc naprzemiennie dokładamy niewidoczną spację.
|
|
93
|
+
*/
|
|
94
|
+
const announce = (count, to) => {
|
|
95
|
+
const text = t('pickList.moved', { count, list: headerOf(to) });
|
|
96
|
+
setAnnouncement((current) => (current === text ? text + '\u00a0' : text));
|
|
97
|
+
};
|
|
85
98
|
const move = (from, all) => {
|
|
86
99
|
const to = other(from);
|
|
87
100
|
const fromItems = listOf(from);
|
|
@@ -92,6 +105,7 @@ export function KptPickList({ source, defaultSource = EMPTY, onSourceChange, tar
|
|
|
92
105
|
setListOf(from)(fromItems.filter((item) => !moving.includes(item)));
|
|
93
106
|
setListOf(to)([...listOf(to), ...moving]);
|
|
94
107
|
setSelectionOf(from)(new Set());
|
|
108
|
+
announce(moving.length, to);
|
|
95
109
|
};
|
|
96
110
|
/** Przenosi jeden element między panelami, wstawiając go pod wskazanym indeksem. */
|
|
97
111
|
const transfer = (fromSide, fromIndex, toSide, toIndex) => {
|
|
@@ -103,6 +117,8 @@ export function KptPickList({ source, defaultSource = EMPTY, onSourceChange, tar
|
|
|
103
117
|
toItems.splice(toIndex, 0, moved);
|
|
104
118
|
setListOf(fromSide)(fromItems);
|
|
105
119
|
setListOf(toSide)(toItems);
|
|
120
|
+
if (fromSide !== toSide)
|
|
121
|
+
announce(1, toSide);
|
|
106
122
|
// Element opuszcza panel — jego zaznaczenie po tamtej stronie przestaje cokolwiek znaczyć.
|
|
107
123
|
const selection = selectionOf(fromSide);
|
|
108
124
|
if (selection.has(moved)) {
|
|
@@ -208,11 +224,11 @@ export function KptPickList({ source, defaultSource = EMPTY, onSourceChange, tar
|
|
|
208
224
|
const column = (side) => {
|
|
209
225
|
const items = listOf(side);
|
|
210
226
|
const selection = selectionOf(side);
|
|
211
|
-
const header = side
|
|
227
|
+
const header = headerOf(side);
|
|
212
228
|
return (_jsxs("div", { className: "kpt-pick-list__col", children: [_jsx("div", { className: "kpt-pick-list__header", children: header }), _jsx("ul", { className: cn('kpt-pick-list__items', listOver === side && 'is-dragover'), role: "listbox", "aria-multiselectable": "true", "aria-label": header, onDragOver: (event) => onListDragOver(side, event), onDragLeave: () => setListOver(null), onDrop: (event) => onListDrop(side, event), children: items.map((item, index) => {
|
|
213
229
|
const dragging = drag?.side === side && drag.index === index;
|
|
214
230
|
return (_jsx("li", { className: cn('kpt-pick-list__item', selection.has(item) && 'is-selected', dragging && 'is-dragging', dragdrop && 'kpt-pick-list__item--draggable', over?.side === side && over.index === index && !dragging && 'kpt-pick-list__item--dragover'), role: "option", "aria-selected": selection.has(item), tabIndex: index === tabIndexOf(side) ? 0 : -1, onFocus: () => setFocusOf(side)(index), onKeyDown: (event) => onItemKeyDown(side, index, item, event), draggable: dragdrop || undefined, onClick: () => toggle(side, item), onDoubleClick: () => moveOne(side, item), onDragStart: (event) => onDragStart(side, index, event), onDragOver: (event) => onDragOver(side, index, event), onDragLeave: () => setOver(null), onDrop: (event) => onDrop(side, index, event), onDragEnd: clearDrag, children: renderItem(item, index) }, index));
|
|
215
231
|
}) })] }));
|
|
216
232
|
};
|
|
217
|
-
return (
|
|
233
|
+
return (_jsxs("div", { ref: composeRefs(rootRef, ref), className: cn('kpt-pick-list-host', className), ...rest, children: [_jsxs("div", { className: "kpt-pick-list", children: [column('source'), _jsxs("div", { className: "kpt-pick-list__controls", children: [_jsxs("button", { type: "button", "aria-label": t('pickList.moveAllRight'), "aria-disabled": !left.length || undefined, onClick: () => move('source', true), children: [_jsx(KptIcon, { name: "chevron-right" }), _jsx(KptIcon, { name: "chevron-right" })] }), _jsx("button", { type: "button", "aria-label": t('pickList.moveRight'), "aria-disabled": !selLeft.size || undefined, onClick: () => move('source', false), children: _jsx(KptIcon, { name: "chevron-right" }) }), _jsx("button", { type: "button", "aria-label": t('pickList.moveLeft'), "aria-disabled": !selRight.size || undefined, onClick: () => move('target', false), children: _jsx(KptIcon, { name: "chevron-left" }) }), _jsxs("button", { type: "button", "aria-label": t('pickList.moveAllLeft'), "aria-disabled": !right.length || undefined, onClick: () => move('target', true), children: [_jsx(KptIcon, { name: "chevron-left" }), _jsx(KptIcon, { name: "chevron-left" })] })] }), column('target')] }), _jsx("p", { className: "kpt-visually-hidden", "aria-live": "polite", children: announcement })] }));
|
|
218
234
|
}
|
package/dist/popover/llms.txt
CHANGED
|
@@ -47,3 +47,6 @@ open, `aria-controls` — on the real `<button>`, not on the wrapper. Opening mo
|
|
|
47
47
|
interactive element of the bubble; `Escape` or a click outside closes it, and focus returns to the
|
|
48
48
|
trigger when it was inside the bubble. Focus is not trapped — the popover is supporting content, not
|
|
49
49
|
a modal.
|
|
50
|
+
|
|
51
|
+
## Name and Escape
|
|
52
|
+
Give the panel `ariaLabel` — it is a `role="dialog"`. Escape closes only the top-most panel and does not reach an enclosing `<dialog>`, so a popover inside a dialog closes alone.
|
|
@@ -9,6 +9,8 @@ export interface KptPopoverProps extends Omit<ComponentPropsWithRef<'span'>, 'co
|
|
|
9
9
|
onOpenChange?: (open: boolean) => void;
|
|
10
10
|
/** Preferowana strona dymka; gdy brakuje miejsca, przechodzi na drugą. */
|
|
11
11
|
placement?: 'bottom' | 'top';
|
|
12
|
+
/** Nazwa panelu (`role="dialog"`) — okno bez nazwy czytnik ogłasza jako samo „okno dialogowe”. */
|
|
13
|
+
ariaLabel?: string;
|
|
12
14
|
}
|
|
13
15
|
/**
|
|
14
16
|
* Popover — dowolna treść w dymku przypiętym do wyzwalacza (bogatszy niż tooltip).
|
|
@@ -26,4 +28,4 @@ export interface KptPopoverProps extends Omit<ComponentPropsWithRef<'span'>, 'co
|
|
|
26
28
|
* <div>Bogata treść popovera…</div>
|
|
27
29
|
* </KptPopover>
|
|
28
30
|
*/
|
|
29
|
-
export declare function KptPopover({ trigger, children, open, defaultOpen, onOpenChange, placement, className, ...rest }: Readonly<KptPopoverProps>): import("react").JSX.Element;
|
|
31
|
+
export declare function KptPopover({ trigger, children, open, defaultOpen, onOpenChange, placement, ariaLabel, className, ...rest }: Readonly<KptPopoverProps>): import("react").JSX.Element;
|
package/dist/popover/popover.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useEffect, useRef } from 'react';
|
|
3
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
4
|
import { cn } from "../internal/cn.js";
|
|
4
5
|
import { KptOverlay } from "../internal/overlay.js";
|
|
5
6
|
import { useControllableState } from "../internal/use-controllable-state.js";
|
|
@@ -22,8 +23,14 @@ const FOCUSABLE = 'a[href], button:not(:disabled), input:not(:disabled), select,
|
|
|
22
23
|
* <div>Bogata treść popovera…</div>
|
|
23
24
|
* </KptPopover>
|
|
24
25
|
*/
|
|
25
|
-
export function KptPopover({ trigger, children, open, defaultOpen = false, onOpenChange, placement = 'bottom', className, ...rest }) {
|
|
26
|
+
export function KptPopover({ trigger, children, open, defaultOpen = false, onOpenChange, placement = 'bottom', ariaLabel = '', className, ...rest }) {
|
|
26
27
|
const triggerRef = useRef(null);
|
|
28
|
+
/** Kotwica panelu — w stanie, bo czyta ją render; ref zostaje dla handlerów. */
|
|
29
|
+
const [anchor, setAnchor] = useState(null);
|
|
30
|
+
const setTrigger = useCallback((element) => {
|
|
31
|
+
triggerRef.current = element;
|
|
32
|
+
setAnchor(element);
|
|
33
|
+
}, []);
|
|
27
34
|
const panelRef = useRef(null);
|
|
28
35
|
const panelId = useKptId(undefined, 'kpt-popover');
|
|
29
36
|
const [isOpen, setOpen] = useControllableState({
|
|
@@ -58,5 +65,5 @@ export function KptPopover({ trigger, children, open, defaultOpen = false, onOpe
|
|
|
58
65
|
findControl()?.focus();
|
|
59
66
|
setOpen(false);
|
|
60
67
|
}, [setOpen]);
|
|
61
|
-
return (_jsxs("span", { className: cn('kpt-popover-host', className), ...rest, children: [_jsx("span", { className: "kpt-popover__trigger", ref:
|
|
68
|
+
return (_jsxs("span", { className: cn('kpt-popover-host', className), ...rest, children: [_jsx("span", { className: "kpt-popover__trigger", ref: setTrigger, onClick: () => setOpen(!isOpen), children: trigger }), _jsx(KptOverlay, { open: isOpen, anchor: anchor, onClose: close, offset: 6, preferAbove: placement === 'top', children: _jsx("div", { ref: panelRef, id: panelId, className: "kpt-popover__panel", role: "dialog", "aria-label": ariaLabel || undefined, children: children }) })] }));
|
|
62
69
|
}
|
package/dist/public-api.d.ts
CHANGED
|
@@ -120,7 +120,7 @@ export { KptButtonGroup } from './button-group/button-group.tsx';
|
|
|
120
120
|
export type { KptButtonGroupProps } from './button-group/button-group.tsx';
|
|
121
121
|
export { KptSelect } from './select/select.tsx';
|
|
122
122
|
export type { KptSelectProps } from './select/select.tsx';
|
|
123
|
-
export type { KptAlertVariant, KptOption } from './shared/types.ts';
|
|
123
|
+
export type { KptAlertVariant, KptNavigateEvent, KptOption } from './shared/types.ts';
|
|
124
124
|
export { KptFormField } from './form-field/form-field.tsx';
|
|
125
125
|
export type { KptFormFieldLabelType, KptFormFieldProps } from './form-field/form-field.tsx';
|
|
126
126
|
export { useKptFieldA11y, useKptFormFieldContext, useKptFormFieldFilled } from './form-field/form-field-context.ts';
|
|
@@ -49,3 +49,6 @@ is added to `aria-describedby`. New: `id`, `ariaLabel` and `required` (→ `aria
|
|
|
49
49
|
## Accessibility
|
|
50
50
|
The container is `role="radiogroup"` with `aria-invalid` when the error shows; every option is a
|
|
51
51
|
native radio inside a `<label>`, so the label text is clickable and is the accessible name.
|
|
52
|
+
|
|
53
|
+
## Read-only and required
|
|
54
|
+
`readOnly` blocks the click and the arrow keys (natively they move the selection); the group gets `aria-readonly`.
|
|
@@ -16,6 +16,8 @@ export interface KptRadioGroupProps<T> extends Omit<ComponentPropsWithRef<'div'>
|
|
|
16
16
|
/** Układ opcji: pod sobą (domyślnie) albo w rzędzie, z zawijaniem. */
|
|
17
17
|
orientation?: 'vertical' | 'horizontal';
|
|
18
18
|
disabled?: boolean;
|
|
19
|
+
/** Tylko do odczytu — wybór widać, nie da się go zmienić (`aria-readonly`). */
|
|
20
|
+
readOnly?: boolean;
|
|
19
21
|
invalid?: boolean;
|
|
20
22
|
touched?: boolean;
|
|
21
23
|
onTouch?: () => void;
|
|
@@ -29,4 +31,4 @@ export interface KptRadioGroupProps<T> extends Omit<ComponentPropsWithRef<'div'>
|
|
|
29
31
|
* @example
|
|
30
32
|
* <KptRadioGroup options={plans} value={plan} onValueChange={setPlan} />
|
|
31
33
|
*/
|
|
32
|
-
export declare function KptRadioGroup<T>({ options, value, defaultValue, onValueChange, name, id, ariaLabel, required, orientation, disabled, invalid, touched, onTouch, className, ...rest }: Readonly<KptRadioGroupProps<T>>): import("react").JSX.Element;
|
|
34
|
+
export declare function KptRadioGroup<T>({ options, value, defaultValue, onValueChange, name, id, ariaLabel, required, orientation, disabled, readOnly, invalid, touched, onTouch, className, ...rest }: Readonly<KptRadioGroupProps<T>>): import("react").JSX.Element;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
3
|
import { cn } from "../internal/cn.js";
|
|
4
|
+
import { optionKey } from "../internal/option-key.js";
|
|
3
5
|
import { useControllableState } from "../internal/use-controllable-state.js";
|
|
4
6
|
import { useKptId } from "../internal/use-id.js";
|
|
5
7
|
import { useKptFieldA11y } from "../form-field/form-field-context.js";
|
|
@@ -12,7 +14,7 @@ import { useKptFieldA11y } from "../form-field/form-field-context.js";
|
|
|
12
14
|
* @example
|
|
13
15
|
* <KptRadioGroup options={plans} value={plan} onValueChange={setPlan} />
|
|
14
16
|
*/
|
|
15
|
-
export function KptRadioGroup({ options = [], value, defaultValue, onValueChange, name, id, ariaLabel = null, required = false, orientation = 'vertical', disabled = false, invalid = false, touched, onTouch, className, ...rest }) {
|
|
17
|
+
export function KptRadioGroup({ options = [], value, defaultValue, onValueChange, name, id, ariaLabel = null, required = false, orientation = 'vertical', disabled = false, readOnly = false, invalid = false, touched, onTouch, className, ...rest }) {
|
|
16
18
|
/** Grupa nie dostaje nazwy z `label[for]` — stąd `aria-labelledby` na etykietę wrappera. */
|
|
17
19
|
const field = useKptFieldA11y(id, 'kpt-radio-group');
|
|
18
20
|
const groupName = useKptId(name, 'kpt-radio');
|
|
@@ -22,5 +24,14 @@ export function KptRadioGroup({ options = [], value, defaultValue, onValueChange
|
|
|
22
24
|
onChange: onValueChange,
|
|
23
25
|
});
|
|
24
26
|
const showInvalid = invalid && (touched ?? true);
|
|
25
|
-
return (_jsx("div", { className: cn('kpt-radio-group-host', className), ...rest, children: _jsx("div", { className: cn('kpt-radio-group', orientation === 'horizontal' && 'kpt-radio-group--horizontal'), role: "radiogroup", "aria-orientation": orientation, "aria-invalid": showInvalid || undefined, id: field.id, "aria-label": ariaLabel ?? undefined, "aria-labelledby": ariaLabel ? undefined : field.labelledBy, "aria-describedby": field.describedBy, "aria-required": required || undefined,
|
|
27
|
+
return (_jsx("div", { className: cn('kpt-radio-group-host', className), ...rest, children: _jsx("div", { className: cn('kpt-radio-group', orientation === 'horizontal' && 'kpt-radio-group--horizontal'), role: "radiogroup", "aria-orientation": orientation, "aria-invalid": showInvalid || undefined, id: field.id, "aria-label": ariaLabel ?? undefined, "aria-labelledby": ariaLabel ? undefined : field.labelledBy, "aria-describedby": field.describedBy, "aria-required": required || undefined, "aria-readonly": readOnly || undefined,
|
|
28
|
+
// Tylko do odczytu: strzałki natywnie przestawiałyby zaznaczenie.
|
|
29
|
+
onKeyDown: (event) => {
|
|
30
|
+
if (readOnly && ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', ' '].includes(event.key))
|
|
31
|
+
event.preventDefault();
|
|
32
|
+
}, children: options.map((option, index) => (_jsxs("label", { className: cn('kpt-radio', (disabled || option.disabled) && 'kpt-radio--disabled'), children: [_jsx("input", { type: "radio", className: "kpt-radio__input", name: groupName, checked: option.value === current, disabled: disabled || option.disabled, onChange: () => {
|
|
33
|
+
// Pole kontrolowane: bez zmiany stanu React przywraca poprzednie zaznaczenie.
|
|
34
|
+
if (!readOnly)
|
|
35
|
+
setCurrent(option.value);
|
|
36
|
+
}, onBlur: () => onTouch?.() }), _jsx("span", { className: "kpt-radio__dot", "aria-hidden": "true" }), _jsx("span", { className: "kpt-radio__label", children: option.label })] }, optionKey(option.value, index)))) }) }));
|
|
26
37
|
}
|
package/dist/rating/rating.js
CHANGED
package/dist/rich-text/llms.txt
CHANGED
|
@@ -37,6 +37,11 @@ coming from the application or the HTML source view.
|
|
|
37
37
|
Not just paste: a `value` from a database, a CMS or another user is untrusted too. Tags outside the
|
|
38
38
|
allowlist are unwrapped (the text survives), `on*` handlers and `javascript:` URLs are dropped,
|
|
39
39
|
`style` is filtered declaration by declaration. `sanitize.ts` knows nothing about React.
|
|
40
|
+
Links keep only relative addresses and `http(s):`, `mailto:`, `tel:` — the same allowlist as the link tool, so
|
|
41
|
+
`data:`, `vbscript:` or `blob:` from a paste are dropped. `class` keeps only the editor's own classes
|
|
42
|
+
(`kpt-rt-*`, `kpt-mention`), a link with `target` always gets `rel="noopener noreferrer"`, and when the
|
|
43
|
+
sanitizer removed something, the cleaned HTML is written back to `value` — the model never holds the
|
|
44
|
+
unsafe original (a pure formatting difference does not touch it, so a clean value does not dirty a form).
|
|
40
45
|
|
|
41
46
|
## Lists are indented by our own DOM code
|
|
42
47
|
`execCommand('indent')` produces invalid `ul > ul` in Chrome, so nesting goes through `lists.ts`
|
|
@@ -50,6 +55,12 @@ The toolbar is a named `role="toolbar"` ("Formatting", `richText.toolbar`) with
|
|
|
50
55
|
`←`/`→` (mirrored in RTL) and `Home`/`End` move between tools, skipping disabled ones; the stop
|
|
51
56
|
remembers the last tool, so Tab from the toolbar goes straight to the editor.
|
|
52
57
|
|
|
58
|
+
Tool panels open in the overlay container at the end of `<body>`, so focus moves into them:
|
|
59
|
+
the paragraph style list is a `listbox` (`↑`/`↓`, `Home`/`End`, `Enter`/`Space` picks, `Escape`/`Tab` close);
|
|
60
|
+
the table grid is one stop (arrows change the size 1–8, the size is announced, `Enter` inserts);
|
|
61
|
+
the colour palette starts on the first swatch (`←`/`→` between swatches, `Tab` cycles inside the
|
|
62
|
+
panel). `Escape` closes any of them and returns focus to the tool.
|
|
63
|
+
|
|
53
64
|
## Tables
|
|
54
65
|
Insert from an 8×8 grid popover. Dragging across cells makes a rectangular selection that grows
|
|
55
66
|
until it fully covers merged cells on its edges. Right-click opens the menu: insert and delete rows
|
|
@@ -89,3 +100,6 @@ generated. A `div` with a role is not named by `label[for]`, so inside `KptFormF
|
|
|
89
100
|
`aria-labelledby` pointing at the wrapper's label (unless `ariaLabel` is set), and the hint or error
|
|
90
101
|
is added to `aria-describedby`. New: `id`, `ariaLabel` and `required` (→ `aria-required`) on the
|
|
91
102
|
`role="textbox"` area.
|
|
103
|
+
|
|
104
|
+
## Read-only and required
|
|
105
|
+
`readOnly` keeps the content selectable and copyable but not editable (`aria-readonly`); the toolbar is disabled.
|
|
@@ -20,6 +20,8 @@ export interface KptRichTextProps extends Omit<ComponentPropsWithRef<'div'>, 'on
|
|
|
20
20
|
/** Oznacza pole jako wymagane atrybutem `aria-required`. */
|
|
21
21
|
required?: boolean;
|
|
22
22
|
disabled?: boolean;
|
|
23
|
+
/** Tylko do odczytu — treść da się zaznaczyć i skopiować, nie edytować (`aria-readonly`). */
|
|
24
|
+
readOnly?: boolean;
|
|
23
25
|
invalid?: boolean;
|
|
24
26
|
touched?: boolean;
|
|
25
27
|
onTouch?: () => void;
|
|
@@ -33,12 +35,4 @@ export interface KptRichTextProps extends Omit<ComponentPropsWithRef<'div'>, 'on
|
|
|
33
35
|
/** Wstawiona wzmianka — moment na powiadomienie osoby albo dopięcie powiązania. */
|
|
34
36
|
onMention?: (item: KptMentionItem) => void;
|
|
35
37
|
}
|
|
36
|
-
|
|
37
|
-
* Lekki edytor tekstu sformatowanego na `contenteditable`, z konfigurowalnym paskiem sekcji
|
|
38
|
-
* i edycją tabel. Wartością jest HTML.
|
|
39
|
-
*
|
|
40
|
-
* @example
|
|
41
|
-
* <KptRichText value={html} onValueChange={setHtml} />
|
|
42
|
-
* <KptRichText value={html} onValueChange={setHtml} toolbar={[['bold', 'italic'], ['link']]} />
|
|
43
|
-
*/
|
|
44
|
-
export declare function KptRichText({ value, defaultValue, onValueChange, toolbar, placeholder, maxListLevel, id, ariaLabel, required, disabled, invalid, touched, onTouch, mentions, mentionLimit, onMention, locale, dictionary, className, ...rest }: Readonly<KptRichTextProps>): import("react").JSX.Element;
|
|
38
|
+
export declare function KptRichText({ value, defaultValue, onValueChange, toolbar, placeholder, maxListLevel, id, ariaLabel, required, disabled: disabledProp, readOnly, invalid, touched, onTouch, mentions, mentionLimit, onMention, locale, dictionary, className, ...rest }: Readonly<KptRichTextProps>): import("react").JSX.Element;
|