@konce-pt/react 0.9.1 → 0.10.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/dist/accordion/accordion.js +7 -1
- package/dist/accordion/llms.txt +8 -4
- package/dist/alert/alert.d.ts +10 -4
- package/dist/alert/alert.js +13 -6
- package/dist/alert/llms.txt +8 -6
- package/dist/app-shell/app-shell.js +28 -2
- package/dist/app-shell/llms.txt +3 -0
- package/dist/autocomplete/autocomplete.d.ts +7 -2
- package/dist/autocomplete/autocomplete.js +46 -9
- package/dist/autocomplete/llms.txt +12 -0
- package/dist/avatar/avatar.d.ts +4 -2
- package/dist/avatar/avatar.js +8 -3
- package/dist/avatar/llms.txt +9 -5
- package/dist/avatar-group/avatar-group.d.ts +5 -2
- package/dist/avatar-group/avatar-group.js +10 -2
- package/dist/avatar-group/llms.txt +7 -4
- package/dist/badge/badge.d.ts +8 -1
- package/dist/badge/badge.js +3 -2
- package/dist/badge/llms.txt +11 -5
- package/dist/bottom-sheet/bottom-sheet.d.ts +1 -1
- package/dist/bottom-sheet/bottom-sheet.js +8 -2
- package/dist/bottom-sheet/llms.txt +8 -2
- package/dist/breadcrumb/breadcrumb.js +4 -4
- package/dist/breadcrumb/llms.txt +12 -5
- package/dist/card/card.d.ts +9 -1
- package/dist/card/card.js +2 -2
- package/dist/card/llms.txt +6 -2
- package/dist/carousel/carousel.d.ts +8 -1
- package/dist/carousel/carousel.js +9 -2
- package/dist/carousel/llms.txt +24 -10
- package/dist/checkbox/checkbox.d.ts +6 -1
- package/dist/checkbox/checkbox.js +11 -3
- package/dist/checkbox/llms.txt +11 -0
- package/dist/chip/chip.js +10 -1
- package/dist/chip/llms.txt +7 -4
- package/dist/chips-input/chips-input.d.ts +7 -1
- package/dist/chips-input/chips-input.js +21 -5
- package/dist/chips-input/llms.txt +12 -0
- package/dist/color-picker/color-picker.d.ts +18 -5
- package/dist/color-picker/color-picker.js +48 -14
- package/dist/color-picker/llms.txt +14 -0
- package/dist/confirm/confirm-container.d.ts +5 -1
- package/dist/confirm/confirm-container.js +15 -1
- package/dist/confirm/llms.txt +4 -2
- package/dist/context-menu/context-menu.d.ts +5 -0
- package/dist/context-menu/context-menu.js +48 -4
- package/dist/context-menu/llms.txt +11 -4
- package/dist/date-range/date-range.d.ts +14 -1
- package/dist/date-range/date-range.js +133 -17
- package/dist/date-range/llms.txt +26 -3
- package/dist/datepicker/datepicker.d.ts +10 -1
- package/dist/datepicker/datepicker.js +147 -14
- package/dist/datepicker/llms.txt +27 -3
- package/dist/dialog/dialog.d.ts +4 -0
- package/dist/dialog/dialog.js +7 -1
- package/dist/dialog/llms.txt +2 -0
- package/dist/drawer/drawer.js +7 -2
- package/dist/drawer/llms.txt +10 -5
- package/dist/empty/empty.d.ts +6 -1
- package/dist/empty/empty.js +2 -2
- package/dist/empty/llms.txt +4 -3
- package/dist/fab/llms.txt +2 -1
- package/dist/file-upload/file-upload.d.ts +5 -0
- package/dist/file-upload/file-upload.js +24 -3
- package/dist/file-upload/llms.txt +15 -4
- package/dist/form-field/form-field-context.d.ts +30 -1
- package/dist/form-field/form-field-context.js +27 -0
- package/dist/form-field/form-field.js +16 -2
- package/dist/form-field/llms.txt +10 -1
- package/dist/galleria/galleria.d.ts +6 -1
- package/dist/galleria/galleria.js +40 -1
- package/dist/galleria/llms.txt +17 -8
- package/dist/icon/icon.d.ts +7 -1
- package/dist/icon/icon.js +18 -4
- package/dist/icon/llms.txt +5 -2
- package/dist/image/image.d.ts +5 -0
- package/dist/image/image.js +38 -2
- package/dist/image/llms.txt +24 -17
- package/dist/input/input.d.ts +1 -1
- package/dist/input/input.js +4 -6
- package/dist/input/llms.txt +5 -0
- package/dist/input-mask/input-mask.d.ts +1 -1
- package/dist/input-mask/input-mask.js +8 -6
- package/dist/input-mask/llms.txt +11 -0
- package/dist/input-number/input-number.d.ts +13 -1
- package/dist/input-number/input-number.js +49 -9
- package/dist/input-number/llms.txt +21 -3
- package/dist/input-otp/input-otp.d.ts +7 -2
- package/dist/input-otp/input-otp.js +36 -7
- package/dist/input-otp/llms.txt +14 -0
- package/dist/internal/calendar-focus.d.ts +8 -0
- package/dist/internal/calendar-focus.js +38 -0
- package/dist/internal/option-nav.d.ts +24 -0
- package/dist/internal/option-nav.js +80 -0
- package/dist/internal/roving.d.ts +9 -0
- package/dist/internal/roving.js +43 -0
- package/dist/internal/use-modal-focus.d.ts +10 -0
- package/dist/internal/use-modal-focus.js +57 -0
- package/dist/knob/knob.d.ts +5 -1
- package/dist/knob/knob.js +4 -2
- package/dist/knob/llms.txt +8 -0
- package/dist/listbox/listbox.d.ts +13 -1
- package/dist/listbox/listbox.js +89 -6
- package/dist/listbox/llms.txt +21 -5
- package/dist/megamenu/llms.txt +28 -10
- package/dist/megamenu/megamenu.d.ts +10 -1
- package/dist/megamenu/megamenu.js +116 -5
- package/dist/menu/llms.txt +12 -1
- package/dist/menu/menu-keys.d.ts +15 -0
- package/dist/menu/menu-keys.js +53 -0
- package/dist/menu/menu-rows.js +1 -1
- package/dist/menu/menu.js +52 -4
- package/dist/menubar/llms.txt +24 -7
- package/dist/menubar/menubar.d.ts +8 -1
- package/dist/menubar/menubar.js +92 -6
- package/dist/meter-group/llms.txt +10 -3
- package/dist/meter-group/meter-group.d.ts +9 -2
- package/dist/meter-group/meter-group.js +8 -2
- package/dist/order-list/llms.txt +11 -3
- package/dist/order-list/order-list.d.ts +5 -1
- package/dist/order-list/order-list.js +46 -8
- package/dist/panel/llms.txt +9 -6
- package/dist/panel/panel.js +3 -1
- package/dist/password/llms.txt +8 -0
- package/dist/password/password.d.ts +6 -1
- package/dist/password/password.js +6 -5
- package/dist/pick-list/llms.txt +12 -3
- package/dist/pick-list/pick-list.d.ts +5 -1
- package/dist/pick-list/pick-list.js +52 -5
- package/dist/popover/llms.txt +9 -2
- package/dist/popover/popover.d.ts +9 -3
- package/dist/popover/popover.js +41 -6
- package/dist/public-api.d.ts +6 -6
- package/dist/public-api.js +1 -1
- package/dist/radio-group/llms.txt +11 -0
- package/dist/radio-group/radio-group.d.ts +9 -1
- package/dist/radio-group/radio-group.js +5 -2
- package/dist/rating/llms.txt +18 -5
- package/dist/rating/rating.d.ts +21 -3
- package/dist/rating/rating.js +87 -12
- package/dist/rich-text/llms.txt +20 -0
- package/dist/rich-text/rich-text.d.ts +7 -1
- package/dist/rich-text/rich-text.js +155 -28
- package/dist/scroll-top/llms.txt +5 -1
- package/dist/scroll-top/scroll-top.js +3 -1
- package/dist/select/llms.txt +21 -7
- package/dist/select/select.d.ts +12 -1
- package/dist/select/select.js +110 -38
- package/dist/sidenav/llms.txt +6 -0
- package/dist/skeleton/llms.txt +3 -2
- package/dist/skeleton/skeleton.d.ts +1 -1
- package/dist/skeleton/skeleton.js +2 -2
- package/dist/slider/llms.txt +14 -1
- package/dist/slider/slider.d.ts +11 -2
- package/dist/slider/slider.js +15 -2
- package/dist/speed-dial/llms.txt +6 -2
- package/dist/speed-dial/speed-dial.d.ts +1 -1
- package/dist/speed-dial/speed-dial.js +14 -3
- package/dist/spinner/llms.txt +12 -8
- package/dist/spinner/spinner.d.ts +5 -1
- package/dist/spinner/spinner.js +12 -2
- package/dist/split-button/llms.txt +11 -3
- package/dist/split-button/split-button.d.ts +3 -1
- package/dist/split-button/split-button.js +43 -3
- package/dist/splitter/llms.txt +14 -4
- package/dist/splitter/splitter.d.ts +9 -2
- package/dist/splitter/splitter.js +15 -2
- package/dist/stepper/llms.txt +8 -4
- package/dist/stepper/stepper.d.ts +3 -2
- package/dist/stepper/stepper.js +7 -2
- package/dist/switch/llms.txt +10 -0
- package/dist/switch/switch.d.ts +1 -1
- package/dist/switch/switch.js +4 -2
- package/dist/switch-group/llms.txt +11 -0
- package/dist/switch-group/switch-group.d.ts +5 -1
- package/dist/switch-group/switch-group.js +7 -2
- package/dist/tabs/llms.txt +10 -4
- package/dist/tabs/tabs.d.ts +4 -0
- package/dist/tabs/tabs.js +21 -2
- package/dist/textarea/llms.txt +10 -0
- package/dist/textarea/textarea.d.ts +3 -1
- package/dist/textarea/textarea.js +13 -8
- package/dist/toast/llms.txt +21 -7
- package/dist/toast/toast-container.d.ts +6 -1
- package/dist/toast/toast-container.js +52 -1
- package/dist/toast/toast-store.d.ts +12 -0
- package/dist/toast/toast-store.js +46 -7
- package/dist/tooltip/llms.txt +10 -3
- package/dist/tooltip/tooltip.d.ts +5 -0
- package/dist/tooltip/tooltip.js +80 -7
- package/dist/tree/llms.txt +20 -8
- package/dist/tree/tree-keys.d.ts +27 -0
- package/dist/tree/tree-keys.js +39 -0
- package/dist/tree/tree.d.ts +12 -5
- package/dist/tree/tree.js +75 -29
- package/package.json +12 -12
|
@@ -6,8 +6,14 @@
|
|
|
6
6
|
* zbudowany na liście) zgłaszają je przez `useKptFormFieldFilled`.
|
|
7
7
|
*/
|
|
8
8
|
export interface KptFormFieldContextValue {
|
|
9
|
-
/** Id, którym wrapper wiąże `label[for]` — kontrolka
|
|
9
|
+
/** Id, którym wrapper wiąże `label[for]` — kontrolka bez własnego `id` nadaje je swojemu elementowi. */
|
|
10
10
|
controlId: string;
|
|
11
|
+
/** Id elementu etykiety albo `undefined`, gdy wrapper jej nie pokazuje. */
|
|
12
|
+
labelId: string | undefined;
|
|
13
|
+
/** Id widocznego komunikatu (błąd albo podpowiedź) albo `undefined`. */
|
|
14
|
+
describedBy: string | undefined;
|
|
15
|
+
/** Kontrolka z własnym `id` zgłasza je wrapperowi — na nie wskaże `label[for]`. */
|
|
16
|
+
reportControlId: (id: string | null) => void;
|
|
11
17
|
/** Czy pole jest oznaczone jako nieprawidłowe (niepusty `error` na wrapperze). */
|
|
12
18
|
invalid: boolean;
|
|
13
19
|
/** Zgłasza wypełnienie kontrolki, gdy nie da się go odczytać z DOM. */
|
|
@@ -21,3 +27,26 @@ export declare function useKptFormFieldContext(): KptFormFieldContextValue | nul
|
|
|
21
27
|
* Steruje uniesieniem etykiety w wariantach float.
|
|
22
28
|
*/
|
|
23
29
|
export declare function useKptFormFieldFilled(filled: boolean): void;
|
|
30
|
+
/** To, czego kontrolka potrzebuje, żeby wrapper ją nazwał i opisał. */
|
|
31
|
+
export interface KptFieldA11y {
|
|
32
|
+
/** Id elementu fokusowalnego: własne `id`, potem `controlId` wrappera, potem wygenerowane. */
|
|
33
|
+
id: string;
|
|
34
|
+
/**
|
|
35
|
+
* Id etykiety wrappera — dla kontrolek na elemencie, którego `label[for]` nie nazywa
|
|
36
|
+
* (`div` z rolą, `contenteditable`). Natywne `input`/`textarea` nazywa już samo `htmlFor`.
|
|
37
|
+
*/
|
|
38
|
+
labelledBy: string | undefined;
|
|
39
|
+
/** Id podpowiedzi albo błędu wrappera — do `aria-describedby`. */
|
|
40
|
+
describedBy: string | undefined;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Wiąże kontrolkę z otaczającym `KptFormField`. Poza wrapperem zwraca samo `id` (własne albo
|
|
44
|
+
* wygenerowane) i puste powiązania. Bliźniak `kptFieldA11y` z portu Angulara.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* const field = useKptFieldA11y(id, 'kpt-textarea');
|
|
48
|
+
* <textarea id={field.id} aria-describedby={field.describedBy} />
|
|
49
|
+
*/
|
|
50
|
+
export declare function useKptFieldA11y(provided: string | undefined, prefix: string): KptFieldA11y;
|
|
51
|
+
/** Łączy kilka id w wartość `aria-describedby`/`aria-labelledby`; `undefined`, gdy nie ma żadnego. */
|
|
52
|
+
export declare function joinIds(...ids: (string | null | undefined | false)[]): string | undefined;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createContext, useContext, useEffect } from 'react';
|
|
2
|
+
import { useKptId } from "../internal/use-id.js";
|
|
2
3
|
export const KptFormFieldContext = createContext(null);
|
|
3
4
|
/** Kontekst pola formularza albo `null`, gdy kontrolka stoi poza `KptFormField`. */
|
|
4
5
|
export function useKptFormFieldContext() {
|
|
@@ -15,3 +16,29 @@ export function useKptFormFieldFilled(filled) {
|
|
|
15
16
|
reportFilled?.(filled);
|
|
16
17
|
}, [reportFilled, filled]);
|
|
17
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Wiąże kontrolkę z otaczającym `KptFormField`. Poza wrapperem zwraca samo `id` (własne albo
|
|
21
|
+
* wygenerowane) i puste powiązania. Bliźniak `kptFieldA11y` z portu Angulara.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* const field = useKptFieldA11y(id, 'kpt-textarea');
|
|
25
|
+
* <textarea id={field.id} aria-describedby={field.describedBy} />
|
|
26
|
+
*/
|
|
27
|
+
export function useKptFieldA11y(provided, prefix) {
|
|
28
|
+
const context = useKptFormFieldContext();
|
|
29
|
+
const id = useKptId(provided ?? context?.controlId, prefix);
|
|
30
|
+
const report = context?.reportControlId;
|
|
31
|
+
const own = provided ?? null;
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (!report || !own)
|
|
34
|
+
return;
|
|
35
|
+
report(own);
|
|
36
|
+
return () => report(null);
|
|
37
|
+
}, [report, own]);
|
|
38
|
+
return { id, labelledBy: context?.labelId, describedBy: context?.describedBy };
|
|
39
|
+
}
|
|
40
|
+
/** Łączy kilka id w wartość `aria-describedby`/`aria-labelledby`; `undefined`, gdy nie ma żadnego. */
|
|
41
|
+
export function joinIds(...ids) {
|
|
42
|
+
const list = ids.filter((id) => !!id);
|
|
43
|
+
return list.length ? list.join(' ') : undefined;
|
|
44
|
+
}
|
|
@@ -23,7 +23,14 @@ export function KptFormField({ label = '', hint = '', error = null, required = f
|
|
|
23
23
|
const [nativeFilled, setNativeFilled] = useState(false);
|
|
24
24
|
const [reportedFilled, setReportedFilled] = useState(null);
|
|
25
25
|
const generatedId = useKptId(undefined, 'kpt-form-field-control');
|
|
26
|
+
const baseId = useKptId(undefined, 'kpt-form-field');
|
|
26
27
|
const id = controlId || generatedId;
|
|
28
|
+
// Kontrolka z własnym `id` zgłasza je przez kontekst — wtedy `label[for]` wskazuje właśnie je.
|
|
29
|
+
const [reportedId, setReportedId] = useState(null);
|
|
30
|
+
const labelFor = controlId || reportedId || generatedId;
|
|
31
|
+
const labelId = label ? `${baseId}-label` : undefined;
|
|
32
|
+
const messageId = `${baseId}-message`;
|
|
33
|
+
const describedBy = error || hint ? messageId : undefined;
|
|
27
34
|
/** Odczytuje wartość projektowanego `input`/`textarea` i aktualizuje stan wypełnienia. */
|
|
28
35
|
const updateFilled = useCallback(() => {
|
|
29
36
|
const el = hostRef.current?.querySelector('.kpt-form-field__control input, .kpt-form-field__control textarea');
|
|
@@ -36,6 +43,13 @@ export function KptFormField({ label = '', hint = '', error = null, required = f
|
|
|
36
43
|
// powtarzamy po każdym renderze — to jeden `querySelector`, nie warto tu optymalizować.
|
|
37
44
|
useEffect(updateFilled);
|
|
38
45
|
const filled = reportedFilled ?? nativeFilled;
|
|
39
|
-
const context = useMemo(() => ({
|
|
40
|
-
|
|
46
|
+
const context = useMemo(() => ({
|
|
47
|
+
controlId: id,
|
|
48
|
+
labelId,
|
|
49
|
+
describedBy,
|
|
50
|
+
invalid: !!error,
|
|
51
|
+
reportFilled: setReportedFilled,
|
|
52
|
+
reportControlId: setReportedId,
|
|
53
|
+
}), [id, labelId, describedBy, error]);
|
|
54
|
+
return (_jsx(KptFormFieldContext.Provider, { value: context, children: _jsx("div", { className: cn('kpt-form-field-host', className), ref: composeRefs(hostRef, ref), onInput: updateFilled, onChange: updateFilled, ...rest, children: _jsxs("div", { className: cn('kpt-form-field', `kpt-form-field--${labelType}`, labelType !== 'fixed' && 'kpt-form-field--float', error && 'kpt-form-field--invalid'), children: [_jsxs("div", { className: cn('kpt-form-field__box', filled && 'is-filled'), children: [label ? (_jsxs("label", { className: "kpt-form-field__label", id: labelId, htmlFor: labelFor, children: [label, required ? (_jsx("span", { className: "kpt-form-field__required", "aria-hidden": "true", children: "*" })) : null] })) : null, _jsx("div", { className: "kpt-form-field__control", children: children })] }), error ? (_jsx("p", { className: "kpt-form-field__message kpt-form-field__message--error", role: "alert", id: messageId, children: error })) : hint ? (_jsx("p", { className: "kpt-form-field__message kpt-form-field__message--hint", id: messageId, children: hint })) : null] }) }) }));
|
|
41
55
|
}
|
package/dist/form-field/llms.txt
CHANGED
|
@@ -25,7 +25,16 @@ Import: `import { KptFormField } from '@konce-pt/react';`
|
|
|
25
25
|
|
|
26
26
|
## Label wiring
|
|
27
27
|
The wrapper generates a control id and publishes it through context, so `<KptInput>` inside needs
|
|
28
|
-
no configuration — `label[for]` and `input[id]` always match. Pass `controlId` to take over
|
|
28
|
+
no configuration — `label[for]` and `input[id]` always match. Pass `controlId` to take over; a control
|
|
29
|
+
with its own `id` reports it, and the label follows.
|
|
30
|
+
|
|
31
|
+
The context also carries the label's id and the id of the visible hint or error. Every library
|
|
32
|
+
control uses them through `useKptFieldA11y`: the hint or error lands in `aria-describedby`, and a
|
|
33
|
+
control on a `div` with a role (`KptSelect`, `KptDatepicker`, `KptRadioGroup`, `KptRichText`…) gets
|
|
34
|
+
`aria-labelledby` pointing at the label, because `label[for]` does not name it. A custom control:
|
|
35
|
+
|
|
36
|
+
const field = useKptFieldA11y(id, 'my-control');
|
|
37
|
+
<div role="slider" id={field.id} aria-labelledby={field.labelledBy} aria-describedby={field.describedBy} />
|
|
29
38
|
|
|
30
39
|
## Float labels
|
|
31
40
|
`labelType` other than `fixed` turns on the floating label with an animated transition: `over`
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ComponentPropsWithRef } from 'react';
|
|
2
2
|
import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
|
|
3
3
|
export interface KptGalleriaItem {
|
|
4
4
|
src: string;
|
|
@@ -16,6 +16,11 @@ export interface KptGalleriaProps extends Omit<ComponentPropsWithRef<'div'>, 'on
|
|
|
16
16
|
/**
|
|
17
17
|
* Galleria — duży podgląd obrazu z paskiem miniatur i nawigacją.
|
|
18
18
|
*
|
|
19
|
+
* Klawiatura: na całej galerii strzałki w lewo/prawo przechodzą do sąsiedniego obrazu (w RTL
|
|
20
|
+
* odwrotnie), `Home`/`End` — do pierwszego/ostatniego; gdy fokus stoi na miniaturze, idzie za
|
|
21
|
+
* aktywną. Miniatura ma nazwę z `alt` obrazu i `aria-current` na aktywnej, a zmiana obrazu jest
|
|
22
|
+
* ogłaszana uprzejmie („Obraz 2 z 4: …”) — dopiero po działaniu użytkownika, nie przy starcie.
|
|
23
|
+
*
|
|
19
24
|
* @example
|
|
20
25
|
* <KptGalleria items={photos} activeIndex={index} onActiveIndexChange={setIndex} />
|
|
21
26
|
*/
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useState } from 'react';
|
|
2
3
|
import { cn } from "../internal/cn.js";
|
|
3
4
|
import { useControllableState } from "../internal/use-controllable-state.js";
|
|
4
5
|
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
5
6
|
import { KptIcon } from "../icon/icon.js";
|
|
7
|
+
/** Klawisze przechodzące między obrazami: kierunek względem bieżącego albo skrajny indeks. */
|
|
8
|
+
const NAV_KEYS = new Set(['ArrowLeft', 'ArrowRight', 'Home', 'End']);
|
|
6
9
|
/**
|
|
7
10
|
* Galleria — duży podgląd obrazu z paskiem miniatur i nawigacją.
|
|
8
11
|
*
|
|
12
|
+
* Klawiatura: na całej galerii strzałki w lewo/prawo przechodzą do sąsiedniego obrazu (w RTL
|
|
13
|
+
* odwrotnie), `Home`/`End` — do pierwszego/ostatniego; gdy fokus stoi na miniaturze, idzie za
|
|
14
|
+
* aktywną. Miniatura ma nazwę z `alt` obrazu i `aria-current` na aktywnej, a zmiana obrazu jest
|
|
15
|
+
* ogłaszana uprzejmie („Obraz 2 z 4: …”) — dopiero po działaniu użytkownika, nie przy starcie.
|
|
16
|
+
*
|
|
9
17
|
* @example
|
|
10
18
|
* <KptGalleria items={photos} activeIndex={index} onActiveIndexChange={setIndex} />
|
|
11
19
|
*/
|
|
@@ -16,6 +24,37 @@ export function KptGalleria({ items = [], activeIndex, defaultActiveIndex = 0, o
|
|
|
16
24
|
defaultValue: defaultActiveIndex,
|
|
17
25
|
onChange: onActiveIndexChange,
|
|
18
26
|
});
|
|
27
|
+
// Ogłoszenie dopiero po działaniu użytkownika — przy starcie region ma milczeć.
|
|
28
|
+
const [userMoved, setUserMoved] = useState(false);
|
|
29
|
+
const thumbsRef = useRef([]);
|
|
30
|
+
const rootRef = useRef(null);
|
|
19
31
|
const current = items[active] ?? null;
|
|
20
|
-
|
|
32
|
+
const last = items.length - 1;
|
|
33
|
+
const select = (index) => {
|
|
34
|
+
if (last < 0)
|
|
35
|
+
return active;
|
|
36
|
+
const next = Math.min(Math.max(index, 0), last);
|
|
37
|
+
setUserMoved(true);
|
|
38
|
+
setActive(next);
|
|
39
|
+
return next;
|
|
40
|
+
};
|
|
41
|
+
const onKeyDown = (event) => {
|
|
42
|
+
if (!NAV_KEYS.has(event.key))
|
|
43
|
+
return;
|
|
44
|
+
event.preventDefault();
|
|
45
|
+
const rtl = rootRef.current ? getComputedStyle(rootRef.current).direction === 'rtl' : false;
|
|
46
|
+
const step = event.key === 'ArrowRight' ? 1 : -1;
|
|
47
|
+
const target = event.key === 'Home' ? 0 : event.key === 'End' ? last : active + (rtl ? -step : step);
|
|
48
|
+
const focusOnThumb = thumbsRef.current.some((thumb) => thumb === document.activeElement);
|
|
49
|
+
const next = select(target);
|
|
50
|
+
// Fokus na miniaturze idzie za aktywnym obrazem — inaczej strzałka zmieniałaby obraz,
|
|
51
|
+
// a fokus zostawał na poprzedniej miniaturze.
|
|
52
|
+
if (focusOnThumb)
|
|
53
|
+
thumbsRef.current[next]?.focus();
|
|
54
|
+
};
|
|
55
|
+
const position = t('galleria.position', { index: active + 1, total: items.length });
|
|
56
|
+
const announcement = userMoved ? (current?.alt ? `${position}: ${current.alt}` : position) : '';
|
|
57
|
+
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) => {
|
|
58
|
+
thumbsRef.current[index] = el;
|
|
59
|
+
}, 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: "" }) }, index))) })) : null, _jsx("span", { className: "kpt-galleria__live", "aria-live": "polite", children: announcement })] }) }));
|
|
21
60
|
}
|
package/dist/galleria/llms.txt
CHANGED
|
@@ -7,13 +7,14 @@ Import: `import { KptGalleria, type KptGalleriaItem } from '@konce-pt/react';`
|
|
|
7
7
|
<div class="kpt-galleria-host">
|
|
8
8
|
<div class="kpt-galleria">
|
|
9
9
|
<div class="kpt-galleria__stage">
|
|
10
|
-
<button class="kpt-galleria__nav kpt-galleria__nav--prev">…</button>
|
|
11
|
-
<img class="kpt-galleria__image" src="…" alt="
|
|
12
|
-
<button class="kpt-galleria__nav kpt-galleria__nav--next">…</button>
|
|
10
|
+
<button class="kpt-galleria__nav kpt-galleria__nav--prev" aria-disabled="true" aria-label="Previous">…</button>
|
|
11
|
+
<img class="kpt-galleria__image" src="…" alt="Mountain lake">
|
|
12
|
+
<button class="kpt-galleria__nav kpt-galleria__nav--next" aria-label="Next">…</button>
|
|
13
13
|
</div>
|
|
14
14
|
<div class="kpt-galleria__thumbs">
|
|
15
|
-
<button class="kpt-galleria__thumb is-active"><img src="…"></button>…
|
|
15
|
+
<button class="kpt-galleria__thumb is-active" aria-current="true" aria-label="Mountain lake"><img src="…" alt=""></button>…
|
|
16
16
|
</div>
|
|
17
|
+
<span class="kpt-galleria__live" aria-live="polite">Image 2 of 4: Pine forest</span>
|
|
17
18
|
</div>
|
|
18
19
|
</div>
|
|
19
20
|
|
|
@@ -36,11 +37,19 @@ data is still loading shows the stage and the disabled arrows instead of crashin
|
|
|
36
37
|
<KptGalleria items={photos} activeIndex={index} onActiveIndexChange={setIndex} showThumbnails={false} />
|
|
37
38
|
|
|
38
39
|
## i18n
|
|
39
|
-
The arrows use `common.previous` / `common.next`, the thumbnails `galleria.image` (`{index}`).
|
|
40
|
+
The arrows use `common.previous` / `common.next`, the thumbnails without `alt` `galleria.image` (`{index}`), the announcement `galleria.position` (`{index}`, `{total}`).
|
|
41
|
+
|
|
42
|
+
## Keyboard
|
|
43
|
+
Anywhere inside the gallery, `ArrowLeft`/`ArrowRight` move to the neighbouring image (reversed in
|
|
44
|
+
RTL) and `Home`/`End` to the first/last. When focus is on a thumbnail, it follows the active one.
|
|
40
45
|
|
|
41
46
|
## Tokens
|
|
42
|
-
The stage `--kpt-color-muted
|
|
47
|
+
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`.
|
|
43
48
|
|
|
44
49
|
## Accessibility
|
|
45
|
-
Each thumbnail is a
|
|
46
|
-
|
|
50
|
+
The large image carries its `alt`. Each thumbnail is a button named by the item's `alt` (or
|
|
51
|
+
`galleria.image` — “Image 3” — when there is none), with `alt=""` on the thumbnail image so the
|
|
52
|
+
name is not read twice; the active one carries `aria-current`. The arrows use `aria-disabled` at
|
|
53
|
+
the ends instead of `disabled`, so they keep the focus. A change of image is announced politely
|
|
54
|
+
through a hidden live region — `galleria.position` (“Image 2 of 4”) plus the `alt` — only after the
|
|
55
|
+
user acts, never on first render.
|
package/dist/icon/icon.d.ts
CHANGED
|
@@ -4,14 +4,20 @@ export interface KptIconProps extends ComponentPropsWithRef<'span'> {
|
|
|
4
4
|
name: string;
|
|
5
5
|
/** Rozmiar jako długość CSS (np. '1.25rem'); domyślnie dziedziczy z `font-size`. */
|
|
6
6
|
size?: string;
|
|
7
|
+
/** Nazwa dla czytnika ekranu — tylko gdy ikona niesie znaczenie bez tekstu obok. */
|
|
8
|
+
label?: string;
|
|
7
9
|
}
|
|
8
10
|
/**
|
|
9
11
|
* Ikona Koncept UI (inline SVG, zestaw Tabler Icons — MIT). Rozmiar dziedziczy z `font-size`
|
|
10
12
|
* (1em) lub ustaw `size`; kolor z `currentColor`. Ikona musi być w rejestrze — wbudowany
|
|
11
13
|
* zestaw działa od razu, pełny dodaje `registerKptTablerIcons()` z `@konce-pt/react/icons`.
|
|
12
14
|
*
|
|
15
|
+
* Domyślnie ikona jest dekoracją (`aria-hidden`). Ikona niosąca znaczenie sama, bez tekstu obok,
|
|
16
|
+
* dostaje `label` — wtedy jest `role="img"` z tą nazwą. W buildzie deweloperskim nieznana nazwa
|
|
17
|
+
* daje jedno ostrzeżenie w konsoli zamiast cichej pustki.
|
|
18
|
+
*
|
|
13
19
|
* @example
|
|
14
20
|
* <KptIcon name="menu" />
|
|
15
21
|
* <KptIcon name="check" size="1.25rem" />
|
|
16
22
|
*/
|
|
17
|
-
export declare function KptIcon({ name, size, className, style, ...rest }: Readonly<KptIconProps>): import("react").JSX.Element;
|
|
23
|
+
export declare function KptIcon({ name, size, label, className, style, ...rest }: Readonly<KptIconProps>): import("react").JSX.Element;
|
package/dist/icon/icon.js
CHANGED
|
@@ -1,23 +1,37 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { renderKptIconSvg } from '@konce-pt/icons';
|
|
3
|
-
import { useSyncExternalStore } from 'react';
|
|
3
|
+
import { useEffect, useSyncExternalStore } from 'react';
|
|
4
4
|
import { cn } from "../internal/cn.js";
|
|
5
|
+
import { isDevBuild } from "../internal/dev.js";
|
|
5
6
|
import { getKptIcon, getKptIconsVersion, subscribeKptIcons } from "./icon-registry.js";
|
|
7
|
+
/** Nazwy, o których już ostrzegliśmy — jedno ostrzeżenie na nazwę, nie na każdą instancję. */
|
|
8
|
+
const warned = new Set();
|
|
6
9
|
/**
|
|
7
10
|
* Ikona Koncept UI (inline SVG, zestaw Tabler Icons — MIT). Rozmiar dziedziczy z `font-size`
|
|
8
11
|
* (1em) lub ustaw `size`; kolor z `currentColor`. Ikona musi być w rejestrze — wbudowany
|
|
9
12
|
* zestaw działa od razu, pełny dodaje `registerKptTablerIcons()` z `@konce-pt/react/icons`.
|
|
10
13
|
*
|
|
14
|
+
* Domyślnie ikona jest dekoracją (`aria-hidden`). Ikona niosąca znaczenie sama, bez tekstu obok,
|
|
15
|
+
* dostaje `label` — wtedy jest `role="img"` z tą nazwą. W buildzie deweloperskim nieznana nazwa
|
|
16
|
+
* daje jedno ostrzeżenie w konsoli zamiast cichej pustki.
|
|
17
|
+
*
|
|
11
18
|
* @example
|
|
12
19
|
* <KptIcon name="menu" />
|
|
13
20
|
* <KptIcon name="check" size="1.25rem" />
|
|
14
21
|
*/
|
|
15
|
-
export function KptIcon({ name, size = '', className, style, ...rest }) {
|
|
22
|
+
export function KptIcon({ name, size = '', label = '', className, style, ...rest }) {
|
|
16
23
|
// Rejestr bywa uzupełniany po pierwszym renderze (leniwy import zestawu Tabler),
|
|
17
24
|
// więc komponent słucha jego wersji zamiast czytać go raz.
|
|
18
25
|
useSyncExternalStore(subscribeKptIcons, getKptIconsVersion, getKptIconsVersion);
|
|
19
26
|
// Treść pochodzi z zaufanego rejestru i przeszła sanitizację przy rejestracji —
|
|
20
27
|
// to ten sam kontrakt, co `innerHTML` w porcie Angulara.
|
|
21
|
-
const
|
|
22
|
-
|
|
28
|
+
const svg = getKptIcon(name);
|
|
29
|
+
const html = renderKptIconSvg(svg);
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (svg || !isDevBuild() || warned.has(name))
|
|
32
|
+
return;
|
|
33
|
+
warned.add(name);
|
|
34
|
+
console.warn(`KptIcon: ikony „${name}” nie ma (jeszcze) w rejestrze — sprawdź nazwę albo zarejestruj ją (registerKptIcons / registerKptTablerIcons).`);
|
|
35
|
+
}, [svg, name]);
|
|
36
|
+
return (_jsx("span", { className: cn('kpt-icon-host', className), "aria-hidden": label ? undefined : true, role: label ? 'img' : undefined, "aria-label": label || undefined, style: size ? { fontSize: size, ...style } : style, dangerouslySetInnerHTML: { __html: html }, ...rest }));
|
|
23
37
|
}
|
package/dist/icon/llms.txt
CHANGED
|
@@ -12,6 +12,7 @@ Import: `import { KptIcon } from '@konce-pt/react';`
|
|
|
12
12
|
## Props
|
|
13
13
|
- `name`: string — the icon name from the registry (required)
|
|
14
14
|
- `size`: string — a CSS length (e.g. '1.25rem'); by default inherited from `font-size`
|
|
15
|
+
- `label`: string — a name for screen readers; with it the icon is `role="img"`, without it `aria-hidden`
|
|
15
16
|
- `className`, `ref` and every other `<span>` prop go to the host
|
|
16
17
|
|
|
17
18
|
## The registry
|
|
@@ -62,5 +63,7 @@ CMS, an API or an upload.
|
|
|
62
63
|
<KptButton><KptIcon name="phone" /> Call</KptButton>
|
|
63
64
|
|
|
64
65
|
## Accessibility
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
By default an icon is `aria-hidden` — decorative, which is right next to a text label or inside
|
|
67
|
+
an icon button with its own name. An icon that carries meaning on its own gets `label`: it becomes
|
|
68
|
+
`role="img"` with that `aria-label`. In development builds an unknown name logs one console
|
|
69
|
+
warning per name instead of rendering silently empty.
|
package/dist/image/image.d.ts
CHANGED
|
@@ -11,6 +11,11 @@ export interface KptImageProps extends Omit<ComponentPropsWithRef<'div'>, 'onCli
|
|
|
11
11
|
/**
|
|
12
12
|
* Obraz z opcjonalnym podglądem pełnoekranowym.
|
|
13
13
|
*
|
|
14
|
+
* Z `preview` miniatura jest przyciskiem (osiągalnym z klawiatury), a podgląd otwiera się na
|
|
15
|
+
* natywnym `<dialog>` przez `showModal()`: przeglądarka daje pułapkę fokusu, `Escape`, bezwładną
|
|
16
|
+
* stronę i górną warstwę. Na czas podglądu blokujemy przewijanie dokumentu, a po zamknięciu
|
|
17
|
+
* fokus wraca na miniaturę.
|
|
18
|
+
*
|
|
14
19
|
* @example
|
|
15
20
|
* <KptImage src="/foto.jpg" alt="Zdjęcie" preview width="240px" />
|
|
16
21
|
*/
|
package/dist/image/image.js
CHANGED
|
@@ -1,16 +1,52 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState } from 'react';
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
3
|
import { cn } from "../internal/cn.js";
|
|
4
4
|
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
5
5
|
import { KptIcon } from "../icon/icon.js";
|
|
6
6
|
/**
|
|
7
7
|
* Obraz z opcjonalnym podglądem pełnoekranowym.
|
|
8
8
|
*
|
|
9
|
+
* Z `preview` miniatura jest przyciskiem (osiągalnym z klawiatury), a podgląd otwiera się na
|
|
10
|
+
* natywnym `<dialog>` przez `showModal()`: przeglądarka daje pułapkę fokusu, `Escape`, bezwładną
|
|
11
|
+
* stronę i górną warstwę. Na czas podglądu blokujemy przewijanie dokumentu, a po zamknięciu
|
|
12
|
+
* fokus wraca na miniaturę.
|
|
13
|
+
*
|
|
9
14
|
* @example
|
|
10
15
|
* <KptImage src="/foto.jpg" alt="Zdjęcie" preview width="240px" />
|
|
11
16
|
*/
|
|
12
17
|
export function KptImage({ src, alt = '', width = null, preview = false, locale, dictionary, className, ...rest }) {
|
|
13
18
|
const { t } = useKptMessages({ locale, dictionary });
|
|
14
19
|
const [open, setOpen] = useState(false);
|
|
15
|
-
|
|
20
|
+
const dialogRef = useRef(null);
|
|
21
|
+
const triggerRef = useRef(null);
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const el = dialogRef.current;
|
|
24
|
+
if (!el || !open)
|
|
25
|
+
return;
|
|
26
|
+
if (!el.open)
|
|
27
|
+
el.showModal();
|
|
28
|
+
const root = document.documentElement;
|
|
29
|
+
const saved = root.style.overflow;
|
|
30
|
+
root.style.overflow = 'hidden';
|
|
31
|
+
return () => {
|
|
32
|
+
root.style.overflow = saved;
|
|
33
|
+
if (el.open)
|
|
34
|
+
el.close();
|
|
35
|
+
};
|
|
36
|
+
}, [open]);
|
|
37
|
+
const style = width ? { width } : undefined;
|
|
38
|
+
if (!preview) {
|
|
39
|
+
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
|
+
}
|
|
41
|
+
// `close` przychodzi z każdej drogi: `×`, `Escape`, tło.
|
|
42
|
+
const onClosed = () => {
|
|
43
|
+
setOpen(false);
|
|
44
|
+
triggerRef.current?.focus();
|
|
45
|
+
};
|
|
46
|
+
// 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
|
+
const onDialogClick = (event) => {
|
|
48
|
+
if (event.target === event.currentTarget)
|
|
49
|
+
event.currentTarget.close();
|
|
50
|
+
};
|
|
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: () => dialogRef.current?.close(), "aria-label": t('common.close'), children: _jsx(KptIcon, { name: "x" }) }), _jsx("img", { className: "kpt-image__full", src: src, alt: alt, loading: "lazy" })] })] }));
|
|
16
52
|
}
|
package/dist/image/llms.txt
CHANGED
|
@@ -5,27 +5,29 @@ Import: `import { KptImage } from '@konce-pt/react';`
|
|
|
5
5
|
|
|
6
6
|
## DOM
|
|
7
7
|
<div class="kpt-image-host">
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
<
|
|
16
|
-
|
|
8
|
+
<!-- with preview: -->
|
|
9
|
+
<button class="kpt-image kpt-image--preview" type="button" aria-haspopup="dialog"
|
|
10
|
+
aria-label="Show full size: Photo" style="width:240px">
|
|
11
|
+
<img class="kpt-image__img" src="…" alt="">
|
|
12
|
+
<span class="kpt-image__mask" aria-hidden="true">…zoom-in…</span>
|
|
13
|
+
</button>
|
|
14
|
+
<dialog class="kpt-image__overlay" aria-label="Photo">
|
|
15
|
+
<button class="kpt-image__close" type="button" aria-label="Close">…</button>
|
|
16
|
+
<img class="kpt-image__full" src="…" alt="Photo" loading="lazy">
|
|
17
|
+
</dialog>
|
|
18
|
+
<!-- without preview: <div class="kpt-image"><img class="kpt-image__img" …></div> -->
|
|
17
19
|
</div>
|
|
18
20
|
|
|
19
21
|
## Props
|
|
20
22
|
- `src`: string (required); `alt`: string
|
|
21
23
|
- `width`: string | null — a CSS length, e.g. `'240px'`
|
|
22
|
-
- `preview`: boolean — a
|
|
24
|
+
- `preview`: boolean — the thumbnail becomes a button opening a modal preview
|
|
23
25
|
- `className`, `ref` and every other `<div>` prop go to the host
|
|
24
26
|
|
|
25
27
|
## The overlay closes around the photo, not on it
|
|
26
|
-
The
|
|
27
|
-
the
|
|
28
|
-
|
|
28
|
+
The dialog fills the viewport, so a click on the dialog element itself — not on the photo or the
|
|
29
|
+
× — is a click on the backdrop and closes the preview. Clicking the photo (to look closer, to drag
|
|
30
|
+
it) does not dismiss it; Escape and × are the explicit ways out.
|
|
29
31
|
|
|
30
32
|
## The mask is decorative
|
|
31
33
|
The zoom-in overlay is `aria-hidden`: it repeats what the cursor (`zoom-in`) already says and would
|
|
@@ -36,11 +38,16 @@ only add noise for a screen reader.
|
|
|
36
38
|
<KptImage src="/logo.svg" alt="Logo" />
|
|
37
39
|
|
|
38
40
|
## i18n
|
|
39
|
-
The close button
|
|
41
|
+
The thumbnail is named from `image.previewOf` (`{alt}`) or `image.preview`; the close button from `common.close`.
|
|
40
42
|
|
|
41
43
|
## Tokens
|
|
42
|
-
The radius `--kpt-radius-md
|
|
44
|
+
The radius `--kpt-radius-md`; the backdrop and the hover mask `--kpt-color-overlay-scrim`; the × button and the magnifier disc `--kpt-color-surface` / `--kpt-color-on-surface` with `--kpt-elevation-2`.
|
|
43
45
|
|
|
44
46
|
## Accessibility
|
|
45
|
-
`
|
|
46
|
-
|
|
47
|
+
With `preview`, the thumbnail is a `<button aria-haspopup="dialog">` named “Show full size: <alt>”
|
|
48
|
+
(`image.previewOf`, or `image.preview` without `alt`), so it is reached with Tab and opened with
|
|
49
|
+
Enter or Space; the magnifier shows on hover and on `:focus-visible`. The preview is a native modal
|
|
50
|
+
`<dialog>` opened with `showModal()` and named by `alt`: focus moves to its × button and stays inside,
|
|
51
|
+
Escape closes it, the page behind is inert, document scrolling is locked while it is open, and focus
|
|
52
|
+
returns to the thumbnail. It sits in the top layer, so no ancestor with `transform` or
|
|
53
|
+
`overflow: hidden` can clip it.
|
package/dist/input/input.d.ts
CHANGED
|
@@ -38,4 +38,4 @@ export interface KptInputProps extends Omit<ComponentPropsWithRef<'input'>, 'typ
|
|
|
38
38
|
* <KptInput name="email" type="email" defaultValue="" />
|
|
39
39
|
* <KptInput value={email} onValueChange={setEmail} invalid={!ok} touched={blurred} />
|
|
40
40
|
*/
|
|
41
|
-
export declare function KptInput({ type, required, invalid, touched, onValueChange, onTouch, hostProps, className, id, onChange, onBlur, 'aria-invalid': ariaInvalid, ...rest }: Readonly<KptInputProps>): import("react").JSX.Element;
|
|
41
|
+
export declare function KptInput({ type, required, invalid, touched, onValueChange, onTouch, hostProps, className, id, onChange, onBlur, 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedBy, ...rest }: Readonly<KptInputProps>): import("react").JSX.Element;
|
package/dist/input/input.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback } from 'react';
|
|
3
3
|
import { cn } from "../internal/cn.js";
|
|
4
|
-
import {
|
|
5
|
-
import { useKptFormFieldContext } from "../form-field/form-field-context.js";
|
|
4
|
+
import { joinIds, useKptFieldA11y } from "../form-field/form-field-context.js";
|
|
6
5
|
/**
|
|
7
6
|
* Pole tekstowe Koncept UI. Renderuje natywny `<input>` w hoście `.kpt-input-host`.
|
|
8
7
|
*
|
|
@@ -17,10 +16,9 @@ import { useKptFormFieldContext } from "../form-field/form-field-context.js";
|
|
|
17
16
|
* <KptInput name="email" type="email" defaultValue="" />
|
|
18
17
|
* <KptInput value={email} onValueChange={setEmail} invalid={!ok} touched={blurred} />
|
|
19
18
|
*/
|
|
20
|
-
export function KptInput({ type = 'text', required = false, invalid = false, touched, onValueChange, onTouch, hostProps, className, id, onChange, onBlur, 'aria-invalid': ariaInvalid, ...rest }) {
|
|
21
|
-
const field = useKptFormFieldContext();
|
|
19
|
+
export function KptInput({ type = 'text', required = false, invalid = false, touched, onValueChange, onTouch, hostProps, className, id, onChange, onBlur, 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedBy, ...rest }) {
|
|
22
20
|
const { className: hostClassName, ...hostRest } = hostProps ?? {};
|
|
23
|
-
const
|
|
21
|
+
const field = useKptFieldA11y(id, 'kpt-input');
|
|
24
22
|
// Stan nieprawidłowy pokazujemy dopiero po dotknięciu pola — jak natywne `:user-invalid`.
|
|
25
23
|
// Bez `touched` nie ma czego bramkować, więc decyduje samo `invalid`.
|
|
26
24
|
const showInvalid = invalid && (touched ?? true);
|
|
@@ -32,5 +30,5 @@ export function KptInput({ type = 'text', required = false, invalid = false, tou
|
|
|
32
30
|
onBlur?.(event);
|
|
33
31
|
onTouch?.();
|
|
34
32
|
}, [onBlur, onTouch]);
|
|
35
|
-
return (_jsx("span", { className: cn('kpt-input-host', hostClassName), ...hostRest, children: _jsx("input", { className: cn('kpt-input__field', className), id:
|
|
33
|
+
return (_jsx("span", { className: cn('kpt-input-host', hostClassName), ...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, ...rest }) }));
|
|
36
34
|
}
|
package/dist/input/llms.txt
CHANGED
|
@@ -46,6 +46,11 @@ configuration. An explicit `id` prop still wins.
|
|
|
46
46
|
<KptInput type="email" invalid={!!err} onTouch={markTouched} />
|
|
47
47
|
</KptFormField>
|
|
48
48
|
|
|
49
|
+
## Label and description
|
|
50
|
+
`id` goes to the native field, not the host; without one it comes from `KptFormField` or is
|
|
51
|
+
generated. Inside `KptFormField` the wrapper's `label[for]` points at it and the hint or error is
|
|
52
|
+
added to `aria-describedby` (merged with your own). Without a wrapper, name it with `aria-label`.
|
|
53
|
+
|
|
49
54
|
## Tokens
|
|
50
55
|
`--kpt-form-field-{bg,text,placeholder,border,border-hover,border-focus,border-error,radius}`.
|
|
51
56
|
|
|
@@ -16,4 +16,4 @@ export interface KptInputMaskProps extends Omit<ComponentPropsWithRef<'input'>,
|
|
|
16
16
|
* @example
|
|
17
17
|
* <KptInputMask mask="(999) 999-99-99" value={phone} onValueChange={setPhone} />
|
|
18
18
|
*/
|
|
19
|
-
export declare function KptInputMask({ value, defaultValue, onValueChange, mask, placeholder, invalid, touched, onTouch, disabled, className, id, onBlur, ...rest }: Readonly<KptInputMaskProps>): import("react").JSX.Element;
|
|
19
|
+
export declare function KptInputMask({ value, defaultValue, onValueChange, mask, placeholder, invalid, touched, onTouch, disabled, className, id, onBlur, 'aria-describedby': ariaDescribedBy, inputMode, ...rest }: Readonly<KptInputMaskProps>): import("react").JSX.Element;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from "../internal/cn.js";
|
|
3
3
|
import { useControllableState } from "../internal/use-controllable-state.js";
|
|
4
|
-
import {
|
|
5
|
-
import { useKptFormFieldContext } from "../form-field/form-field-context.js";
|
|
4
|
+
import { joinIds, useKptFieldA11y } from "../form-field/form-field-context.js";
|
|
6
5
|
/** Tokeny maski: `9` = cyfra, `a` = litera, `*` = znak alfanumeryczny. */
|
|
7
6
|
const TOKENS = {
|
|
8
7
|
'9': /\d/,
|
|
@@ -16,9 +15,12 @@ const TOKENS = {
|
|
|
16
15
|
* @example
|
|
17
16
|
* <KptInputMask mask="(999) 999-99-99" value={phone} onValueChange={setPhone} />
|
|
18
17
|
*/
|
|
19
|
-
export function KptInputMask({ value, defaultValue = '', onValueChange, mask = '', placeholder, invalid = false, touched, onTouch, disabled = false, className, id, onBlur, ...rest }) {
|
|
20
|
-
const field =
|
|
21
|
-
|
|
18
|
+
export function KptInputMask({ value, defaultValue = '', onValueChange, mask = '', placeholder, invalid = false, touched, onTouch, disabled = false, className, id, onBlur, 'aria-describedby': ariaDescribedBy, inputMode, ...rest }) {
|
|
19
|
+
const field = useKptFieldA11y(id, 'kpt-input-mask');
|
|
20
|
+
// Klawiatura ekranowa z maski: same tokeny `9` (telefon, kod pocztowy) → numeryczna.
|
|
21
|
+
const tokens = [...mask].filter((char) => char in TOKENS);
|
|
22
|
+
const autoInputMode = tokens.length && tokens.every((char) => char === '9') ? 'numeric' : 'text';
|
|
23
|
+
const inputId = field.id;
|
|
22
24
|
const [current, setCurrent] = useControllableState({ value, defaultValue, onChange: onValueChange });
|
|
23
25
|
const showInvalid = invalid && (touched ?? true);
|
|
24
26
|
/*
|
|
@@ -58,7 +60,7 @@ export function KptInputMask({ value, defaultValue = '', onValueChange, mask = '
|
|
|
58
60
|
// wpisany znak zostałby na ekranie, więc DOM zrównujemy ręcznie.
|
|
59
61
|
event.target.value = formatted;
|
|
60
62
|
};
|
|
61
|
-
return (_jsx("span", { className: "kpt-input-mask-host", children: _jsx("input", { className: cn('kpt-input-mask__input', showInvalid && 'kpt-input-mask--invalid', className), id: inputId, type: "text", value: current, disabled: disabled, placeholder: placeholder || mask, onChange: onInput, onBlur: (event) => {
|
|
63
|
+
return (_jsx("span", { className: "kpt-input-mask-host", children: _jsx("input", { className: cn('kpt-input-mask__input', showInvalid && 'kpt-input-mask--invalid', className), id: inputId, "aria-invalid": showInvalid || undefined, "aria-describedby": joinIds(ariaDescribedBy, field.describedBy), inputMode: inputMode ?? autoInputMode, type: "text", value: current, disabled: disabled, placeholder: placeholder || mask, onChange: onInput, onBlur: (event) => {
|
|
62
64
|
onBlur?.(event);
|
|
63
65
|
onTouch?.();
|
|
64
66
|
}, ...rest }) }));
|
package/dist/input-mask/llms.txt
CHANGED
|
@@ -29,6 +29,17 @@ change, so React would not re-render and the character would linger on screen.
|
|
|
29
29
|
<KptInputMask mask="(999) 999-99-99" value={phone} onValueChange={setPhone} />
|
|
30
30
|
<KptInputMask mask="aa-9999" value={code} onValueChange={setCode} />
|
|
31
31
|
|
|
32
|
+
## Screen reader and keyboard
|
|
33
|
+
The on-screen keyboard follows the mask: only `9` tokens (phone, postal code) give
|
|
34
|
+
`inputMode="numeric"`, otherwise `text`; pass `inputMode` to override it, and `autoComplete` (`tel`,
|
|
35
|
+
`postal-code`…) so the browser can fill the field in.
|
|
36
|
+
|
|
37
|
+
## Label and description
|
|
38
|
+
`id` goes to the native field, not the host; without one it comes from `KptFormField` or is
|
|
39
|
+
generated. Inside `KptFormField` the wrapper's `label[for]` points at it and the hint or error is
|
|
40
|
+
added to `aria-describedby` (merged with your own). Without a wrapper, name it with `aria-label`.
|
|
41
|
+
New: `aria-invalid` after touch.
|
|
42
|
+
|
|
32
43
|
## Tokens
|
|
33
44
|
`--kpt-form-field-{bg,text,placeholder,border,border-focus,border-error,radius}`.
|
|
34
45
|
|