@konce-pt/react 0.16.1 → 0.18.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 +23 -19
- package/dist/carousel/carousel.d.ts +10 -2
- package/dist/carousel/carousel.js +21 -5
- package/dist/carousel/llms.txt +7 -1
- package/dist/charts/chart.js +26 -12
- package/dist/charts/llms.txt +11 -1
- package/dist/data-table/data-table.d.ts +8 -1
- package/dist/data-table/data-table.js +30 -11
- package/dist/data-table/llms.txt +6 -1
- package/dist/dialog/dialog.js +7 -0
- package/dist/galleria/galleria.d.ts +6 -0
- package/dist/galleria/galleria.js +70 -4
- package/dist/galleria/llms.txt +29 -2
- package/dist/image/image.d.ts +4 -0
- package/dist/image/image.js +55 -4
- package/dist/image/llms.txt +15 -0
- package/dist/input/input.d.ts +13 -2
- package/dist/input/input.js +49 -3
- package/dist/input/llms.txt +20 -0
- package/dist/popover/llms.txt +3 -0
- package/dist/public-api.d.ts +5 -3
- package/dist/public-api.js +1 -0
- package/dist/select/llms.txt +5 -1
- package/dist/select/select.d.ts +24 -2
- package/dist/select/select.js +0 -19
- package/dist/tabs/llms.txt +40 -8
- package/dist/tabs/tabs.d.ts +32 -3
- package/dist/tabs/tabs.js +39 -7
- package/package.json +14 -14
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useRef, useState } from 'react';
|
|
3
|
+
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
4
|
+
import { attachDragScroll, playGallerySlide, resolveEnterMotion, revealThumb, scrollStrip, stopGallerySlide, trackStripEdges, } from '@konce-pt/motion';
|
|
5
|
+
import { useKptMotion } from "../motion/motion-context.js";
|
|
4
6
|
import { cn } from "../internal/cn.js";
|
|
5
7
|
import { useControllableState } from "../internal/use-controllable-state.js";
|
|
6
8
|
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
@@ -15,6 +17,12 @@ const NAV_KEYS = new Set(['ArrowLeft', 'ArrowRight', 'Home', 'End']);
|
|
|
15
17
|
* aktywną. Miniatura ma nazwę z `alt` obrazu i `aria-current` na aktywnej, a zmiana obrazu jest
|
|
16
18
|
* ogłaszana uprzejmie ("Obraz 2 z 4: ...") - dopiero po działaniu użytkownika, nie przy starcie.
|
|
17
19
|
*
|
|
20
|
+
* Ruch: nowe zdjęcie wjeżdża w kierunku zmiany na sprężynie, a poprzednie cofa się wolniej
|
|
21
|
+
* i przygasa (paralaksa); rodzaj z `enter` w `KptMotionProvider`, przy "ogranicz ruch" - przenikanie.
|
|
22
|
+
* Pasek miniatur nie ma paska przewijania: przewija się przeciąganiem (mysz, pióro, palec)
|
|
23
|
+
* albo strzałkami paska, krawędzie gasną tam, gdzie jest coś dalej, a aktywna miniatura
|
|
24
|
+
* zawsze wraca do widoku.
|
|
25
|
+
*
|
|
18
26
|
* @example
|
|
19
27
|
* <KptGalleria items={photos} activeIndex={index} onActiveIndexChange={setIndex} />
|
|
20
28
|
*/
|
|
@@ -31,8 +39,66 @@ export function KptGalleria({ items = [], activeIndex, defaultActiveIndex = 0, o
|
|
|
31
39
|
const [userMoved, setUserMoved] = useState(false);
|
|
32
40
|
const thumbsRef = useRef([]);
|
|
33
41
|
const rootRef = useRef(null);
|
|
42
|
+
const stageRef = useRef(null);
|
|
43
|
+
const imageRef = useRef(null);
|
|
44
|
+
const stripRef = useRef(null);
|
|
45
|
+
const stripWrapRef = useRef(null);
|
|
46
|
+
const edgesRef = useRef(null);
|
|
47
|
+
/** Co było pokazane przy poprzednim renderze - z tego wynika kierunek i duch poprzedniego zdjęcia. */
|
|
48
|
+
const shownRef = useRef(null);
|
|
49
|
+
const motion = resolveEnterMotion(undefined, useKptMotion());
|
|
34
50
|
const current = items[active] ?? null;
|
|
35
51
|
const last = items.length - 1;
|
|
52
|
+
const currentSrc = current?.src ?? '';
|
|
53
|
+
// Zmiana zdjęcia: po wpisaniu nowego `src` do DOM, przed malowaniem. Pierwszy render i wymiana
|
|
54
|
+
// listy bez zmiany indeksu nie grają ruchu - przesuw ilustruje przejście, nie ładowanie.
|
|
55
|
+
useLayoutEffect(() => {
|
|
56
|
+
const prev = shownRef.current;
|
|
57
|
+
shownRef.current = { index: active, src: currentSrc };
|
|
58
|
+
const strip = stripRef.current;
|
|
59
|
+
const thumb = thumbsRef.current[active];
|
|
60
|
+
// Start na dalekim zdjęciu: pasek od razu pokazuje aktywną miniaturę, bez przewijania na oczach.
|
|
61
|
+
if (!prev) {
|
|
62
|
+
if (strip && thumb)
|
|
63
|
+
revealThumb(strip, thumb, false);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (prev.index === active)
|
|
67
|
+
return;
|
|
68
|
+
const stage = stageRef.current;
|
|
69
|
+
const image = imageRef.current;
|
|
70
|
+
if (stage && image && prev.src && prev.src !== currentSrc) {
|
|
71
|
+
playGallerySlide({ stage, image, previousSrc: prev.src, direction: active > prev.index ? 1 : -1, motion });
|
|
72
|
+
}
|
|
73
|
+
if (strip && thumb)
|
|
74
|
+
revealThumb(strip, thumb);
|
|
75
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- ruch czyta konfigurację z chwili zmiany; sama zmiana konfiguracji nie przesuwa zdjęcia
|
|
76
|
+
}, [active, currentSrc]);
|
|
77
|
+
// Pasek: krawędzie i przeciąganie - podpinane, gdy pasek istnieje, i odpinane razem z nim.
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
const strip = stripRef.current;
|
|
80
|
+
const wrap = stripWrapRef.current;
|
|
81
|
+
if (!strip || !wrap)
|
|
82
|
+
return;
|
|
83
|
+
const edges = trackStripEdges(strip, wrap);
|
|
84
|
+
edgesRef.current = edges;
|
|
85
|
+
const detach = attachDragScroll(strip);
|
|
86
|
+
return () => {
|
|
87
|
+
edges.dispose();
|
|
88
|
+
detach();
|
|
89
|
+
edgesRef.current = null;
|
|
90
|
+
};
|
|
91
|
+
}, [showThumbnails]);
|
|
92
|
+
// Nowa lista miniatur zmienia szerokość paska bez zmiany jego rozmiaru - krawędzie liczymy na nowo.
|
|
93
|
+
useEffect(() => edgesRef.current?.update(), [items]);
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
const stage = stageRef.current;
|
|
96
|
+
const image = imageRef;
|
|
97
|
+
return () => {
|
|
98
|
+
if (stage)
|
|
99
|
+
stopGallerySlide(stage, image.current);
|
|
100
|
+
};
|
|
101
|
+
}, []);
|
|
36
102
|
const select = (index) => {
|
|
37
103
|
if (last < 0)
|
|
38
104
|
return active;
|
|
@@ -57,7 +123,7 @@ export function KptGalleria({ items = [], activeIndex, defaultActiveIndex = 0, o
|
|
|
57
123
|
};
|
|
58
124
|
const position = t('galleria.position', { index: active + 1, total: items.length });
|
|
59
125
|
const announcement = userMoved ? (current?.alt ? `${position}: ${current.alt}` : position) : '';
|
|
60
|
-
return (_jsx("div", { className: cn('kpt-galleria-host', className), ...rest, children: _jsxs("div", { ref: rootRef, className: "kpt-galleria", onKeyDown: onKeyDown, children: [_jsxs("div", { className: "kpt-galleria__stage", children: [_jsx("button", { type: "button", className: "kpt-galleria__nav kpt-galleria__nav--prev", "aria-disabled": active <= 0 || undefined, "aria-label": t('common.previous'), onClick: () => select(active - 1), children: _jsx(KptIcon, { name: "chevron-left" }) }), current ? _jsx("img", { className: "kpt-galleria__image", src: current.src, alt: current.alt || '' }) : null, _jsx("button", { type: "button", className: "kpt-galleria__nav kpt-galleria__nav--next", "aria-disabled": active >= last || undefined, "aria-label": t('common.next'), onClick: () => select(active + 1), children: _jsx(KptIcon, { name: "chevron-right" }) })] }), showThumbnails ? (_jsx("div", { className: "kpt-galleria__thumbs", children: items.map((item, index) => (_jsx("button", { ref: (el) => {
|
|
61
|
-
|
|
62
|
-
|
|
126
|
+
return (_jsx("div", { className: cn('kpt-galleria-host', className), ...rest, children: _jsxs("div", { ref: rootRef, className: "kpt-galleria", onKeyDown: onKeyDown, children: [_jsxs("div", { ref: stageRef, className: "kpt-galleria__stage", children: [_jsx("button", { type: "button", className: "kpt-galleria__nav kpt-galleria__nav--prev", "aria-disabled": active <= 0 || undefined, "aria-label": t('common.previous'), onClick: () => select(active - 1), children: _jsx(KptIcon, { name: "chevron-left" }) }), current ? _jsx("img", { ref: imageRef, className: "kpt-galleria__image", src: current.src, alt: current.alt || '' }) : null, _jsx("button", { type: "button", className: "kpt-galleria__nav kpt-galleria__nav--next", "aria-disabled": active >= last || undefined, "aria-label": t('common.next'), onClick: () => select(active + 1), children: _jsx(KptIcon, { name: "chevron-right" }) })] }), showThumbnails ? (_jsxs("div", { ref: stripWrapRef, className: "kpt-galleria__strip", children: [_jsx("button", { type: "button", className: "kpt-galleria__scroll kpt-galleria__scroll--back", tabIndex: -1, "aria-label": t('galleria.scrollBack'), onClick: () => stripRef.current && scrollStrip(stripRef.current, -1), children: _jsx(KptIcon, { name: "chevron-left" }) }), _jsx("div", { ref: stripRef, className: "kpt-galleria__thumbs", children: items.map((item, index) => (_jsx("button", { ref: (el) => {
|
|
127
|
+
thumbsRef.current[index] = el;
|
|
128
|
+
}, type: "button", className: cn('kpt-galleria__thumb', index === active && 'is-active'), "aria-current": index === active ? 'true' : undefined, "aria-label": item.alt || t('galleria.image', { index: index + 1 }), onClick: () => select(index), children: _jsx("img", { src: item.thumbnail || item.src, alt: "", draggable: false }) }, index))) }), _jsx("button", { type: "button", className: "kpt-galleria__scroll kpt-galleria__scroll--forward", tabIndex: -1, "aria-label": t('galleria.scrollForward'), onClick: () => stripRef.current && scrollStrip(stripRef.current, 1), children: _jsx(KptIcon, { name: "chevron-right" }) })] })) : null, _jsx("span", { className: "kpt-galleria__live", "aria-live": "polite", children: announcement })] }) }));
|
|
63
129
|
}
|
package/dist/galleria/llms.txt
CHANGED
|
@@ -11,8 +11,12 @@ Import: `import { KptGalleria, type KptGalleriaItem } from '@konce-pt/react';`
|
|
|
11
11
|
<img class="kpt-galleria__image" src="..." alt="Mountain lake">
|
|
12
12
|
<button class="kpt-galleria__nav kpt-galleria__nav--next" aria-label="Next">...</button>
|
|
13
13
|
</div>
|
|
14
|
-
<div class="kpt-
|
|
15
|
-
<button class="kpt-
|
|
14
|
+
<div class="kpt-galleria__strip" data-scroll-end>
|
|
15
|
+
<button class="kpt-galleria__scroll kpt-galleria__scroll--back" tabindex="-1" aria-label="Scroll thumbnails back">...</button>
|
|
16
|
+
<div class="kpt-galleria__thumbs">
|
|
17
|
+
<button class="kpt-galleria__thumb is-active" aria-current="true" aria-label="Mountain lake"><img src="..." alt="" draggable="false"></button>...
|
|
18
|
+
</div>
|
|
19
|
+
<button class="kpt-galleria__scroll kpt-galleria__scroll--forward" tabindex="-1" aria-label="Scroll thumbnails forward">...</button>
|
|
16
20
|
</div>
|
|
17
21
|
<span class="kpt-galleria__live" aria-live="polite">Image 2 of 4: Pine forest</span>
|
|
18
22
|
</div>
|
|
@@ -43,6 +47,29 @@ The arrows use `common.previous` / `common.next`, the thumbnails without `alt` `
|
|
|
43
47
|
Anywhere inside the gallery, `ArrowLeft`/`ArrowRight` move to the neighbouring image (reversed in
|
|
44
48
|
RTL) and `Home`/`End` to the first/last. When focus is on a thumbnail, it follows the active one.
|
|
45
49
|
|
|
50
|
+
## Motion and the thumbnail strip
|
|
51
|
+
Changing the image slides the new one in from the side of travel on a soft spring, while the
|
|
52
|
+
previous one - a temporary `<img class="kpt-galleria__ghost">` under it - drifts back slower and
|
|
53
|
+
dims (parallax). The kind comes from `enter` in `KptMotionProvider`: `spring` (default), `fade` (a short
|
|
54
|
+
cross-fade) or `none`; with the system "reduce motion" setting it only cross-fades. The first
|
|
55
|
+
render and a new `items` list at the same index do not animate.
|
|
56
|
+
|
|
57
|
+
The strip has no scrollbar. It scrolls by dragging (mouse and pen; a touch screen scrolls natively),
|
|
58
|
+
with the wheel or with two small strip arrows that appear only on the side there is more to see -
|
|
59
|
+
the strip fades out on that edge too (`data-scroll-start` / `data-scroll-end` on
|
|
60
|
+
`.kpt-galleria__strip`). A move under 5 px is still a click on a thumbnail; after a real drag the
|
|
61
|
+
click is swallowed and the strip glides on with inertia. The active thumbnail always returns into
|
|
62
|
+
view, and only as far as needed. The strip arrows are `tabindex="-1"`: from the keyboard the gallery
|
|
63
|
+
arrows drive the strip. All of it lives in `@konce-pt/motion` (`playGallerySlide`, `attachDragScroll`,
|
|
64
|
+
`revealThumb`, `trackStripEdges`), shared by both ports.
|
|
65
|
+
|
|
66
|
+
## Size
|
|
67
|
+
The stage has a fixed aspect ratio and the image fills it, so the gallery takes the full width of
|
|
68
|
+
its container and does not jump in height between images. An image with different proportions is
|
|
69
|
+
cropped (`object-fit: cover`). Two custom properties change it, set on the gallery or any ancestor:
|
|
70
|
+
- `--kpt-galleria-aspect-ratio` (default `16 / 9`), e.g. `4 / 3` or `1`
|
|
71
|
+
- `--kpt-galleria-fit` (default `cover`); `contain` shows the whole image with the stage colour around it
|
|
72
|
+
|
|
46
73
|
## Tokens
|
|
47
74
|
The stage `--kpt-color-muted`; the active thumbnail `--kpt-color-primary`; the arrows `--kpt-color-surface` (85% via `color-mix`, solid on hover) / `--kpt-color-on-surface` with `--kpt-elevation-2`.
|
|
48
75
|
|
package/dist/image/image.d.ts
CHANGED
|
@@ -16,6 +16,10 @@ export interface KptImageProps extends Omit<ComponentPropsWithRef<'div'>, 'onCli
|
|
|
16
16
|
* stronę i górną warstwę. Na czas podglądu blokujemy przewijanie dokumentu, a po zamknięciu
|
|
17
17
|
* fokus wraca na miniaturę.
|
|
18
18
|
*
|
|
19
|
+
* Ruch: zdjęcie wylatuje z miniatury i ląduje na środku na sprężynie, a przy zamknięciu (`x`,
|
|
20
|
+
* `Escape`, tło) wraca do niej, zanim okno się zamknie. Rodzaj ruchu bierze z `enter`
|
|
21
|
+
* w `KptMotionProvider` (`spring` / `fade` / `none`); przy "ogranicz ruch" podgląd tylko się przenika.
|
|
22
|
+
*
|
|
19
23
|
* @example
|
|
20
24
|
* <KptImage src="/foto.jpg" alt="Zdjęcie" preview width="240px" />
|
|
21
25
|
*/
|
package/dist/image/image.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { playZoomClose, playZoomOpen, resolveEnterMotion, stopZoomMotion } from '@konce-pt/motion';
|
|
5
|
+
import { useKptMotion } from "../motion/motion-context.js";
|
|
4
6
|
import { cn } from "../internal/cn.js";
|
|
5
7
|
import { kptLockScroll } from "../internal/scroll-lock.js";
|
|
6
8
|
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
@@ -13,6 +15,10 @@ import { KptIcon } from "../icon/icon.js";
|
|
|
13
15
|
* stronę i górną warstwę. Na czas podglądu blokujemy przewijanie dokumentu, a po zamknięciu
|
|
14
16
|
* fokus wraca na miniaturę.
|
|
15
17
|
*
|
|
18
|
+
* Ruch: zdjęcie wylatuje z miniatury i ląduje na środku na sprężynie, a przy zamknięciu (`x`,
|
|
19
|
+
* `Escape`, tło) wraca do niej, zanim okno się zamknie. Rodzaj ruchu bierze z `enter`
|
|
20
|
+
* w `KptMotionProvider` (`spring` / `fade` / `none`); przy "ogranicz ruch" podgląd tylko się przenika.
|
|
21
|
+
*
|
|
16
22
|
* @example
|
|
17
23
|
* <KptImage src="/foto.jpg" alt="Zdjęcie" preview width="240px" />
|
|
18
24
|
*/
|
|
@@ -21,32 +27,77 @@ export function KptImage({ src, alt = '', width = null, preview = false, locale,
|
|
|
21
27
|
const [open, setOpen] = useState(false);
|
|
22
28
|
const dialogRef = useRef(null);
|
|
23
29
|
const triggerRef = useRef(null);
|
|
30
|
+
const fullRef = useRef(null);
|
|
31
|
+
const closeRef = useRef(null);
|
|
32
|
+
const closeTimer = useRef(null);
|
|
33
|
+
const motion = resolveEnterMotion(undefined, useKptMotion());
|
|
34
|
+
const zoomElements = () => {
|
|
35
|
+
const thumb = triggerRef.current, image = fullRef.current, dialog = dialogRef.current;
|
|
36
|
+
return thumb && image && dialog ? { thumb, image, dialog, close: closeRef.current } : null;
|
|
37
|
+
};
|
|
24
38
|
useEffect(() => {
|
|
25
39
|
const el = dialogRef.current;
|
|
26
40
|
if (!el || !open)
|
|
27
41
|
return;
|
|
28
|
-
if (!el.open)
|
|
42
|
+
if (!el.open) {
|
|
29
43
|
el.showModal();
|
|
44
|
+
// Tuż po showModal(), przed malowaniem - pierwsza klatka stoi już na miniaturze.
|
|
45
|
+
const els = zoomElements();
|
|
46
|
+
if (els)
|
|
47
|
+
playZoomOpen(els, motion);
|
|
48
|
+
}
|
|
30
49
|
const releaseScroll = kptLockScroll();
|
|
31
50
|
return () => {
|
|
32
51
|
releaseScroll();
|
|
33
52
|
if (el.open)
|
|
34
53
|
el.close();
|
|
35
54
|
};
|
|
55
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- ruch czyta stan z chwili otwarcia; zmiana konfiguracji nie otwiera okna ponownie
|
|
36
56
|
}, [open]);
|
|
57
|
+
// Zegar zamknięcia nie może przeżyć komponentu.
|
|
58
|
+
useEffect(() => () => {
|
|
59
|
+
if (closeTimer.current)
|
|
60
|
+
clearTimeout(closeTimer.current);
|
|
61
|
+
}, []);
|
|
37
62
|
const style = width ? { width } : undefined;
|
|
38
63
|
if (!preview) {
|
|
39
64
|
return (_jsx("div", { className: cn('kpt-image-host', className), ...rest, children: _jsx("div", { className: "kpt-image", style: style, children: _jsx("img", { className: "kpt-image__img", src: src, alt: alt }) }) }));
|
|
40
65
|
}
|
|
41
|
-
//
|
|
66
|
+
// Zamknięcie z `x`, `Escape` i tła: najpierw powrót zdjęcia do miniatury, potem okno.
|
|
67
|
+
const requestClose = () => {
|
|
68
|
+
if (closeTimer.current)
|
|
69
|
+
return;
|
|
70
|
+
const els = zoomElements();
|
|
71
|
+
const ms = els ? playZoomClose(els, motion) : 0;
|
|
72
|
+
if (ms === 0) {
|
|
73
|
+
dialogRef.current?.close();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
closeTimer.current = setTimeout(() => {
|
|
77
|
+
closeTimer.current = null;
|
|
78
|
+
dialogRef.current?.close();
|
|
79
|
+
}, ms);
|
|
80
|
+
};
|
|
81
|
+
// `Escape` zamknąłby okno od razu - przejmujemy je, żeby zdjęcie zdążyło wrócić.
|
|
82
|
+
const onCancel = (event) => {
|
|
83
|
+
event.preventDefault();
|
|
84
|
+
requestClose();
|
|
85
|
+
};
|
|
86
|
+
// `close` przychodzi z każdej drogi (także wymuszonej przez przeglądarkę) - tu sprzątamy.
|
|
42
87
|
const onClosed = () => {
|
|
88
|
+
if (closeTimer.current)
|
|
89
|
+
clearTimeout(closeTimer.current);
|
|
90
|
+
closeTimer.current = null;
|
|
91
|
+
const els = zoomElements();
|
|
92
|
+
if (els)
|
|
93
|
+
stopZoomMotion(els);
|
|
43
94
|
setOpen(false);
|
|
44
95
|
triggerRef.current?.focus();
|
|
45
96
|
};
|
|
46
97
|
// Okno wypełnia cały ekran, więc kliknięcie w nie samo (a nie w zdjęcie) to kliknięcie w tło.
|
|
47
98
|
const onDialogClick = (event) => {
|
|
48
99
|
if (event.target === event.currentTarget)
|
|
49
|
-
|
|
100
|
+
requestClose();
|
|
50
101
|
};
|
|
51
|
-
return (_jsxs("div", { className: cn('kpt-image-host', className), ...rest, children: [_jsxs("button", { ref: triggerRef, type: "button", className: "kpt-image kpt-image--preview", "aria-haspopup": "dialog", "aria-label": alt ? t('image.previewOf', { alt }) : t('image.preview'), style: style, onClick: () => setOpen(true), children: [_jsx("img", { className: "kpt-image__img", src: src, alt: "" }), _jsx("span", { className: "kpt-image__mask", "aria-hidden": "true", children: _jsx(KptIcon, { name: "zoom-in" }) })] }), _jsxs("dialog", { ref: dialogRef, className: "kpt-image__overlay", "aria-label": alt || t('image.preview'), onClose: onClosed, onClick: onDialogClick, children: [_jsx("button", { type: "button", className: "kpt-image__close", onClick:
|
|
102
|
+
return (_jsxs("div", { className: cn('kpt-image-host', className), ...rest, children: [_jsxs("button", { ref: triggerRef, type: "button", className: "kpt-image kpt-image--preview", "aria-haspopup": "dialog", "aria-label": alt ? t('image.previewOf', { alt }) : t('image.preview'), style: style, onClick: () => setOpen(true), children: [_jsx("img", { className: "kpt-image__img", src: src, alt: "" }), _jsx("span", { className: "kpt-image__mask", "aria-hidden": "true", children: _jsx(KptIcon, { name: "zoom-in" }) })] }), _jsxs("dialog", { ref: dialogRef, className: "kpt-image__overlay", "aria-label": alt || t('image.preview'), onClose: onClosed, onCancel: onCancel, onClick: onDialogClick, children: [_jsx("button", { ref: closeRef, type: "button", className: "kpt-image__close", onClick: requestClose, "aria-label": t('common.close'), children: _jsx(KptIcon, { name: "x" }) }), _jsx("img", { ref: fullRef, className: "kpt-image__full", src: src, alt: alt })] })] }));
|
|
52
103
|
}
|
package/dist/image/llms.txt
CHANGED
|
@@ -40,6 +40,21 @@ only add noise for a screen reader.
|
|
|
40
40
|
## i18n
|
|
41
41
|
The thumbnail is named from `image.previewOf` (`{alt}`) or `image.preview`; the close button from `common.close`.
|
|
42
42
|
|
|
43
|
+
## Motion
|
|
44
|
+
The preview flies out of the thumbnail and lands in the middle on a soft spring; the corners keep the
|
|
45
|
+
thumbnail radius in flight and the thumbnail is empty while the photo is out. Closing - the x button,
|
|
46
|
+
`Escape` or a click on the backdrop - sends it back into the thumbnail before the dialog closes
|
|
47
|
+
(`Escape` is taken over through the dialog's `cancel` event). The kind comes from `enter` in the
|
|
48
|
+
motion config: `spring` (default), `fade` (cross-fade) or `none`. With the system "reduce motion"
|
|
49
|
+
setting the preview only cross-fades. The flight lives in `@konce-pt/motion` (`playZoomOpen`,
|
|
50
|
+
`playZoomClose`), shared by both ports.
|
|
51
|
+
|
|
52
|
+
## Preview size
|
|
53
|
+
The full-size photo keeps its proportions and is limited to 80% of the window height and 90% of its
|
|
54
|
+
width, so a portrait photo fits whole with a margin above and below, and the x button does not cover
|
|
55
|
+
it. Change the limits with `--kpt-image-preview-max-height` (default `80vh`) and
|
|
56
|
+
`--kpt-image-preview-max-width` (default `90vw`).
|
|
57
|
+
|
|
43
58
|
## Tokens
|
|
44
59
|
The radius `--kpt-radius-md`; the backdrop and the hover mask `--kpt-color-overlay-scrim`; the x button and the magnifier disc `--kpt-color-surface` / `--kpt-color-on-surface` with `--kpt-elevation-2`.
|
|
45
60
|
|
package/dist/input/input.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { type ComponentPropsWithRef } from 'react';
|
|
2
2
|
import type { KptSpeechMode } from '@konce-pt/speech';
|
|
3
|
+
import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
|
|
3
4
|
import { type KptSpeechColor } from '../speech/speech-button.tsx';
|
|
4
5
|
import type { KptHostProps } from '../internal/types.ts';
|
|
5
6
|
export type KptInputType = 'text' | 'email' | 'password' | 'number' | 'search' | 'tel' | 'url' | 'time';
|
|
6
|
-
export interface KptInputProps extends Omit<ComponentPropsWithRef<'input'>, 'type' | 'required'
|
|
7
|
+
export interface KptInputProps extends Omit<ComponentPropsWithRef<'input'>, 'type' | 'required'>, KptI18nOverrides {
|
|
7
8
|
/** Typ pola. */
|
|
8
9
|
type?: KptInputType;
|
|
9
10
|
/**
|
|
@@ -36,6 +37,16 @@ export interface KptInputProps extends Omit<ComponentPropsWithRef<'input'>, 'typ
|
|
|
36
37
|
* mikrofonu, a dyktowanie kończy się po jednej wypowiedzi (wzorzec wyszukiwarki).
|
|
37
38
|
*/
|
|
38
39
|
speechMode?: KptSpeechMode;
|
|
40
|
+
/**
|
|
41
|
+
* Przycisk czyszczenia na końcu pola. Bez ustawienia - włączony tylko dla `type="search"`,
|
|
42
|
+
* gdzie zastępuje systemowe "x" (ukryte zawsze, także przy `clearable={false}`).
|
|
43
|
+
*/
|
|
44
|
+
clearable?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Ikona z rejestru na początku pola (`search`, `mail`...). Dekoracyjna - nazwę pola daje etykieta
|
|
47
|
+
* albo `aria-label`, więc ikona nie trafia do drzewa dostępności.
|
|
48
|
+
*/
|
|
49
|
+
icon?: string;
|
|
39
50
|
}
|
|
40
51
|
/**
|
|
41
52
|
* Pole tekstowe Koncept UI. Renderuje natywny `<input>` w hoście `.kpt-input-host`.
|
|
@@ -51,4 +62,4 @@ export interface KptInputProps extends Omit<ComponentPropsWithRef<'input'>, 'typ
|
|
|
51
62
|
* <KptInput name="email" type="email" defaultValue="" />
|
|
52
63
|
* <KptInput value={email} onValueChange={setEmail} invalid={!ok} touched={blurred} />
|
|
53
64
|
*/
|
|
54
|
-
export declare function KptInput({ type, required, invalid, touched, onValueChange, onTouch, hostProps, className, id, onChange, onBlur, ref, speech, speechLang, speechColor, speechMode, 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedBy, ...rest }: Readonly<KptInputProps>): import("react").JSX.Element;
|
|
65
|
+
export declare function KptInput({ type, required, invalid, touched, onValueChange, onTouch, hostProps, className, id, onChange, onBlur, ref, speech, speechLang, speechColor, speechMode, clearable, icon, onKeyDown, locale, dictionary, 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedBy, ...rest }: Readonly<KptInputProps>): import("react").JSX.Element;
|
package/dist/input/input.js
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useCallback, useRef } from 'react';
|
|
3
|
+
import { useCallback, useEffect, useRef, useState, } from 'react';
|
|
4
|
+
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
5
|
+
import { KptIcon } from "../icon/icon.js";
|
|
4
6
|
import { cn } from "../internal/cn.js";
|
|
5
7
|
import { composeRefs } from "../internal/compose-refs.js";
|
|
6
8
|
import { KptSpeechButton } from "../speech/speech-button.js";
|
|
7
9
|
import { useKptSpeechRecognition } from "../speech/use-speech-recognition.js";
|
|
8
10
|
import { joinIds, useKptFieldA11y } from "../form-field/form-field-context.js";
|
|
11
|
+
/**
|
|
12
|
+
* Wpisuje wartość setterem z prototypu i wysyła `input`. Samo `el.value = ''` React by połknął:
|
|
13
|
+
* jego tracker wartości uznałby zdarzenie za echo i nie wywołałby `onChange`.
|
|
14
|
+
*/
|
|
15
|
+
function setFieldValue(field, value) {
|
|
16
|
+
Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set?.call(field, value);
|
|
17
|
+
field.dispatchEvent(new Event('input', { bubbles: true }));
|
|
18
|
+
}
|
|
9
19
|
/**
|
|
10
20
|
* Pole tekstowe Koncept UI. Renderuje natywny `<input>` w hoście `.kpt-input-host`.
|
|
11
21
|
*
|
|
@@ -20,7 +30,7 @@ import { joinIds, useKptFieldA11y } from "../form-field/form-field-context.js";
|
|
|
20
30
|
* <KptInput name="email" type="email" defaultValue="" />
|
|
21
31
|
* <KptInput value={email} onValueChange={setEmail} invalid={!ok} touched={blurred} />
|
|
22
32
|
*/
|
|
23
|
-
export function KptInput({ type = 'text', required = false, invalid = false, touched, onValueChange, onTouch, hostProps, className, id, onChange, onBlur, ref, speech = false, speechLang, speechColor, speechMode = 'insert', 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedBy, ...rest }) {
|
|
33
|
+
export function KptInput({ type = 'text', required = false, invalid = false, touched, onValueChange, onTouch, hostProps, className, id, onChange, onBlur, ref, speech = false, speechLang, speechColor, speechMode = 'insert', clearable, icon, onKeyDown, locale, dictionary, 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedBy, ...rest }) {
|
|
24
34
|
const { className: hostClassName, ...hostRest } = hostProps ?? {};
|
|
25
35
|
const field = useKptFieldA11y(id, 'kpt-input');
|
|
26
36
|
const innerRef = useRef(null);
|
|
@@ -38,13 +48,49 @@ export function KptInput({ type = 'text', required = false, invalid = false, tou
|
|
|
38
48
|
// Stan nieprawidłowy pokazujemy dopiero po dotknięciu pola - jak natywne `:user-invalid`.
|
|
39
49
|
// Bez `touched` nie ma czego bramkować, więc decyduje samo `invalid`.
|
|
40
50
|
const showInvalid = invalid && (touched ?? true);
|
|
51
|
+
const { t } = useKptMessages({ locale, dictionary });
|
|
52
|
+
// W trybie niekontrolowanym wartość należy do DOM i React jej nie widzi - to, czy jest co
|
|
53
|
+
// czyścić, trzymamy osobno. W kontrolowanym decyduje `value`.
|
|
54
|
+
const [filledUncontrolled, setFilledUncontrolled] = useState(() => String(rest.defaultValue ?? '') !== '');
|
|
55
|
+
const filled = rest.value === undefined ? filledUncontrolled : String(rest.value) !== '';
|
|
56
|
+
const showClear = (clearable ?? type === 'search') && filled && !rest.disabled && !rest.readOnly;
|
|
57
|
+
// Reset formularza (`form.reset()`, a w React 19 także po `<form action>`) przywraca wartość bez
|
|
58
|
+
// zdarzenia `input` - stan "jest co czyścić" odczytujemy wtedy z pola, gdy reset już się wykona.
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
const form = innerRef.current?.form;
|
|
61
|
+
if (!form)
|
|
62
|
+
return;
|
|
63
|
+
const onReset = () => {
|
|
64
|
+
queueMicrotask(() => setFilledUncontrolled((innerRef.current?.value ?? '') !== ''));
|
|
65
|
+
};
|
|
66
|
+
form.addEventListener('reset', onReset);
|
|
67
|
+
return () => form.removeEventListener('reset', onReset);
|
|
68
|
+
}, []);
|
|
41
69
|
const handleChange = useCallback((event) => {
|
|
70
|
+
setFilledUncontrolled(event.target.value !== '');
|
|
42
71
|
onChange?.(event);
|
|
43
72
|
onValueChange?.(event.target.value);
|
|
44
73
|
}, [onChange, onValueChange]);
|
|
74
|
+
/** Czyści zdarzeniem `input` - przechodzi przez `onChange` jak wpisane, w obu trybach. */
|
|
75
|
+
const clear = () => {
|
|
76
|
+
const field = innerRef.current;
|
|
77
|
+
if (!field)
|
|
78
|
+
return;
|
|
79
|
+
setFieldValue(field, '');
|
|
80
|
+
field.focus();
|
|
81
|
+
};
|
|
82
|
+
/** Escape czyści pełne pole i nie idzie dalej - pierwszy nie zamyka okna, w którym pole stoi. */
|
|
83
|
+
const handleKeyDown = (event) => {
|
|
84
|
+
onKeyDown?.(event);
|
|
85
|
+
if (event.defaultPrevented || event.key !== 'Escape' || !showClear)
|
|
86
|
+
return;
|
|
87
|
+
event.preventDefault();
|
|
88
|
+
event.stopPropagation();
|
|
89
|
+
clear();
|
|
90
|
+
};
|
|
45
91
|
const handleBlur = useCallback((event) => {
|
|
46
92
|
onBlur?.(event);
|
|
47
93
|
onTouch?.();
|
|
48
94
|
}, [onBlur, onTouch]);
|
|
49
|
-
return (_jsxs("span", { className: cn('kpt-input-host', hostClassName), "data-speech": speechState, ...hostRest, children: [_jsx("input", { className: cn('kpt-input__field', className), id: field.id, "aria-describedby": joinIds(ariaDescribedBy, field.describedBy), type: type, "aria-required": required || undefined, "aria-invalid": ariaInvalid ?? (showInvalid || undefined), onChange: handleChange, onBlur: handleBlur, ref: composeRefs(innerRef, ref), ...rest }), speech ? (_jsx(KptSpeechButton, { listeningColor: speechColor, speech: mic, size: "sm", unsupported: "hide", disabled: !!rest.disabled || !!rest.readOnly, hostProps: { className: 'kpt-input__speech' } })) : null] }));
|
|
95
|
+
return (_jsxs("span", { className: cn('kpt-input-host', hostClassName), "data-speech": speechState, "data-clear": showClear ? '' : undefined, "data-icon": icon ? '' : undefined, ...hostRest, children: [icon ? _jsx(KptIcon, { className: "kpt-input__icon", name: icon }) : null, _jsx("input", { className: cn('kpt-input__field', className), id: field.id, "aria-describedby": joinIds(ariaDescribedBy, field.describedBy), type: type, "aria-required": required || undefined, "aria-invalid": ariaInvalid ?? (showInvalid || undefined), onChange: handleChange, onBlur: handleBlur, onKeyDown: handleKeyDown, ref: composeRefs(innerRef, ref), ...rest }), showClear ? (_jsx("button", { type: "button", className: "kpt-input__clear", tabIndex: -1, "aria-label": t('input.clear'), onMouseDown: (event) => event.preventDefault(), onClick: clear, children: _jsx(KptIcon, { name: "x" }) })) : null, speech ? (_jsx(KptSpeechButton, { listeningColor: speechColor, speech: mic, size: "sm", unsupported: "hide", disabled: !!rest.disabled || !!rest.readOnly, hostProps: { className: 'kpt-input__speech' } })) : null] }));
|
|
50
96
|
}
|
package/dist/input/llms.txt
CHANGED
|
@@ -15,6 +15,10 @@ Import: `import { KptInput } from '@konce-pt/react';`
|
|
|
15
15
|
- `onTouch()` - fires on blur, the moment the field becomes touched
|
|
16
16
|
- `required`: boolean - see the note below
|
|
17
17
|
- `invalid`: boolean, `touched`: boolean - the error state
|
|
18
|
+
- `clearable`: boolean (default: `true` only for `type="search"`) - a clear button, see "Clear button"
|
|
19
|
+
- `icon`: string - a registry icon at the start of the field (`search`, `mail`...). Decorative: the field's
|
|
20
|
+
name comes from the label or the aria label, so the icon stays out of the accessibility tree; a click on it
|
|
21
|
+
lands in the field
|
|
18
22
|
- `hostProps` - props for the `.kpt-input-host` wrapper
|
|
19
23
|
- `className`, `ref`, `name`, `placeholder`, `autoComplete` and every other `<input>` prop go to
|
|
20
24
|
the inner `<input>`
|
|
@@ -51,6 +55,22 @@ configuration. An explicit `id` prop still wins.
|
|
|
51
55
|
generated. Inside `KptFormField` the wrapper's `label[for]` points at it and the hint or error is
|
|
52
56
|
added to `aria-describedby` (merged with your own). Without a wrapper, name it with `aria-label`.
|
|
53
57
|
|
|
58
|
+
## Clear button
|
|
59
|
+
`type="search"` gets a clear button at the end of the field as soon as it has a value; `clearable`
|
|
60
|
+
turns it on for any other type, `clearable={false}` turns it off. The browser's own "x"
|
|
61
|
+
(`::-webkit-search-cancel-button`) is always hidden - Chromium and Safari draw it differently and
|
|
62
|
+
Firefox not at all. The button is hidden while the field is empty, disabled or read-only.
|
|
63
|
+
|
|
64
|
+
Clearing dispatches a real `input` event, so the value goes through the same path as typed text,
|
|
65
|
+
and the focus stays in the field. The button is `tabindex="-1"` (like the `KptInputNumber`
|
|
66
|
+
steppers); from the keyboard `Escape` clears a filled field and stops the event, so the first
|
|
67
|
+
`Escape` does not close the dialog the field sits in. Its name comes from `input.clear` in the
|
|
68
|
+
dictionary. The host gets `data-clear` while the button is visible.
|
|
69
|
+
|
|
70
|
+
<KptInput type="search" value={query} onValueChange={setQuery} />
|
|
71
|
+
<KptInput clearable value={coupon} onValueChange={setCoupon} />
|
|
72
|
+
<KptInput type="search" icon="search" value={query} onValueChange={setQuery} />
|
|
73
|
+
|
|
54
74
|
## Tokens
|
|
55
75
|
`--kpt-form-field-{bg,text,placeholder,border,border-hover,border-focus,border-error,radius}`.
|
|
56
76
|
|
package/dist/popover/llms.txt
CHANGED
|
@@ -17,6 +17,9 @@ Import: `import { KptPopover } from '@konce-pt/react';`
|
|
|
17
17
|
- `placement`: 'bottom' (default) | 'top' - the preferred side; it flips when there is no room
|
|
18
18
|
- `maxWidth`: string - the panel's maximum width, any CSS length (`'24rem'`; default `20rem`). The panel is
|
|
19
19
|
never wider than the window minus the 8 px overlay margin
|
|
20
|
+
- `ariaLabel`: string - the accessible name of the panel (`role="dialog"`); give one whenever the panel has
|
|
21
|
+
no visible heading. It names the panel, not the host - `aria-label` in the rest props would land on the
|
|
22
|
+
host `<span>`
|
|
20
23
|
- `className`, `ref` and every other `<span>` prop go to the host
|
|
21
24
|
|
|
22
25
|
## The panel is a portal
|
package/dist/public-api.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export type { KptToolbarProps } from './toolbar/toolbar.tsx';
|
|
|
5
5
|
export { KptSidenav, KptSidenavFooter } from './sidenav/sidenav.tsx';
|
|
6
6
|
export type { KptSidenavProps } from './sidenav/sidenav.tsx';
|
|
7
7
|
export { KptTabs, KptTab } from './tabs/tabs.tsx';
|
|
8
|
-
export type { KptTabProps, KptTabsProps } from './tabs/tabs.tsx';
|
|
8
|
+
export type { KptTabIconColor, KptTabProps, KptTabsProps } from './tabs/tabs.tsx';
|
|
9
9
|
export { KptAccordion, KptAccordionPanel } from './accordion/accordion.tsx';
|
|
10
10
|
export type { KptAccordionPanelProps, KptAccordionProps } from './accordion/accordion.tsx';
|
|
11
11
|
export { KptPanel, KptPanelActions } from './panel/panel.tsx';
|
|
@@ -74,7 +74,7 @@ export { KptDataTable } from './data-table/data-table.tsx';
|
|
|
74
74
|
export type { KptDataTableProps } from './data-table/data-table.tsx';
|
|
75
75
|
export { exportTableCsv } from './data-table/export-csv.ts';
|
|
76
76
|
export type { KptCellEditContext, KptCellEditEvent, KptCellEditRenderers, KptCellRenderers, KptColumn, KptColumnEdit, KptColumnEditType, KptSortDir, KptTableLazyState, } from './data-table/data-table-types.ts';
|
|
77
|
-
export type { KptColumnFilter, KptColumnFilterType, KptColumnSummary, KptSummaryFn } from '@konce-pt/table';
|
|
77
|
+
export type { KptColumnFilter, KptColumnFilterType, KptColumnGroup, KptColumnSummary, KptSummaryFn } from '@konce-pt/table';
|
|
78
78
|
export { KptDataView } from './data-view/data-view.tsx';
|
|
79
79
|
export type { KptDataViewLayout, KptDataViewProps } from './data-view/data-view.tsx';
|
|
80
80
|
export { KptTree } from './tree/tree.tsx';
|
|
@@ -123,7 +123,7 @@ export type { KptOverlayProps } from './internal/overlay.tsx';
|
|
|
123
123
|
export { KptButtonGroup } from './button-group/button-group.tsx';
|
|
124
124
|
export type { KptButtonGroupProps } from './button-group/button-group.tsx';
|
|
125
125
|
export { KptSelect } from './select/select.tsx';
|
|
126
|
-
export type { KptSelectProps } from './select/select.tsx';
|
|
126
|
+
export type { KptSelectMultipleProps, KptSelectProps, KptSelectSingleProps } from './select/select.tsx';
|
|
127
127
|
export type { KptAlertVariant, KptNavigateEvent, KptOption } from './shared/types.ts';
|
|
128
128
|
export { KptFormField } from './form-field/form-field.tsx';
|
|
129
129
|
export type { KptFormFieldLabelType, KptFormFieldProps } from './form-field/form-field.tsx';
|
|
@@ -194,6 +194,8 @@ export type { KptCardHeadingLevel, KptCardProps, KptCardVariant } from './card/c
|
|
|
194
194
|
export { KptI18nProvider, useKptI18n, useKptMessages } from './i18n/i18n-context.tsx';
|
|
195
195
|
export type { KptI18nContextValue, KptI18nOverrides, KptI18nProviderProps } from './i18n/i18n-context.tsx';
|
|
196
196
|
export { KPT_MESSAGES_EN, KPT_MESSAGES_PL } from '@konce-pt/i18n';
|
|
197
|
+
export { KptMotionProvider, useKptMotion } from './motion/motion-context.tsx';
|
|
198
|
+
export type { KptMotionProviderProps, KptMotionConfig, KptLayoutMotion } from './motion/motion-context.tsx';
|
|
197
199
|
export type { DeepPartial, KptLocale, KptMessages } from '@konce-pt/i18n';
|
|
198
200
|
export { cn } from './internal/cn.ts';
|
|
199
201
|
export type { ClassValue } from './internal/cn.ts';
|
package/dist/public-api.js
CHANGED
|
@@ -109,6 +109,7 @@ export { KptCard, KptCardActions, KptCardFooter, KptCardHeader, KptCardLead, Kpt
|
|
|
109
109
|
// i18n
|
|
110
110
|
export { KptI18nProvider, useKptI18n, useKptMessages } from "./i18n/i18n-context.js";
|
|
111
111
|
export { KPT_MESSAGES_EN, KPT_MESSAGES_PL } from '@konce-pt/i18n';
|
|
112
|
+
export { KptMotionProvider, useKptMotion } from "./motion/motion-context.js";
|
|
112
113
|
// Prymitywy pomocnicze
|
|
113
114
|
export { cn } from "./internal/cn.js";
|
|
114
115
|
export { composeRefs } from "./internal/compose-refs.js";
|
package/dist/select/llms.txt
CHANGED
|
@@ -15,7 +15,11 @@ A select on its own panel. Variants: single, with a search box (`filter`) and mu
|
|
|
15
15
|
|
|
16
16
|
## Props
|
|
17
17
|
- `options`: `KptOption<T>[]` - `{ label, value, disabled? }`
|
|
18
|
-
- `value` / `defaultValue` / `onValueChange` - a single `T`, or `T[]` in `multiple` mode
|
|
18
|
+
- `value` / `defaultValue` / `onValueChange` - a single `T`, or `T[]` in `multiple` mode. The handler type
|
|
19
|
+
follows `multiple` (overloads): without it `(value: T) => void` - a single select cannot clear its
|
|
20
|
+
choice, so it never reports an array or `null`; with `multiple` `(value: T[]) => void`. A flag computed at
|
|
21
|
+
run time (`multiple={flag}`) keeps the wide `T | T[] | null`. Types: `KptSelectSingleProps`,
|
|
22
|
+
`KptSelectMultipleProps`, `KptSelectProps` (the wide one)
|
|
19
23
|
- `open` / `defaultOpen` / `onOpenChange` - the panel state, so the list can be opened from outside
|
|
20
24
|
- `placeholder`, `multiple`, `filter`, `disabled`, `invalid`, `touched`
|
|
21
25
|
- `onTouch()` - fires when the panel closes or focus leaves the closed trigger
|
package/dist/select/select.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ComponentPropsWithRef } from 'react';
|
|
1
|
+
import { type ComponentPropsWithRef, type JSX } from 'react';
|
|
2
2
|
import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
|
|
3
3
|
import type { KptOption } from '../shared/types.ts';
|
|
4
4
|
export interface KptSelectProps<T> extends Omit<ComponentPropsWithRef<'div'>, 'children' | 'onChange' | 'defaultValue'>, KptI18nOverrides {
|
|
@@ -28,6 +28,23 @@ export interface KptSelectProps<T> extends Omit<ComponentPropsWithRef<'div'>, 'c
|
|
|
28
28
|
/** Wywoływane, gdy panel się zamyka albo trigger traci fokus. */
|
|
29
29
|
onTouch?: () => void;
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Pojedynczy wybór: `onValueChange` dostaje zawsze wybraną opcję - nigdy tablicę ani `null`, bo
|
|
33
|
+
* lista bez `multiple` nie ma jak wyczyścić wyboru. Rodzic trzyma więc zwykłe `T`.
|
|
34
|
+
*/
|
|
35
|
+
export interface KptSelectSingleProps<T> extends Omit<KptSelectProps<T>, 'multiple' | 'value' | 'defaultValue' | 'onValueChange'> {
|
|
36
|
+
multiple?: false;
|
|
37
|
+
value?: T | null;
|
|
38
|
+
defaultValue?: T | null;
|
|
39
|
+
onValueChange?: (value: T) => void;
|
|
40
|
+
}
|
|
41
|
+
/** Wybór wielokrotny: wartością jest zawsze tablica (pusta, gdy nic nie wybrano). */
|
|
42
|
+
export interface KptSelectMultipleProps<T> extends Omit<KptSelectProps<T>, 'multiple' | 'value' | 'defaultValue' | 'onValueChange'> {
|
|
43
|
+
multiple: true;
|
|
44
|
+
value?: T[] | null;
|
|
45
|
+
defaultValue?: T[] | null;
|
|
46
|
+
onValueChange?: (value: T[]) => void;
|
|
47
|
+
}
|
|
31
48
|
/**
|
|
32
49
|
* Lista wyboru (select) na własnym panelu. Warianty: pojedynczy, z wyszukiwarką (`filter`)
|
|
33
50
|
* i wielokrotny (`multiple`, chipy).
|
|
@@ -46,5 +63,10 @@ export interface KptSelectProps<T> extends Omit<ComponentPropsWithRef<'div'>, 'c
|
|
|
46
63
|
* @example
|
|
47
64
|
* <KptSelect options={roles} value={role} onValueChange={setRole} placeholder="Wybierz..." />
|
|
48
65
|
* <KptSelect options={tags} multiple filter value={tags} onValueChange={setTags} />
|
|
66
|
+
*
|
|
67
|
+
* Typ `onValueChange` idzie za `multiple`: bez niego `(value: T)`, z `multiple` - `(value: T[])`.
|
|
68
|
+
* `multiple` liczone w locie (`multiple={flag}`) zostawia szeroki typ `T | T[] | null`.
|
|
49
69
|
*/
|
|
50
|
-
export declare function KptSelect<T>(
|
|
70
|
+
export declare function KptSelect<T>(props: Readonly<KptSelectSingleProps<T>>): JSX.Element;
|
|
71
|
+
export declare function KptSelect<T>(props: Readonly<KptSelectMultipleProps<T>>): JSX.Element;
|
|
72
|
+
export declare function KptSelect<T>(props: Readonly<KptSelectProps<T>>): JSX.Element;
|
package/dist/select/select.js
CHANGED
|
@@ -18,25 +18,6 @@ function filterSelectOptions(options, text) {
|
|
|
18
18
|
const q = foldMentionText(text.trim());
|
|
19
19
|
return q ? options.filter((option) => foldMentionText(option.label).includes(q)) : options;
|
|
20
20
|
}
|
|
21
|
-
/**
|
|
22
|
-
* Lista wyboru (select) na własnym panelu. Warianty: pojedynczy, z wyszukiwarką (`filter`)
|
|
23
|
-
* i wielokrotny (`multiple`, chipy).
|
|
24
|
-
*
|
|
25
|
-
* Klawiatura według wzorca WAI-ARIA "select-only combobox": fokus zostaje na polu, a aktywna opcja
|
|
26
|
-
* idzie przez `aria-activedescendant`. `ArrowDown`/`ArrowUp`/`Enter`/`Spacja` otwierają listę na wybranej opcji,
|
|
27
|
-
* strzałki, `Home`/`End`, `PageUp`/`PageDown` chodzą po opcjach (z pominięciem wyłączonych),
|
|
28
|
-
* wpisywane litery skaczą do pasującej opcji (bez polskich znaków), `Enter` wybiera, `Escape`
|
|
29
|
-
* zamyka. Z `filter` fokus przechodzi do pola wyszukiwania, które obsługuje te same klawisze.
|
|
30
|
-
* W trybie `multiple` `Backspace` na zamkniętym polu usuwa ostatni wybór.
|
|
31
|
-
*
|
|
32
|
-
* Wewnątrz `KptFormField` zgłasza wypełnienie hookiem `useKptFormFieldFilled`, więc etykieta
|
|
33
|
-
* Float Label unosi się nad wybraną wartością - natywnego pola, z którego wrapper mógłby to
|
|
34
|
-
* odczytać, tu nie ma.
|
|
35
|
-
*
|
|
36
|
-
* @example
|
|
37
|
-
* <KptSelect options={roles} value={role} onValueChange={setRole} placeholder="Wybierz..." />
|
|
38
|
-
* <KptSelect options={tags} multiple filter value={tags} onValueChange={setTags} />
|
|
39
|
-
*/
|
|
40
21
|
export function KptSelect({ options = [], placeholder = '', multiple = false, filter = false, disabled = false, readOnly = false, invalid = false, touched, value, defaultValue = null, onValueChange, open, defaultOpen = false, onOpenChange, onTouch, locale, dictionary, className, onBlur, id, required = false, 'aria-label': ariaLabel, 'aria-describedby': ariaDescribedBy, ...rest }) {
|
|
41
22
|
/** `div` z rolą nie dostaje nazwy z `label[for]` - stąd `aria-labelledby` na etykietę wrappera. */
|
|
42
23
|
const field = useKptFieldA11y(id, 'kpt-select-trigger');
|