@konce-pt/react 0.11.2 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -2
- package/dist/accordion/accordion.js +11 -10
- package/dist/alert/alert.js +1 -0
- package/dist/alert/llms.txt +1 -0
- package/dist/app-shell/app-shell.js +12 -5
- package/dist/app-shell/llms.txt +5 -4
- package/dist/auth/auth.js +17 -8
- package/dist/auth/llms.txt +3 -2
- package/dist/autocomplete/autocomplete.d.ts +1 -1
- package/dist/autocomplete/autocomplete.js +12 -4
- package/dist/autocomplete/llms.txt +3 -0
- package/dist/avatar/avatar.js +1 -0
- package/dist/avatar-group/avatar-group.js +1 -0
- package/dist/backdrop/backdrop.d.ts +0 -19
- package/dist/backdrop/backdrop.js +18 -13
- package/dist/bottom-sheet/bottom-sheet.d.ts +6 -1
- package/dist/bottom-sheet/bottom-sheet.js +3 -2
- package/dist/bottom-sheet/llms.txt +3 -0
- package/dist/breadcrumb/breadcrumb.d.ts +7 -1
- package/dist/breadcrumb/breadcrumb.js +10 -2
- package/dist/breadcrumb/llms.txt +8 -0
- package/dist/button/button.js +2 -1
- package/dist/button-group/button-group.d.ts +14 -1
- package/dist/button-group/button-group.js +30 -4
- package/dist/button-group/llms.txt +9 -6
- package/dist/card/card.js +4 -3
- package/dist/carousel/carousel.js +4 -1
- package/dist/charts/chart-legend.js +1 -0
- package/dist/charts/chart.d.ts +3 -2
- package/dist/charts/chart.js +12 -4
- package/dist/charts/llms.txt +6 -3
- package/dist/chat/chat.js +39 -9
- package/dist/chat/llms.txt +9 -0
- package/dist/chat-composer/chat-composer.d.ts +5 -2
- package/dist/chat-composer/chat-composer.js +10 -1
- package/dist/chat-dock/chat-dock.js +1 -0
- package/dist/chat-message/chat-message.js +2 -1
- package/dist/chat-typing/chat-typing.js +1 -0
- package/dist/checkbox/checkbox.d.ts +1 -1
- package/dist/checkbox/checkbox.js +9 -2
- package/dist/checkbox/llms.txt +3 -0
- package/dist/chip/chip.js +1 -0
- package/dist/chips-input/chips-input.d.ts +3 -1
- package/dist/chips-input/chips-input.js +14 -5
- package/dist/chips-input/llms.txt +3 -0
- package/dist/clock/clock.d.ts +9 -4
- package/dist/clock/clock.js +35 -5
- package/dist/clock/llms.txt +6 -2
- package/dist/color-picker/color-picker.d.ts +5 -1
- package/dist/color-picker/color-picker.js +36 -17
- package/dist/color-picker/llms.txt +3 -0
- package/dist/confirm/confirm-container.js +1 -0
- package/dist/confirm/confirm-store.js +6 -0
- package/dist/confirm/llms.txt +2 -1
- package/dist/context-menu/context-menu.js +35 -12
- package/dist/context-menu/llms.txt +3 -0
- package/dist/data-table/data-table.d.ts +12 -1
- package/dist/data-table/data-table.js +113 -51
- package/dist/data-table/export-csv.d.ts +2 -2
- package/dist/data-table/export-csv.js +7 -9
- package/dist/data-table/llms.txt +19 -4
- package/dist/data-view/data-view.js +1 -0
- package/dist/date-range/date-range.d.ts +16 -4
- package/dist/date-range/date-range.js +69 -46
- package/dist/date-range/llms.txt +17 -3
- package/dist/datepicker/datepicker.d.ts +17 -3
- package/dist/datepicker/datepicker.js +86 -54
- package/dist/datepicker/llms.txt +17 -3
- package/dist/dialog/dialog.js +29 -8
- package/dist/dialog/llms.txt +3 -0
- package/dist/divider/divider.d.ts +7 -1
- package/dist/divider/divider.js +3 -2
- package/dist/divider/llms.txt +1 -0
- package/dist/drawer/drawer.d.ts +6 -1
- package/dist/drawer/drawer.js +3 -2
- package/dist/drawer/llms.txt +3 -0
- package/dist/fieldset/fieldset.js +4 -1
- package/dist/file-upload/file-upload.d.ts +7 -1
- package/dist/file-upload/file-upload.js +18 -7
- package/dist/file-upload/llms.txt +6 -3
- package/dist/form-field/form-field.js +1 -0
- package/dist/galleria/galleria.js +4 -1
- package/dist/i18n/i18n-context.d.ts +3 -3
- package/dist/i18n/i18n-context.js +25 -3
- package/dist/icon/icon.js +4 -1
- package/dist/image/image.js +4 -4
- package/dist/input/input.js +1 -0
- package/dist/input-mask/input-mask.d.ts +7 -2
- package/dist/input-mask/input-mask.js +15 -41
- package/dist/input-mask/llms.txt +3 -0
- package/dist/input-number/input-number.d.ts +3 -1
- package/dist/input-number/input-number.js +15 -11
- package/dist/input-number/llms.txt +5 -2
- package/dist/input-otp/input-otp.d.ts +16 -3
- package/dist/input-otp/input-otp.js +43 -19
- package/dist/input-otp/llms.txt +11 -0
- package/dist/internal/mask.d.ts +14 -0
- package/dist/internal/mask.js +62 -0
- package/dist/internal/number-group.d.ts +22 -0
- package/dist/internal/number-group.js +53 -0
- package/dist/internal/option-key.d.ts +6 -0
- package/dist/internal/option-key.js +8 -0
- package/dist/internal/overlay.js +37 -7
- package/dist/internal/password-strength.d.ts +6 -0
- package/dist/internal/password-strength.js +74 -0
- package/dist/internal/scroll-lock.d.ts +6 -0
- package/dist/internal/scroll-lock.js +35 -0
- package/dist/internal/slot-children.d.ts +13 -0
- package/dist/internal/slot-children.js +31 -0
- package/dist/internal/step.d.ts +12 -0
- package/dist/internal/step.js +31 -0
- package/dist/internal/tween.js +2 -3
- package/dist/internal/use-controllable-state.d.ts +5 -0
- package/dist/internal/use-controllable-state.js +25 -4
- package/dist/internal/use-modal-focus.d.ts +1 -0
- package/dist/internal/use-modal-focus.js +12 -1
- package/dist/knob/knob.d.ts +5 -2
- package/dist/knob/knob.js +15 -14
- package/dist/knob/llms.txt +4 -2
- package/dist/listbox/listbox.d.ts +3 -1
- package/dist/listbox/listbox.js +30 -10
- package/dist/listbox/llms.txt +3 -0
- package/dist/map/llms.txt +5 -1
- package/dist/map/map-create-dialog.js +30 -22
- package/dist/map/map-hours-field.js +1 -0
- package/dist/map/map-image-field.js +2 -1
- package/dist/map/map-legend.js +1 -0
- package/dist/map/map-poi-card.js +2 -1
- package/dist/map/map-search.js +54 -30
- package/dist/map/map.d.ts +6 -1
- package/dist/map/map.js +62 -34
- package/dist/megamenu/llms.txt +11 -0
- package/dist/megamenu/megamenu.d.ts +7 -1
- package/dist/megamenu/megamenu.js +15 -6
- package/dist/menu/menu-rows.js +1 -0
- package/dist/menu/menu.js +10 -3
- package/dist/menubar/llms.txt +11 -0
- package/dist/menubar/menubar.d.ts +7 -1
- package/dist/menubar/menubar.js +15 -3
- package/dist/meter-group/meter-group.js +1 -0
- package/dist/order-list/llms.txt +3 -0
- package/dist/order-list/order-list.js +21 -6
- package/dist/paginator/paginator.js +7 -4
- package/dist/panel/panel.js +5 -3
- package/dist/password/llms.txt +6 -2
- package/dist/password/password.js +4 -14
- package/dist/peek/llms.txt +1 -1
- package/dist/peek/peek.js +37 -15
- package/dist/pick-list/llms.txt +3 -0
- package/dist/pick-list/pick-list.js +19 -3
- package/dist/popover/llms.txt +3 -0
- package/dist/popover/popover.d.ts +3 -1
- package/dist/popover/popover.js +10 -3
- package/dist/progress/progress.js +1 -0
- package/dist/public-api.d.ts +1 -1
- package/dist/radio-group/llms.txt +3 -0
- package/dist/radio-group/radio-group.d.ts +3 -1
- package/dist/radio-group/radio-group.js +13 -2
- package/dist/rating/rating.js +1 -0
- package/dist/rich-text/llms.txt +14 -0
- package/dist/rich-text/rich-text.d.ts +3 -9
- package/dist/rich-text/rich-text.js +241 -96
- package/dist/rich-text/sanitize.d.ts +9 -0
- package/dist/rich-text/sanitize.js +77 -29
- package/dist/roadmap/llms.txt +9 -3
- package/dist/roadmap/roadmap.js +88 -11
- package/dist/scroll-top/scroll-top.js +1 -0
- package/dist/select/llms.txt +3 -0
- package/dist/select/select.d.ts +3 -1
- package/dist/select/select.js +46 -32
- package/dist/shared/types.d.ts +21 -0
- package/dist/shared/types.js +9 -1
- package/dist/sidenav/sidenav.d.ts +1 -2
- package/dist/sidenav/sidenav.js +4 -3
- package/dist/slider/llms.txt +3 -0
- package/dist/slider/slider.d.ts +3 -1
- package/dist/slider/slider.js +15 -4
- package/dist/speed-dial/speed-dial.js +2 -1
- package/dist/spinner/spinner.js +1 -0
- package/dist/split-button/split-button.js +10 -3
- package/dist/splitter/splitter.js +23 -8
- package/dist/stepper/llms.txt +3 -0
- package/dist/stepper/stepper.js +9 -5
- package/dist/switch/llms.txt +3 -0
- package/dist/switch/switch.d.ts +1 -1
- package/dist/switch/switch.js +9 -2
- package/dist/switch-group/llms.txt +3 -0
- package/dist/switch-group/switch-group.d.ts +3 -1
- package/dist/switch-group/switch-group.js +5 -3
- package/dist/tabs/llms.txt +3 -0
- package/dist/tabs/tabs.js +30 -14
- package/dist/textarea/textarea.js +1 -0
- package/dist/toast/llms.txt +3 -0
- package/dist/toast/toast-container.js +1 -0
- package/dist/toast/toast-store.d.ts +5 -1
- package/dist/toast/toast-store.js +1 -1
- package/dist/tooltip/llms.txt +5 -3
- package/dist/tooltip/tooltip.d.ts +3 -2
- package/dist/tooltip/tooltip.js +90 -31
- package/dist/tree/llms.txt +1 -0
- package/dist/tree/tree.d.ts +6 -1
- package/dist/tree/tree.js +14 -7
- package/package.json +15 -14
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
3
|
import { useRef, useState } from 'react';
|
|
3
4
|
import { cn } from "../internal/cn.js";
|
|
4
5
|
import { useControllableState } from "../internal/use-controllable-state.js";
|
|
5
6
|
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
6
7
|
import { KptIcon } from "../icon/icon.js";
|
|
8
|
+
import { useKptFieldA11y } from "../form-field/form-field-context.js";
|
|
7
9
|
const EMPTY = [];
|
|
8
10
|
/** Rozmiar pliku w KB albo MB — bez bibliotek, bo to jedna reguła. */
|
|
9
11
|
function formatSize(file) {
|
|
@@ -26,14 +28,20 @@ function fileIcon(file) {
|
|
|
26
28
|
* @example
|
|
27
29
|
* <KptFileUpload value={files} onValueChange={setFiles} accept="image/*" multiple />
|
|
28
30
|
*/
|
|
29
|
-
export function KptFileUpload({ value, defaultValue = EMPTY, onValueChange, accept = '', multiple = false, disabled = false, invalid = false, touched, onTouch, locale, dictionary, className, ...rest }) {
|
|
31
|
+
export function KptFileUpload({ value, defaultValue = EMPTY, onValueChange, accept = '', multiple = false, disabled = false, readOnly = false, id, ariaLabel = null, invalid = false, touched, onTouch, locale, dictionary, className, ...rest }) {
|
|
30
32
|
const { t } = useKptMessages({ locale, dictionary });
|
|
31
33
|
const inputRef = useRef(null);
|
|
34
|
+
const zoneRef = useRef(null);
|
|
35
|
+
/** `div` z rolą nie dostaje nazwy z `label[for]` — stąd `aria-labelledby` na etykietę wrappera. */
|
|
36
|
+
const field = useKptFieldA11y(id, 'kpt-file-upload');
|
|
37
|
+
// Podpowiedź rozcięta w miejscu `{action}` — pogrubienie robi JSX, nie HTML ze słownika.
|
|
38
|
+
const [hintBefore = '', hintAfter = ''] = t('fileUpload.hint').split('{action}');
|
|
39
|
+
const locked = disabled || readOnly;
|
|
32
40
|
const [current, setCurrent] = useControllableState({ value, defaultValue, onChange: onValueChange });
|
|
33
41
|
const [dragover, setDragover] = useState(false);
|
|
34
42
|
const showError = invalid && (touched ?? true);
|
|
35
43
|
const openPicker = () => {
|
|
36
|
-
if (!
|
|
44
|
+
if (!locked)
|
|
37
45
|
inputRef.current?.click();
|
|
38
46
|
};
|
|
39
47
|
/** `Enter` / spacja na strefie — spacja bez `preventDefault` przewinęłaby stronę. */
|
|
@@ -55,17 +63,20 @@ export function KptFileUpload({ value, defaultValue = EMPTY, onValueChange, acce
|
|
|
55
63
|
// Czyszczenie pola pozwala wybrać ten sam plik drugi raz — inaczej `change` by nie poleciał.
|
|
56
64
|
event.target.value = '';
|
|
57
65
|
};
|
|
58
|
-
return (_jsxs("div", { className: cn('kpt-file-upload-host', className), ...rest, children: [_jsxs("div", { className: cn('kpt-file-upload__zone', dragover && 'is-dragover', disabled && 'is-disabled'), role: "button", tabIndex: disabled ? -1 : 0, "aria-disabled":
|
|
66
|
+
return (_jsxs("div", { className: cn('kpt-file-upload-host', className), ...rest, children: [_jsxs("div", { className: cn('kpt-file-upload__zone', dragover && 'is-dragover', disabled && 'is-disabled'), ref: zoneRef, role: "button", id: field.id, tabIndex: disabled ? -1 : 0, "aria-disabled": locked || undefined, "aria-label": ariaLabel ?? undefined, "aria-labelledby": ariaLabel ? undefined : field.labelledBy, "aria-describedby": field.describedBy, "data-invalid": showError ? '' : undefined, onClick: openPicker, onKeyDown: onZoneKeyDown, onDragOver: (event) => {
|
|
59
67
|
event.preventDefault();
|
|
60
|
-
if (!
|
|
68
|
+
if (!locked)
|
|
61
69
|
setDragover(true);
|
|
62
70
|
}, onDragLeave: () => setDragover(false), onDrop: (event) => {
|
|
63
71
|
event.preventDefault();
|
|
64
72
|
setDragover(false);
|
|
65
|
-
if (!
|
|
73
|
+
if (!locked)
|
|
66
74
|
addFiles(event.dataTransfer?.files);
|
|
67
|
-
}, children: [_jsx("input", { ref: inputRef, type: "file", hidden: true, accept: accept, multiple: multiple, onChange: onSelect }), _jsx(KptIcon, { name: "upload", className: "kpt-file-upload__icon" }),
|
|
75
|
+
}, children: [_jsx("input", { ref: inputRef, type: "file", hidden: true, accept: accept, multiple: multiple, onChange: onSelect }), _jsx(KptIcon, { name: "upload", className: "kpt-file-upload__icon" }), _jsxs("div", { className: "kpt-file-upload__text", children: [hintBefore, _jsx("strong", { children: t('fileUpload.hintAction') }), hintAfter] }), accept ? _jsx("div", { className: "kpt-file-upload__hint", children: accept }) : null] }), current.length ? (_jsx("ul", { className: "kpt-file-upload__list", children: current.map((file, index) => (_jsxs("li", { className: "kpt-file-upload__file", children: [_jsx(KptIcon, { name: fileIcon(file) }), _jsx("span", { className: "kpt-file-upload__name", children: file.name }), _jsx("span", { className: "kpt-file-upload__size", children: formatSize(file) }), readOnly ? null : (_jsx("button", { type: "button", className: "kpt-file-upload__remove", "aria-label": `${t('fileUpload.remove')}: ${file.name}`, onClick: (event) => {
|
|
68
76
|
event.stopPropagation();
|
|
69
77
|
setCurrent(current.filter((item) => item !== file));
|
|
70
|
-
|
|
78
|
+
onTouch?.();
|
|
79
|
+
// Przycisk znika razem z pozycją — fokus wraca na strefę, a nie spada na <body>.
|
|
80
|
+
zoneRef.current?.focus();
|
|
81
|
+
}, children: _jsx(KptIcon, { name: "trash" }) }))] }, `${index}-${file.name}`))) })) : null] }));
|
|
71
82
|
}
|
|
@@ -28,9 +28,9 @@ Import: `import { KptFileUpload } from '@konce-pt/react';`
|
|
|
28
28
|
`event.target.value = ''` after reading the files, otherwise picking the same file twice in a row
|
|
29
29
|
would fire no `change` and the second pick would be silently lost.
|
|
30
30
|
|
|
31
|
-
## The hint
|
|
32
|
-
`fileUpload.hint` holds
|
|
33
|
-
|
|
31
|
+
## The hint is plain text
|
|
32
|
+
`fileUpload.hint` holds an `{action}` placeholder that is replaced by `fileUpload.hintAction` in
|
|
33
|
+
`<strong>`. Dictionaries carry no HTML, so a translation from a CMS cannot inject markup.
|
|
34
34
|
|
|
35
35
|
## Examples
|
|
36
36
|
<KptFileUpload value={files} onValueChange={setFiles} accept="image/*" multiple />
|
|
@@ -57,3 +57,6 @@ focus ring `--kpt-color-focus-ring`.
|
|
|
57
57
|
## Accessibility
|
|
58
58
|
The remove button is labelled from the dictionary. The zone itself is the keyboard path to the
|
|
59
59
|
system picker — see "Keyboard and states" above.
|
|
60
|
+
|
|
61
|
+
## Read-only and required
|
|
62
|
+
`readOnly` shows the list without the remove buttons and does not open the picker nor accept a drop. The zone takes `id` and `ariaLabel` and is linked to `kpt-form-field` (label and description); `button` accepts neither `aria-invalid` nor `aria-required`, so the error is the linked message. The hint is plain text: `fileUpload.hint` with `{action}` and `fileUpload.hintAction` (bold) — dictionaries no longer carry HTML. The remove button names the file.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
3
|
import { useRef, useState } from 'react';
|
|
3
4
|
import { cn } from "../internal/cn.js";
|
|
@@ -19,11 +20,13 @@ const NAV_KEYS = new Set(['ArrowLeft', 'ArrowRight', 'Home', 'End']);
|
|
|
19
20
|
*/
|
|
20
21
|
export function KptGalleria({ items = [], activeIndex, defaultActiveIndex = 0, onActiveIndexChange, showThumbnails = true, locale, dictionary, className, ...rest }) {
|
|
21
22
|
const { t } = useKptMessages({ locale, dictionary });
|
|
22
|
-
const [
|
|
23
|
+
const [requested, setActive] = useControllableState({
|
|
23
24
|
value: activeIndex,
|
|
24
25
|
defaultValue: defaultActiveIndex,
|
|
25
26
|
onChange: onActiveIndexChange,
|
|
26
27
|
});
|
|
28
|
+
// Indeks przycięty do listy: po skróceniu `items` (filtr) widok nie zostaje pusty.
|
|
29
|
+
const active = Math.min(Math.max(requested, 0), Math.max(items.length - 1, 0));
|
|
27
30
|
// Ogłoszenie dopiero po działaniu użytkownika — przy starcie region ma milczeć.
|
|
28
31
|
const [userMoved, setUserMoved] = useState(false);
|
|
29
32
|
const thumbsRef = useRef([]);
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
|
-
import { type DeepPartial, type KptLocale, type KptMessages } from '@konce-pt/i18n';
|
|
3
|
-
/** Kontrakt zwracany przez `useKptI18n()`. */
|
|
2
|
+
import { type DeepPartial, type KptLocale, type KptMessageKey, type KptMessages } from '@konce-pt/i18n';
|
|
4
3
|
export interface KptI18nContextValue {
|
|
5
4
|
/** Bieżący język. */
|
|
6
5
|
locale: KptLocale;
|
|
@@ -11,7 +10,8 @@ export interface KptI18nContextValue {
|
|
|
11
10
|
/** Słownik zarejestrowany dla danego języka; nieznany język spada na EN. */
|
|
12
11
|
messagesFor: (locale: KptLocale) => KptMessages;
|
|
13
12
|
/** Tłumaczy klucz w notacji kropkowej (`'paginator.range'`) z interpolacją `{name}`. */
|
|
14
|
-
|
|
13
|
+
/** Tłumaczenie klucza; `KptMessageKey` daje podpowiedzi kluczy bazowych, dowolny napis też przechodzi. */
|
|
14
|
+
t: (key: KptMessageKey | (string & {}), params?: Record<string, string | number>) => string;
|
|
15
15
|
}
|
|
16
16
|
export interface KptI18nProviderProps {
|
|
17
17
|
/** Język początkowy (domyślnie 'en'). */
|
|
@@ -1,12 +1,34 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
3
|
import { createContext, useCallback, useContext, useMemo, useState } from 'react';
|
|
3
4
|
import { KPT_MESSAGES_EN, KPT_MESSAGES_PL, deepMerge, translate, } from '@konce-pt/i18n';
|
|
5
|
+
import { isDevBuild } from "../internal/dev.js";
|
|
6
|
+
/*
|
|
7
|
+
* Wiązanie i18n dla Reacta. Odpowiednik `provideKptI18n` + `KptI18n` z Angulara,
|
|
8
|
+
* na tym samym rdzeniu z `@konce-pt/i18n` — te same klucze, ten sam fallback
|
|
9
|
+
* (słownik → EN → sam klucz) i ta sama interpolacja `{name}`.
|
|
10
|
+
*/
|
|
11
|
+
/** Kontrakt zwracany przez `useKptI18n()`. */
|
|
12
|
+
/** Klucze, o których brak już ostrzegaliśmy — jedno ostrzeżenie na klucz. */
|
|
13
|
+
const warnedKeys = new Set();
|
|
14
|
+
/**
|
|
15
|
+
* `translate` z ostrzeżeniem w buildzie deweloperskim, gdy klucza nie ma w żadnym słowniku —
|
|
16
|
+
* inaczej literówka daje po cichu sam klucz zamiast tekstu.
|
|
17
|
+
*/
|
|
18
|
+
function translateChecked(dict, key, params) {
|
|
19
|
+
const text = translate(dict, key, params);
|
|
20
|
+
if (text === key && isDevBuild() && !warnedKeys.has(key)) {
|
|
21
|
+
warnedKeys.add(key);
|
|
22
|
+
console.warn(`KptI18n: brak tłumaczenia dla klucza „${key}”.`);
|
|
23
|
+
}
|
|
24
|
+
return text;
|
|
25
|
+
}
|
|
4
26
|
const FALLBACK = {
|
|
5
27
|
locale: 'en',
|
|
6
28
|
setLocale: () => { },
|
|
7
29
|
messages: KPT_MESSAGES_EN,
|
|
8
30
|
messagesFor: () => KPT_MESSAGES_EN,
|
|
9
|
-
t: (key, params) =>
|
|
31
|
+
t: (key, params) => translateChecked(KPT_MESSAGES_EN, key, params),
|
|
10
32
|
};
|
|
11
33
|
const KptI18nContext = createContext(null);
|
|
12
34
|
/**
|
|
@@ -33,7 +55,7 @@ export function KptI18nProvider({ locale = 'en', messages, children }) {
|
|
|
33
55
|
setLocale: setCurrent,
|
|
34
56
|
messages: active,
|
|
35
57
|
messagesFor,
|
|
36
|
-
t: (key, params) =>
|
|
58
|
+
t: (key, params) => translateChecked(active, key, params),
|
|
37
59
|
};
|
|
38
60
|
}, [current, messagesFor]);
|
|
39
61
|
return _jsx(KptI18nContext.Provider, { value: value, children: children });
|
|
@@ -59,7 +81,7 @@ export function useKptMessages(overrides) {
|
|
|
59
81
|
...context,
|
|
60
82
|
locale: effectiveLocale,
|
|
61
83
|
messages: merged,
|
|
62
|
-
t: (key, params) =>
|
|
84
|
+
t: (key, params) => translateChecked(merged, key, params),
|
|
63
85
|
};
|
|
64
86
|
}, [context, locale, dictionary]);
|
|
65
87
|
}
|
package/dist/icon/icon.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
3
|
import { renderKptIconSvg } from '@konce-pt/icons';
|
|
3
4
|
import { useEffect, useSyncExternalStore } from 'react';
|
|
@@ -33,5 +34,7 @@ export function KptIcon({ name, size = '', label = '', className, style, ...rest
|
|
|
33
34
|
warned.add(name);
|
|
34
35
|
console.warn(`KptIcon: ikony „${name}” nie ma (jeszcze) w rejestrze — sprawdź nazwę albo zarejestruj ją (registerKptIcons / registerKptTablerIcons).`);
|
|
35
36
|
}, [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,
|
|
37
|
+
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,
|
|
38
|
+
// eslint-disable-next-line react/no-danger -- treść z rejestru, zsanityzowana przy `register()` (sanitizeSvgFragment)
|
|
39
|
+
dangerouslySetInnerHTML: { __html: html }, ...rest }));
|
|
37
40
|
}
|
package/dist/image/image.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
3
|
import { useEffect, useRef, useState } from 'react';
|
|
3
4
|
import { cn } from "../internal/cn.js";
|
|
5
|
+
import { kptLockScroll } from "../internal/scroll-lock.js";
|
|
4
6
|
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
5
7
|
import { KptIcon } from "../icon/icon.js";
|
|
6
8
|
/**
|
|
@@ -25,11 +27,9 @@ export function KptImage({ src, alt = '', width = null, preview = false, locale,
|
|
|
25
27
|
return;
|
|
26
28
|
if (!el.open)
|
|
27
29
|
el.showModal();
|
|
28
|
-
const
|
|
29
|
-
const saved = root.style.overflow;
|
|
30
|
-
root.style.overflow = 'hidden';
|
|
30
|
+
const releaseScroll = kptLockScroll();
|
|
31
31
|
return () => {
|
|
32
|
-
|
|
32
|
+
releaseScroll();
|
|
33
33
|
if (el.open)
|
|
34
34
|
el.close();
|
|
35
35
|
};
|
package/dist/input/input.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import type { ComponentPropsWithRef } from 'react';
|
|
2
|
-
export interface KptInputMaskProps extends Omit<ComponentPropsWithRef<'input'>, 'value' | 'defaultValue' | 'onChange'> {
|
|
2
|
+
export interface KptInputMaskProps extends Omit<ComponentPropsWithRef<'input'>, 'value' | 'defaultValue' | 'onChange' | 'required'> {
|
|
3
3
|
value?: string;
|
|
4
4
|
defaultValue?: string;
|
|
5
5
|
onValueChange?: (value: string) => void;
|
|
6
6
|
/** Wzorzec maski; pozostałe znaki to literały (spacje, nawiasy, myślniki). */
|
|
7
7
|
mask?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Pole wymagane — `aria-required`, a nie natywne `required` (jak w `KptInput`): natywne
|
|
10
|
+
* blokowałoby wysyłkę i uruchamiało walidację przeglądarki obok Twojej.
|
|
11
|
+
*/
|
|
12
|
+
required?: boolean;
|
|
8
13
|
invalid?: boolean;
|
|
9
14
|
touched?: boolean;
|
|
10
15
|
onTouch?: () => void;
|
|
@@ -16,4 +21,4 @@ export interface KptInputMaskProps extends Omit<ComponentPropsWithRef<'input'>,
|
|
|
16
21
|
* @example
|
|
17
22
|
* <KptInputMask mask="(999) 999-99-99" value={phone} onValueChange={setPhone} />
|
|
18
23
|
*/
|
|
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;
|
|
24
|
+
export declare function KptInputMask({ value, defaultValue, onValueChange, mask, placeholder, required, invalid, touched, onTouch, disabled, className, id, onBlur, 'aria-describedby': ariaDescribedBy, inputMode, ...rest }: Readonly<KptInputMaskProps>): import("react").JSX.Element;
|
|
@@ -1,13 +1,9 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
3
|
import { cn } from "../internal/cn.js";
|
|
3
4
|
import { useControllableState } from "../internal/use-controllable-state.js";
|
|
4
5
|
import { joinIds, useKptFieldA11y } from "../form-field/form-field-context.js";
|
|
5
|
-
|
|
6
|
-
const TOKENS = {
|
|
7
|
-
'9': /\d/,
|
|
8
|
-
a: /[a-zA-Z]/,
|
|
9
|
-
'*': /[a-zA-Z0-9]/,
|
|
10
|
-
};
|
|
6
|
+
import { KPT_MASK_TOKENS, kptApplyMask, kptMaskCaret } from "../internal/mask.js";
|
|
11
7
|
/**
|
|
12
8
|
* Pole z maską znakową. Tokeny: `9` = cyfra, `a` = litera, `*` = alfanumeryczny; reszta wzorca
|
|
13
9
|
* to literały wstawiane automatycznie.
|
|
@@ -15,52 +11,30 @@ const TOKENS = {
|
|
|
15
11
|
* @example
|
|
16
12
|
* <KptInputMask mask="(999) 999-99-99" value={phone} onValueChange={setPhone} />
|
|
17
13
|
*/
|
|
18
|
-
export function KptInputMask({ value, defaultValue = '', onValueChange, mask = '', placeholder, invalid = false, touched, onTouch, disabled = false, className, id, onBlur, 'aria-describedby': ariaDescribedBy, inputMode, ...rest }) {
|
|
14
|
+
export function KptInputMask({ value, defaultValue = '', onValueChange, mask = '', placeholder, required = false, invalid = false, touched, onTouch, disabled = false, className, id, onBlur, 'aria-describedby': ariaDescribedBy, inputMode, ...rest }) {
|
|
19
15
|
const field = useKptFieldA11y(id, 'kpt-input-mask');
|
|
20
16
|
// Klawiatura ekranowa z maski: same tokeny `9` (telefon, kod pocztowy) → numeryczna.
|
|
21
|
-
const tokens = [...mask].filter((char) => char in
|
|
17
|
+
const tokens = [...mask].filter((char) => char in KPT_MASK_TOKENS);
|
|
22
18
|
const autoInputMode = tokens.length && tokens.every((char) => char === '9') ? 'numeric' : 'text';
|
|
23
19
|
const inputId = field.id;
|
|
24
20
|
const [current, setCurrent] = useControllableState({ value, defaultValue, onChange: onValueChange });
|
|
25
21
|
const showInvalid = invalid && (touched ?? true);
|
|
26
|
-
/*
|
|
27
|
-
* Maska przepisuje wejście od zera: z tekstu bierzemy wyłącznie znaki pasujące do kolejnych
|
|
28
|
-
* tokenów, a literały wstawiamy same. Dzięki temu wklejenie „48123456789" i wpisanie
|
|
29
|
-
* „(48) 123…" dają ten sam wynik, a usunięcie literału nie zostawia dziury we wzorcu.
|
|
30
|
-
*/
|
|
31
|
-
const applyMask = (raw) => {
|
|
32
|
-
if (!mask)
|
|
33
|
-
return raw;
|
|
34
|
-
const chars = raw.split('');
|
|
35
|
-
let charIndex = 0;
|
|
36
|
-
let out = '';
|
|
37
|
-
for (let maskIndex = 0; maskIndex < mask.length && charIndex < chars.length;) {
|
|
38
|
-
const token = mask[maskIndex];
|
|
39
|
-
const rule = TOKENS[token];
|
|
40
|
-
if (rule) {
|
|
41
|
-
while (charIndex < chars.length && !rule.test(chars[charIndex]))
|
|
42
|
-
charIndex++;
|
|
43
|
-
if (charIndex >= chars.length)
|
|
44
|
-
break;
|
|
45
|
-
out += chars[charIndex];
|
|
46
|
-
charIndex++;
|
|
47
|
-
maskIndex++;
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
out += token;
|
|
51
|
-
maskIndex++;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return out;
|
|
55
|
-
};
|
|
56
22
|
const onInput = (event) => {
|
|
57
|
-
const
|
|
23
|
+
const el = event.target;
|
|
24
|
+
const raw = el.value;
|
|
25
|
+
const caret = el.selectionStart ?? raw.length;
|
|
26
|
+
const formatted = kptApplyMask(raw, mask);
|
|
58
27
|
setCurrent(formatted);
|
|
59
28
|
// Gdy maska odrzuci znak, wartość się nie zmienia i React nie przerysuje pola —
|
|
60
29
|
// wpisany znak zostałby na ekranie, więc DOM zrównujemy ręcznie.
|
|
61
|
-
|
|
30
|
+
el.value = formatted;
|
|
31
|
+
// Przepisanie `value` przenosi kursor na koniec; wraca za tyle samo znaczących znaków co przed zmianą.
|
|
32
|
+
if (document.activeElement === el) {
|
|
33
|
+
const at = kptMaskCaret(raw, caret, mask);
|
|
34
|
+
el.setSelectionRange(at, at);
|
|
35
|
+
}
|
|
62
36
|
};
|
|
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) => {
|
|
37
|
+
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-required": required || undefined, "aria-describedby": joinIds(ariaDescribedBy, field.describedBy), inputMode: inputMode ?? autoInputMode, type: "text", value: current, disabled: disabled, placeholder: placeholder || mask, onChange: onInput, onBlur: (event) => {
|
|
64
38
|
onBlur?.(event);
|
|
65
39
|
onTouch?.();
|
|
66
40
|
}, ...rest }) }));
|
package/dist/input-mask/llms.txt
CHANGED
|
@@ -46,3 +46,6 @@ New: `aria-invalid` after touch.
|
|
|
46
46
|
## Accessibility
|
|
47
47
|
A native text input; the mask doubles as the placeholder, so the expected shape is visible before
|
|
48
48
|
typing starts.
|
|
49
|
+
|
|
50
|
+
## Read-only and required
|
|
51
|
+
`readOnly` is the native read-only field. The caret stays where you typed: fixing a digit in the middle of a phone number no longer throws it to the end. The `a` and `*` tokens accept every letter (`ą`, `ł`, `ß`), not only ASCII.
|
|
@@ -19,6 +19,8 @@ export interface KptInputNumberProps extends Omit<ComponentPropsWithRef<'div'>,
|
|
|
19
19
|
/** Oznacza pole jako wymagane atrybutem `aria-required`. */
|
|
20
20
|
required?: boolean;
|
|
21
21
|
disabled?: boolean;
|
|
22
|
+
/** Tylko do odczytu — wartość widać i da się ją zaznaczyć, ale nie zmienić (także strzałkami). */
|
|
23
|
+
readOnly?: boolean;
|
|
22
24
|
invalid?: boolean;
|
|
23
25
|
touched?: boolean;
|
|
24
26
|
onTouch?: () => void;
|
|
@@ -37,4 +39,4 @@ export interface KptInputNumberProps extends Omit<ComponentPropsWithRef<'div'>,
|
|
|
37
39
|
* <KptInputNumber value={qty} onValueChange={setQty} min={0} max={99} suffix=" szt." />
|
|
38
40
|
* <KptInputNumber value={from} onValueChange={setFrom} showButtons={false} suffix=" zł" />
|
|
39
41
|
*/
|
|
40
|
-
export declare function KptInputNumber({ value, defaultValue, onValueChange, min, max, step, placeholder, suffix, showButtons, id, ariaLabel, required, disabled, invalid, touched, onTouch, locale, dictionary, className, ...rest }: Readonly<KptInputNumberProps>): import("react").JSX.Element;
|
|
42
|
+
export declare function KptInputNumber({ value, defaultValue, onValueChange, min, max, step, placeholder, suffix, showButtons, id, ariaLabel, required, disabled, readOnly, invalid, touched, onTouch, locale, dictionary, className, ...rest }: Readonly<KptInputNumberProps>): import("react").JSX.Element;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
3
|
import { useCallback, useState } from 'react';
|
|
3
4
|
import { cn } from "../internal/cn.js";
|
|
@@ -5,6 +6,9 @@ import { useControllableState } from "../internal/use-controllable-state.js";
|
|
|
5
6
|
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
6
7
|
import { KptIcon } from "../icon/icon.js";
|
|
7
8
|
import { useKptFieldA11y } from "../form-field/form-field-context.js";
|
|
9
|
+
import { kptRoundTo } from "../internal/step.js";
|
|
10
|
+
/** Liczba dziesiętna wpisana przez człowieka: bez `0x1A`, `1e3` i `Infinity`, które `Number()` też przyjmuje. */
|
|
11
|
+
const DECIMAL = /^[-+]?(?:\d+\.?\d*|\.\d+)$/;
|
|
8
12
|
/**
|
|
9
13
|
* Pole liczbowe ze strzałkami krokowania. Wartość to `number | null` — puste pole ma znaczenie,
|
|
10
14
|
* bo „brak wartości" to nie to samo co zero.
|
|
@@ -19,7 +23,7 @@ import { useKptFieldA11y } from "../form-field/form-field-context.js";
|
|
|
19
23
|
* <KptInputNumber value={qty} onValueChange={setQty} min={0} max={99} suffix=" szt." />
|
|
20
24
|
* <KptInputNumber value={from} onValueChange={setFrom} showButtons={false} suffix=" zł" />
|
|
21
25
|
*/
|
|
22
|
-
export function KptInputNumber({ value, defaultValue = null, onValueChange, min, max, step = 1, placeholder = '', suffix = '', showButtons = true, id, ariaLabel = null, required = false, disabled = false, invalid = false, touched, onTouch, locale, dictionary, className, ...rest }) {
|
|
26
|
+
export function KptInputNumber({ value, defaultValue = null, onValueChange, min, max, step = 1, placeholder = '', suffix = '', showButtons = true, id, ariaLabel = null, required = false, disabled = false, readOnly = false, invalid = false, touched, onTouch, locale, dictionary, className, ...rest }) {
|
|
23
27
|
const { t, locale: activeLocale } = useKptMessages({ locale, dictionary });
|
|
24
28
|
const field = useKptFieldA11y(id, 'kpt-input-number');
|
|
25
29
|
const [current, setCurrent] = useControllableState({
|
|
@@ -39,7 +43,9 @@ export function KptInputNumber({ value, defaultValue = null, onValueChange, min,
|
|
|
39
43
|
}, [min, max]);
|
|
40
44
|
// Zapis liczby wg aktywnego języka (PL: `12,5`), bez separatorów tysięcy — pole przyjmuje i kropkę,
|
|
41
45
|
// i przecinek, a grupowanie cyfr rozjechałoby się z tym, co user wpisuje.
|
|
42
|
-
|
|
46
|
+
// `latn`: cyfry łacińskie także w locale z własnymi (np. `ar`) — inaczej pole pokazywałoby zapis,
|
|
47
|
+
// którego parser niżej nie przeczyta.
|
|
48
|
+
const format = new Intl.NumberFormat(activeLocale, { useGrouping: false, maximumFractionDigits: 20, numberingSystem: 'latn' });
|
|
43
49
|
const display = current === null || current === undefined || Number.isNaN(current) ? '' : `${format.format(current)}${suffix}`;
|
|
44
50
|
const canIncrement = max === undefined || (current ?? -Infinity) < max;
|
|
45
51
|
const canDecrement = min === undefined || (current ?? Infinity) > min;
|
|
@@ -53,21 +59,19 @@ export function KptInputNumber({ value, defaultValue = null, onValueChange, min,
|
|
|
53
59
|
return;
|
|
54
60
|
}
|
|
55
61
|
// Bez przycinania: wartość spoza granic zostaje, a walidacja formularza ją zgłosi.
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
setCurrent(parsed);
|
|
62
|
+
if (DECIMAL.test(raw))
|
|
63
|
+
setCurrent(Number(raw));
|
|
59
64
|
};
|
|
60
65
|
const stepBy = (count) => {
|
|
61
66
|
const base = current ?? min ?? 0;
|
|
62
|
-
//
|
|
63
|
-
const decimals = (String(step).split('.')[1] ?? '').length;
|
|
67
|
+
// Precyzja kroku ALBO wartości, zależnie od tego, która jest drobniejsza (1.25 + 1 = 2.25).
|
|
64
68
|
setDraft(null);
|
|
65
|
-
setCurrent(clamp(
|
|
69
|
+
setCurrent(clamp(kptRoundTo(base + count * step, step, base)));
|
|
66
70
|
};
|
|
67
71
|
const onKeyDown = (event) => {
|
|
68
72
|
const steps = { ArrowUp: 1, ArrowDown: -1, PageUp: 10, PageDown: -10 };
|
|
69
73
|
const count = steps[event.key];
|
|
70
|
-
if (count === undefined || disabled)
|
|
74
|
+
if (count === undefined || disabled || readOnly)
|
|
71
75
|
return;
|
|
72
76
|
event.preventDefault();
|
|
73
77
|
stepBy(count);
|
|
@@ -82,10 +86,10 @@ export function KptInputNumber({ value, defaultValue = null, onValueChange, min,
|
|
|
82
86
|
setDraft(null);
|
|
83
87
|
onTouch?.();
|
|
84
88
|
};
|
|
85
|
-
return (_jsx("div", { className: cn('kpt-input-number-host', className), ...rest, children: _jsxs("div", { className: cn('kpt-input-number', disabled && 'kpt-input-number--disabled', showInvalid && 'kpt-input-number--invalid', !showButtons && 'kpt-input-number--plain'), children: [showButtons ? (_jsx("button", { type: "button", className: "kpt-input-number__btn", tabIndex: -1, disabled: disabled || !canDecrement, onMouseDown: (event) => event.preventDefault(), onClick: () => {
|
|
89
|
+
return (_jsx("div", { className: cn('kpt-input-number-host', className), ...rest, children: _jsxs("div", { className: cn('kpt-input-number', disabled && 'kpt-input-number--disabled', showInvalid && 'kpt-input-number--invalid', !showButtons && 'kpt-input-number--plain'), children: [showButtons ? (_jsx("button", { type: "button", className: "kpt-input-number__btn", tabIndex: -1, disabled: disabled || readOnly || !canDecrement, onMouseDown: (event) => event.preventDefault(), onClick: () => {
|
|
86
90
|
stepBy(-1);
|
|
87
91
|
onTouch?.();
|
|
88
|
-
}, "aria-label": t('inputNumber.decrement'), children: _jsx(KptIcon, { name: "minus" }) })) : null, _jsx("input", { className: "kpt-input-number__input", id: field.id, "aria-label": ariaLabel ?? undefined, "aria-required": required || undefined, "aria-invalid": showInvalid || undefined, "aria-describedby": field.describedBy, type: "text", inputMode: "decimal", role: "spinbutton", "aria-valuemin": min, "aria-valuemax": max, "aria-valuenow": current ?? undefined, "aria-valuetext": suffix && current !== null ? display : undefined, value: draft ?? display, disabled: disabled, placeholder: placeholder, onChange: onInput, onKeyDown: onKeyDown, onBlur: onBlur }), showButtons ? (_jsx("button", { type: "button", className: "kpt-input-number__btn", tabIndex: -1, disabled: disabled || !canIncrement, onMouseDown: (event) => event.preventDefault(), onClick: () => {
|
|
92
|
+
}, "aria-label": t('inputNumber.decrement'), children: _jsx(KptIcon, { name: "minus" }) })) : null, _jsx("input", { className: "kpt-input-number__input", id: field.id, "aria-label": ariaLabel ?? undefined, "aria-required": required || undefined, "aria-invalid": showInvalid || undefined, "aria-describedby": field.describedBy, type: "text", inputMode: "decimal", role: "spinbutton", "aria-valuemin": min, "aria-valuemax": max, "aria-valuenow": current ?? undefined, "aria-valuetext": suffix && current !== null ? display : undefined, value: draft ?? display, disabled: disabled, readOnly: readOnly, "aria-readonly": readOnly || undefined, placeholder: placeholder, onChange: onInput, onKeyDown: onKeyDown, onBlur: onBlur }), showButtons ? (_jsx("button", { type: "button", className: "kpt-input-number__btn", tabIndex: -1, disabled: disabled || readOnly || !canIncrement, onMouseDown: (event) => event.preventDefault(), onClick: () => {
|
|
89
93
|
stepBy(1);
|
|
90
94
|
onTouch?.();
|
|
91
95
|
}, "aria-label": t('inputNumber.increment'), children: _jsx(KptIcon, { name: "plus" }) })) : null] }) }));
|
|
@@ -16,7 +16,8 @@ A number field with stepper arrows. Import: `import { KptInputNumber } from '@ko
|
|
|
16
16
|
- `min`, `max`, `step` (default 1)
|
|
17
17
|
- `placeholder`, `suffix` — text shown after the number, e.g. ' szt.' or 'ms'
|
|
18
18
|
- `showButtons`: boolean (default true) — `false` leaves the bare field
|
|
19
|
-
- `disabled`, `invalid`, `touched`, `onTouch()`
|
|
19
|
+
- `disabled`, `readOnly`, `invalid`, `touched`, `onTouch()` — `readOnly` leaves the value selectable but
|
|
20
|
+
blocks typing, the arrows and the buttons (`aria-readonly`)
|
|
20
21
|
- `locale`, `dictionary` — i18n overrides for this component only
|
|
21
22
|
|
|
22
23
|
## Null is a value
|
|
@@ -39,7 +40,9 @@ The stepper labels come from `inputNumber.decrement` and `inputNumber.increment`
|
|
|
39
40
|
## Keyboard
|
|
40
41
|
The field is `role="spinbutton"` with `aria-valuemin`/`aria-valuemax`/`aria-valuenow` (and
|
|
41
42
|
`aria-valuetext` with the `suffix`, "5 pcs"). `↑`/`↓` change the value by `step`,
|
|
42
|
-
`PageUp`/`PageDown` by ten steps; stepping is rounded to the precision of `step
|
|
43
|
+
`PageUp`/`PageDown` by ten steps; stepping is rounded to the precision of `step` or of the current
|
|
44
|
+
value, whichever is finer (`1.25` + 1 gives `2.25`, not `2`). Only a plain decimal is accepted while
|
|
45
|
+
typing — `0x1A`, `1e3` and `Infinity` are ignored. A typed value is
|
|
43
46
|
**not** clamped while typing ("150" with `max` 99 stays "150", so the form's `max()` can report it) —
|
|
44
47
|
it is clamped on blur. The `−`/`+` buttons sit outside the Tab order and do not take focus from the
|
|
45
48
|
field, so focus never drops to `<body>` when a button goes disabled at a limit.
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ComponentPropsWithRef } from 'react';
|
|
2
2
|
import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
|
|
3
3
|
export interface KptInputOtpProps extends Omit<ComponentPropsWithRef<'div'>, 'onChange' | 'defaultValue'>, KptI18nOverrides {
|
|
4
4
|
value?: string;
|
|
5
5
|
defaultValue?: string;
|
|
6
6
|
onValueChange?: (value: string) => void;
|
|
7
7
|
length?: number;
|
|
8
|
+
/**
|
|
9
|
+
* Co ile komórek wstawić separator, licząc od lewej (`3` przy `length` 9 → `[][][]–[][][]–[][][]`).
|
|
10
|
+
* `0` (domyślnie) — bez grup. Separator jest wyłącznie wizualny: wartość to same znaki.
|
|
11
|
+
*/
|
|
12
|
+
groupSize?: number;
|
|
13
|
+
/** Znak między grupami komórek. */
|
|
14
|
+
groupSeparator?: string;
|
|
8
15
|
/** Dopuszcza wyłącznie cyfry i przełącza klawiaturę mobilną na numeryczną. */
|
|
9
16
|
integerOnly?: boolean;
|
|
10
17
|
/** Id pierwszej komórki (nie hosta) — klik w etykietę zaczyna wpisywanie od początku. */
|
|
@@ -12,15 +19,21 @@ export interface KptInputOtpProps extends Omit<ComponentPropsWithRef<'div'>, 'on
|
|
|
12
19
|
/** Nazwa grupy komórek, gdy nie opisuje jej etykieta (`KptFormField`). */
|
|
13
20
|
ariaLabel?: string | null;
|
|
14
21
|
disabled?: boolean;
|
|
22
|
+
/** Tylko do odczytu — kod widać, nie da się go zmienić. */
|
|
23
|
+
readOnly?: boolean;
|
|
24
|
+
/** Pole wymagane — `aria-required` na komórkach. */
|
|
25
|
+
required?: boolean;
|
|
15
26
|
invalid?: boolean;
|
|
16
27
|
touched?: boolean;
|
|
17
28
|
onTouch?: () => void;
|
|
18
29
|
}
|
|
19
30
|
/**
|
|
20
31
|
* Pole kodu jednorazowego (OTP/PIN) — N osobnych pól, automatyczne przejście do następnego
|
|
21
|
-
* i wklejanie całego kodu naraz. Wartością
|
|
32
|
+
* i wklejanie całego kodu naraz. Wartością są wpisane znaki **bez dziur** (`'13'`, nie `'1 3'`), więc
|
|
33
|
+
* walidator długości i backend widzą tyle znaków, ile naprawdę wpisano.
|
|
22
34
|
*
|
|
23
35
|
* @example
|
|
24
36
|
* <KptInputOtp value={code} onValueChange={setCode} length={6} integerOnly />
|
|
37
|
+
* <KptInputOtp value={code} onValueChange={setCode} length={9} groupSize={3} />
|
|
25
38
|
*/
|
|
26
|
-
export declare function KptInputOtp({ value, defaultValue, onValueChange, length, integerOnly, id, ariaLabel, disabled, invalid, touched, onTouch, locale, dictionary, className, ...rest }: Readonly<KptInputOtpProps>): import("react").JSX.Element;
|
|
39
|
+
export declare function KptInputOtp({ value, defaultValue, onValueChange, length, groupSize, groupSeparator, integerOnly, id, ariaLabel, disabled, readOnly, required, invalid, touched, onTouch, locale, dictionary, className, ...rest }: Readonly<KptInputOtpProps>): import("react").JSX.Element;
|